aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/README1
-rw-r--r--controller-server/pom.xml29
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java52
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java66
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/NotExistsException.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/OsController.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/RoutingController.java160
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationActivity.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java43
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentActivity.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentMetrics.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java34
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java8
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/MailVerifier.java30
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/QuotaUsage.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/SystemApplication.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/TenantAndApplicationId.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiff.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageStream.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageValidator.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java31
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/TestPackage.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntries.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDb.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/HostedAthenzIdentities.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/config/package-info.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLogger.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggingRequestHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/AssignedCertificate.java15
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificates.java336
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/UnassignedCertificate.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/concurrent/Once.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ConvergenceSummary.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java38
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java18
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobList.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobProfile.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobStatus.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/LockedStep.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeList.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeWithServices.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RetriggerEntry.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RetriggerEntrySerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Run.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunList.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunLog.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java2
-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/StepInfo.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/StepRunner.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Submission.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ZipBuilder.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/AbstractNameServiceRequest.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecord.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceRequest.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationMetaDataGarbageCollector.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdater.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingDatabaseMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainer.java34
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java60
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeManagementAssessor.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeRequestMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudAccountVerifier.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudDatabaseMaintainer.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirer.java139
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DataPlaneTokenRedeployer.java24
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainer.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java132
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunner.java64
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MeteringMonitorMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReadyJobsTrigger.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggerer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java34
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleCleanupMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java11
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/ApplicationMetrics.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/CostCalculator.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/FormattedNotification.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/MailTemplating.java101
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/MissingOptionalException.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java121
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatter.java92
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationSource.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java102
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java109
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStore.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CertifiedOsVersionSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ConfidenceOverrideSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerVersionSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DataplaneTokenSerializer.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DnsChallengeSerializer.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializer.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/JobControlFlags.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MailVerificationSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MockCuratorDb.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NodeVersionSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializer.java44
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionTargetSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RunSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/SupportAccessSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java47
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TrialNotifications.java57
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/UnassignedCertificateSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutor.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/ErrorResponses.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java120
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/HtmlResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParser.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ZipResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java512
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java234
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/CsvResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/certificate/EndpointCertificatesHandler.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/AccessRequestResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/AuditLogResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/DecryptionTokenResealer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/JobsResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/RequestUtils.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ResealedTokenResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/StatsResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/UpgraderResponse.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/WellKnownApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenService.java159
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java30
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/CliApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilter.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/TsdbQueryRewriter.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/FlagsClient.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/FlagsClientException.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResult.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/EndpointConfig.java30
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpointList.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/PreparedEndpoints.java18
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingId.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicy.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyId.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyList.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingStatus.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/ZoneRoutingPolicy.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveZoneRoutingContext.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/RoutingContext.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedZoneRoutingContext.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/Rotation.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationId.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationLock.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepository.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationState.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationStatus.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControlRequests.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzAccessControlRequests.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzCredentials.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzTenantSpec.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/Auth0Credentials.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControlRequests.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudTenantSpec.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudUserSessionManager.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/Credentials.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/TenantSpec.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccess.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessChange.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessControl.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessGrant.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/package-info.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/CertifiedOsVersion.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/DeploymentStatistics.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClient.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/NodeVersion.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersion.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionStatus.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/OsVersionTarget.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionStatus.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionTarget.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java14
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersionTarget.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/package-info.java2
-rw-r--r--controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.athenz.config.athenz.def2
-rw-r--r--controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.controller.def4
-rw-r--r--controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.core-dump-token-resealing.def2
-rw-r--r--controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.well-known-folder.def4
-rw-r--r--controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.maven.repository.config.maven-repository.def2
-rw-r--r--controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.tls.config.tls.def2
-rw-r--r--controller-server/src/main/resources/configdefinitions/vespa.hosted.rotation.config.rotations.def2
-rw-r--r--controller-server/src/main/resources/mail/cloud-trial-notification.vm3
-rw-r--r--controller-server/src/main/resources/mail/default-mail-content.vm131
-rw-r--r--controller-server/src/main/resources/mail/mail-verification.vm (renamed from controller-server/src/main/resources/mail/mail-verification.tmpl)8
-rw-r--r--controller-server/src/main/resources/mail/mail.vm516
-rw-r--r--controller-server/src/main/resources/mail/notification-message.vm6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java43
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java16
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java33
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiffTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/LinesComparatorTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/TestPackageTest.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/archive/CuratorArchiveBucketDbTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java339
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/concurrent/OnceTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ApplicationPackageBuilder.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentContext.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunnerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/QuotaUsageTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/TestConfigSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ZipBuilderTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueueTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationStoreMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ArtifactRegistryMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ArtifactRepositoryMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/AthenzFilterMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerProxyMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MetricsMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/SecretStoreMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java18
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java35
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveAccessMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdaterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdaterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java49
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeManagementAssessorTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeRequestMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CloudTrialExpirerTest.java93
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainerTest.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgraderTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainerTest.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java89
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdaterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/JobRunnerTest.java115
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MeteringMonitorMaintainerTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcherTest.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeSchedulerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggererTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java16
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java10
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdaterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatterTest.java22
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java84
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java22
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStoreTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/CertifiedOsVersionSerializerTest.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ControllerVersionSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/DnsChallengeSerializerTest.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/MailVerificationSerializerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializerTest.java20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionTargetSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java2
-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/SupportAccessSerializerTest.java4
-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/persistence/UnassignedCertificateSerializerTest.java1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyResponseTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ApplicationRequestToDiscFilterRequestWrapper.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java11
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ResponseHandlerToApplicationResponseWrapper.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java271
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java15
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/CliApiHandlerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParserTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1-app2.json1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1.json2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/overview-enclave.json9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java236
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2Test.java158
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-invoices2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants.json62
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response.json4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list.json12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view.json49
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandlerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/WellKnownApiHandlerTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/responses/maintenance.json7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenServiceTest.java144
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java18
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/TsdbQueryRewriterTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/os/OsApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/AllowingFilter.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/DeploymentPlayground.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment.xml3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_alt_full.xml3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_alternative.xml1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_base.xml1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_full.xml1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simple.xml1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simpler.xml1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simplest.xml1
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResultTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiOnPremTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-info-after-created.json11
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/RoutingPoliciesTest.java237
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/security/CloudUserSessionManagerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/Keys.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecretStoreMock.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecureContainerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClientTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java78
-rw-r--r--controller-server/src/test/resources/config-models/cd/config-models-cd.xml1
-rw-r--r--controller-server/src/test/resources/config-models/cd/config-models-main.xml1
-rw-r--r--controller-server/src/test/resources/mail/notification.html (renamed from controller-server/src/main/resources/mail/mail-notification.tmpl)154
-rw-r--r--controller-server/src/test/resources/mail/trial-expired.html (renamed from controller-server/src/test/resources/mail/notification.txt)149
-rw-r--r--controller-server/src/test/resources/mail/trial-expiring-immediately.html646
-rw-r--r--controller-server/src/test/resources/mail/trial-expiring-soon.html646
-rw-r--r--controller-server/src/test/resources/mail/trial-reminder.html646
-rw-r--r--controller-server/src/test/resources/mail/welcome.html646
469 files changed, 7313 insertions, 2725 deletions
diff --git a/controller-server/README b/controller-server/README
new file mode 100644
index 00000000000..476a95019b2
--- /dev/null
+++ b/controller-server/README
@@ -0,0 +1 @@
+controller-server implements the control plane for a Vespa cloud system.
diff --git a/controller-server/pom.xml b/controller-server/pom.xml
index 0fcd55eb7d3..a9db2cace85 100644
--- a/controller-server/pom.xml
+++ b/controller-server/pom.xml
@@ -1,5 +1,5 @@
<?xml version="1.0"?>
-<!-- Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
@@ -118,6 +118,33 @@
<!-- compile -->
<dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <!-- Must use the one provided by Jdisc to prevent two instances of slf4j classes. -->
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.velocity.tools</groupId>
+ <artifactId>velocity-tools-generic</artifactId>
+ <exclusions>
+ <exclusion>
+ <!-- Must use the one provided by Jdisc to prevent two instances of slf4j classes. -->
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
</dependency>
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
index f7eb7cdde0d..0e6f29c760d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index 90653d85aed..d7a3d4fb9e5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.component.Version;
@@ -43,6 +43,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.configserver.Deployment
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.dataplanetoken.DataplaneTokenVersions;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationStore;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ArtifactRepository;
@@ -523,7 +524,7 @@ public class ApplicationController {
try (Mutex lock = lock(applicationId)) {
LockedApplication application = new LockedApplication(requireApplication(applicationId), lock);
application.get().revisions().last().map(ApplicationVersion::id).ifPresent(lastRevision::set);
- return prepareEndpoints(deployment, job, application, applicationPackage, deployLogger);
+ return prepareEndpoints(deployment, job, application, applicationPackage, deployLogger, lock);
}
};
@@ -553,38 +554,32 @@ public class ApplicationController {
if (warnings.isEmpty())
controller.notificationsDb().removeNotification(source, Notification.Type.applicationPackage);
else
- controller.notificationsDb().setNotification(source, Notification.Type.applicationPackage, Notification.Level.warning, warnings);
+ controller.notificationsDb().setApplicationPackageNotification(source, warnings);
}
lockApplicationOrThrow(applicationId, application ->
store(application.with(job.application().instance(),
i -> i.withNewDeployment(zone, revision, platform,
clock.instant(), warningsFrom(dataAndResult.result().log()),
- quotaUsage, dataAndResult.data().cloudAccount().orElse(CloudAccount.empty)))));
+ quotaUsage, dataAndResult.data().cloudAccount().orElse(CloudAccount.empty),
+ dataAndResult.data.dataPlaneTokens()))));
return dataAndResult.result();
}
}
private PreparedEndpoints prepareEndpoints(DeploymentId deployment, JobId job, LockedApplication application,
ApplicationPackageStream applicationPackage,
- Consumer<String> deployLogger) {
+ Consumer<String> deployLogger,
+ Mutex applicationLock) {
Instance instance = application.get().require(job.application().instance());
Tags tags = applicationPackage.truncatedPackage().deploymentSpec().instance(instance.name())
.map(DeploymentInstanceSpec::tags)
.orElseGet(Tags::empty);
- Optional<EndpointCertificate> certificate = endpointCertificates.get(instance, deployment.zoneId(), applicationPackage.truncatedPackage().deploymentSpec());
- certificate.ifPresent(e -> deployLogger.accept("Using CA signed certificate version %s".formatted(e.version())));
- BasicServicesXml services;
- try {
- services = applicationPackage.truncatedPackage().services(deployment, tags);
- } catch (Exception e) {
- // If the basic parsing done by the controller fails, we ignore the exception here so that
- // complete parsing errors are propagated from the config server. Otherwise, throwing here
- // will interrupt the request while it's being streamed to the config server
- log.warning("Ignoring failure to parse services.xml for deployment " + deployment +
- " while streaming application package: " + Exceptions.toMessageString(e));
- services = BasicServicesXml.empty;
- }
+ EndpointCertificate certificate = endpointCertificates.get(deployment,
+ applicationPackage.truncatedPackage().deploymentSpec(),
+ applicationLock);
+ deployLogger.accept("Using CA signed certificate version %s".formatted(certificate.version()));
+ BasicServicesXml services = applicationPackage.truncatedPackage().services(deployment, tags);
return controller.routing().of(deployment).prepare(services, certificate, application);
}
@@ -700,12 +695,29 @@ public class ApplicationController {
operatorCertificates = Stream.concat(operatorCertificates.stream(), testerCertificate.stream()).toList();
}
Supplier<Optional<CloudAccount>> cloudAccount = () -> cloudAccountOverride.apply(decideCloudAccountOf(deployment, applicationPackage.truncatedPackage().deploymentSpec()));
- List<DataplaneTokenVersions> dataplaneTokenVersions = controller.dataplaneTokenService().listTokens(application.tenant());
Supplier<DeploymentEndpoints> endpoints = () -> {
if (preparedEndpoints == null) return DeploymentEndpoints.none;
PreparedEndpoints prepared = preparedEndpoints.get();
generatedEndpoints.set(prepared.endpoints().generated());
- return new DeploymentEndpoints(prepared.containerEndpoints(), prepared.certificate());
+ return new DeploymentEndpoints(prepared.containerEndpoints(), Optional.of(prepared.certificate()));
+ };
+ Supplier<List<DataplaneTokenVersions>> dataplaneTokenVersions = () -> {
+ Tags tags = applicationPackage.truncatedPackage().deploymentSpec()
+ .instance(application.instance())
+ .map(DeploymentInstanceSpec::tags)
+ .orElse(Tags.empty());
+ BasicServicesXml services = applicationPackage.truncatedPackage().services(deployment, tags);
+ Set<TokenId> referencedTokens = services.containers().stream()
+ .flatMap(container -> container.dataPlaneTokens().stream())
+ .collect(toSet());
+ List<DataplaneTokenVersions> currentTokens = controller.dataplaneTokenService().listTokens(application.tenant()).stream()
+ .filter(token -> referencedTokens.contains(token.tokenId()))
+ .toList();
+ return Stream.concat(currentTokens.stream(),
+ referencedTokens.stream()
+ .filter(token -> currentTokens.stream().noneMatch(t -> t.tokenId().equals(token)))
+ .map(token -> new DataplaneTokenVersions(token, List.of(), Instant.EPOCH)))
+ .toList();
};
DeploymentData deploymentData = new DeploymentData(application, zone, applicationPackage::zipStream, platform,
endpoints, dockerImageRepo, domain, deploymentQuota, tenantSecretStores, operatorCertificates, cloudAccount, dataplaneTokenVersions, dryRun);
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 6cbcc64cf33..0b693bb9894 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.component.AbstractComponent;
@@ -131,10 +131,10 @@ public class Controller extends AbstractComponent {
auditLogger = new AuditLogger(curator, clock);
jobControl = new JobControl(new JobControlFlags(curator, flagSource));
archiveBucketDb = new CuratorArchiveBucketDb(this);
- notifier = new Notifier(curator, serviceRegistry.zoneRegistry(), serviceRegistry.mailer(), flagSource);
+ notifier = new Notifier(curator, serviceRegistry.consoleUrls(), serviceRegistry.mailer(), flagSource);
notificationsDb = new NotificationsDb(this);
supportAccessControl = new SupportAccessControl(this);
- mailVerifier = new MailVerifier(serviceRegistry.zoneRegistry().dashboardUrl(), tenantController, serviceRegistry.mailer(), curator, clock);
+ mailVerifier = new MailVerifier(serviceRegistry.consoleUrls(), tenantController, serviceRegistry.mailer(), curator, clock);
dataplaneTokenService = new DataplaneTokenService(this);
// Record the version of this controller
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
index 14bd537a056..0a9c680251c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Instance.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.component.Version;
@@ -7,6 +7,8 @@ import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.DataplaneTokenVersions;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
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.AssignedRotation;
@@ -31,6 +33,8 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
+import static java.util.Comparator.naturalOrder;
+
/**
* An instance of an application.
*
@@ -65,19 +69,24 @@ public class Instance {
}
public Instance withNewDeployment(ZoneId zone, RevisionId revision, Version version, Instant instant,
- Map<DeploymentMetrics.Warning, Integer> warnings, QuotaUsage quotaUsage, CloudAccount cloudAccount) {
+ Map<DeploymentMetrics.Warning, Integer> warnings, QuotaUsage quotaUsage, CloudAccount cloudAccount,
+ List<DataplaneTokenVersions> dataPlaneTokens) {
+ Map<TokenId, Instant> dataPlaneTokenIds = dataPlaneTokens.stream().collect(Collectors.toMap(token -> token.tokenId(),
+ token -> token.lastUpdated()));
// Use info from previous deployment if available, otherwise create a new one.
Deployment previousDeployment = deployments.getOrDefault(zone, new Deployment(zone, cloudAccount, revision,
version, instant,
DeploymentMetrics.none,
DeploymentActivity.none,
QuotaUsage.none,
- OptionalDouble.empty()));
+ OptionalDouble.empty(),
+ dataPlaneTokenIds));
Deployment newDeployment = new Deployment(zone, cloudAccount, revision, version, instant,
previousDeployment.metrics().with(warnings),
previousDeployment.activity(),
quotaUsage,
- previousDeployment.cost());
+ previousDeployment.cost(),
+ dataPlaneTokenIds);
return with(newDeployment);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
index 066d10041c2..830e40bd638 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedApplication.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.config.application.api.DeploymentSpec;
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 7d19acfce80..bfba17bef22 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.google.common.collect.BiMap;
@@ -10,6 +10,7 @@ import com.yahoo.transaction.Mutex;
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.billing.PlanId;
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;
@@ -151,12 +152,14 @@ public abstract class LockedTenant {
private final ArchiveAccess archiveAccess;
private final Optional<Instant> invalidateUserSessionsBefore;
private final Optional<BillingReference> billingReference;
+ private final PlanId planId;
private Cloud(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<SimplePrincipal> creator,
BiMap<PublicKey, SimplePrincipal> developerKeys, TenantInfo info,
List<TenantSecretStore> tenantSecretStores, ArchiveAccess archiveAccess,
Optional<Instant> invalidateUserSessionsBefore, Instant tenantRolesLastMaintained,
- List<CloudAccountInfo> cloudAccounts, Optional<BillingReference> billingReference) {
+ List<CloudAccountInfo> cloudAccounts, Optional<BillingReference> billingReference,
+ PlanId planId) {
super(name, createdAt, lastLoginInfo, tenantRolesLastMaintained, cloudAccounts);
this.developerKeys = ImmutableBiMap.copyOf(developerKeys);
this.creator = creator;
@@ -165,15 +168,20 @@ public abstract class LockedTenant {
this.archiveAccess = archiveAccess;
this.invalidateUserSessionsBefore = invalidateUserSessionsBefore;
this.billingReference = billingReference;
+ this.planId = planId;
}
private Cloud(CloudTenant tenant) {
- this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), tenant.creator(), tenant.developerKeys(), tenant.info(), tenant.tenantSecretStores(), tenant.archiveAccess(), tenant.invalidateUserSessionsBefore(), tenant.tenantRolesLastMaintained(), tenant.cloudAccounts(), tenant.billingReference());
+ this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), tenant.creator(), tenant.developerKeys(),
+ tenant.info(), tenant.tenantSecretStores(), tenant.archiveAccess(), tenant.invalidateUserSessionsBefore(),
+ tenant.tenantRolesLastMaintained(), tenant.cloudAccounts(), tenant.billingReference(), tenant.planId());
}
@Override
public CloudTenant get() {
- return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores,
+ archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained,
+ cloudAccounts, billingReference, planId);
}
public Cloud withDeveloperKey(PublicKey key, Principal principal) {
@@ -184,56 +192,84 @@ public abstract class LockedTenant {
if (keys.inverse().containsKey(simplePrincipal))
throw new IllegalArgumentException(principal + " is already associated with key " + KeyUtils.toPem(keys.inverse().get(simplePrincipal)));
keys.put(key, simplePrincipal);
- return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess,
+ invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts,
+ billingReference, planId);
}
public Cloud withoutDeveloperKey(PublicKey key) {
BiMap<PublicKey, SimplePrincipal> keys = HashBiMap.create(developerKeys);
keys.remove(key);
- return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info, tenantSecretStores, archiveAccess,
+ invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference,
+ planId);
}
public Cloud withInfo(TenantInfo newInfo) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo, tenantSecretStores,
+ archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts,
+ billingReference, planId);
}
@Override
public LockedTenant with(LastLoginInfo lastLoginInfo) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores,
+ archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts,
+ billingReference, planId);
}
public Cloud withSecretStore(TenantSecretStore tenantSecretStore) {
ArrayList<TenantSecretStore> secretStores = new ArrayList<>(tenantSecretStores);
secretStores.add(tenantSecretStore);
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess,
+ invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts,
+ billingReference, planId);
}
public Cloud withoutSecretStore(TenantSecretStore tenantSecretStore) {
ArrayList<TenantSecretStore> secretStores = new ArrayList<>(tenantSecretStores);
secretStores.remove(tenantSecretStore);
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, secretStores, archiveAccess,
+ invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts,
+ billingReference, planId);
}
public Cloud withArchiveAccess(ArchiveAccess archiveAccess) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore,tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess,
+ invalidateUserSessionsBefore,tenantRolesLastMaintained, cloudAccounts,
+ billingReference, planId);
}
public Cloud withInvalidateUserSessionsBefore(Instant invalidateUserSessionsBefore) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, Optional.of(invalidateUserSessionsBefore), tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess,
+ Optional.of(invalidateUserSessionsBefore), tenantRolesLastMaintained, cloudAccounts,
+ billingReference, planId);
}
@Override
public LockedTenant with(Instant tenantRolesLastMaintained) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess,
+ invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts,
+ billingReference, planId);
}
@Override
public LockedTenant withCloudAccounts(List<CloudAccountInfo> cloudAccounts) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, billingReference);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess,
+ invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts,
+ billingReference, planId);
}
public Cloud with(BillingReference billingReference) {
- return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts, Optional.of(billingReference));
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess,
+ invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts,
+ Optional.of(billingReference), planId);
+ }
+
+ public Cloud withPlanId(PlanId planId) {
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess,
+ invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccounts,
+ billingReference, planId);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/NotExistsException.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/NotExistsException.java
index f2cb4346b6a..064a2a39860 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/NotExistsException.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/NotExistsException.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.text.Text;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/OsController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/OsController.java
index 1ca12cac957..bec7c40d2a9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/OsController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/OsController.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.component.Version;
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 b1ffce65852..f11d67762ad 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.google.common.hash.HashCode;
@@ -14,9 +14,9 @@ import com.yahoo.config.provision.zone.AuthMethod;
import com.yahoo.config.provision.zone.RoutingMethod;
import com.yahoo.config.provision.zone.ZoneApi;
import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.Flags;
+import com.yahoo.vespa.flags.StringFlag;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.dns.Record;
@@ -32,6 +32,7 @@ import com.yahoo.vespa.hosted.controller.application.SystemApplication;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.application.pkg.BasicServicesXml;
import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue.Priority;
+import com.yahoo.vespa.hosted.controller.routing.EndpointConfig;
import com.yahoo.vespa.hosted.controller.routing.GeneratedEndpointList;
import com.yahoo.vespa.hosted.controller.routing.PreparedEndpoints;
import com.yahoo.vespa.hosted.controller.routing.RoutingId;
@@ -51,7 +52,6 @@ import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -64,6 +64,8 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -79,11 +81,12 @@ import static java.util.stream.Collectors.toMap;
*/
public class RoutingController {
+ private static final Logger LOG = Logger.getLogger(RoutingController.class.getName());
+
private final Controller controller;
private final RoutingPolicies routingPolicies;
private final RotationRepository rotationRepository;
- private final BooleanFlag generatedEndpoints;
- private final BooleanFlag legacyEndpoints;
+ private final StringFlag endpointConfig;
public RoutingController(Controller controller, RotationsConfig rotationsConfig) {
this.controller = Objects.requireNonNull(controller, "controller must be non-null");
@@ -91,8 +94,7 @@ public class RoutingController {
this.rotationRepository = new RotationRepository(Objects.requireNonNull(rotationsConfig, "rotationsConfig must be non-null"),
controller.applications(),
controller.curator());
- this.generatedEndpoints = Flags.RANDOMIZED_ENDPOINT_NAMES.bindTo(controller.flagSource());
- this.legacyEndpoints = Flags.LEGACY_ENDPOINTS.bindTo(controller.flagSource());
+ this.endpointConfig = Flags.ENDPOINT_CONFIG.bindTo(controller.flagSource());
}
/** Create a routing context for given deployment */
@@ -122,8 +124,23 @@ public class RoutingController {
return rotationRepository;
}
+ /** Returns the endpoint config to use for given instance */
+ public EndpointConfig endpointConfig(ApplicationId instance) {
+ String flagValue = endpointConfig.with(FetchVector.Dimension.TENANT_ID, instance.tenant().value())
+ .with(FetchVector.Dimension.APPLICATION_ID, TenantAndApplicationId.from(instance).serialized())
+ .with(FetchVector.Dimension.INSTANCE_ID, instance.serializedForm())
+ .value();
+ return switch (flagValue) {
+ case "legacy" -> EndpointConfig.legacy;
+ case "combined" -> EndpointConfig.combined;
+ case "generated" -> EndpointConfig.generated;
+ default -> throw new IllegalArgumentException("Invalid endpoint-config flag value: '" + flagValue + "', must be " +
+ "'legacy', 'combined' or 'generated'");
+ };
+ }
+
/** Prepares and returns the endpoints relevant for given deployment */
- public PreparedEndpoints prepare(DeploymentId deployment, BasicServicesXml services, Optional<EndpointCertificate> certificate, LockedApplication application) {
+ public PreparedEndpoints prepare(DeploymentId deployment, BasicServicesXml services, EndpointCertificate certificate, LockedApplication application) {
EndpointList endpoints = EndpointList.EMPTY;
DeploymentSpec spec = application.get().deploymentSpec();
@@ -135,9 +152,9 @@ public class RoutingController {
}
// Add zone-scoped endpoints
- Map<EndpointId, GeneratedEndpointList> generatedForDeclaredEndpoints = new HashMap<>();
+ Map<EndpointId, List<GeneratedEndpoint>> generatedForDeclaredEndpoints = new HashMap<>();
Set<ClusterSpec.Id> clustersWithToken = new HashSet<>();
- boolean generatedEndpointsEnabled = generatedEndpointsEnabled(deployment.applicationId());
+ EndpointConfig config = endpointConfig(deployment.applicationId());
RoutingPolicyList applicationPolicies = policies().read(TenantAndApplicationId.from(deployment.applicationId()));
RoutingPolicyList deploymentPolicies = applicationPolicies.deployment(deployment);
for (var container : services.containers()) {
@@ -149,11 +166,12 @@ public class RoutingController {
Optional<RoutingPolicy> clusterPolicy = deploymentPolicies.cluster(clusterId).first();
List<GeneratedEndpoint> generatedForCluster = clusterPolicy.map(policy -> policy.generatedEndpoints().cluster().asList())
.orElseGet(List::of);
- // Generate endpoints if cluster does not have any
- if (generatedForCluster.isEmpty()) {
- generatedForCluster = generateEndpoints(tokenSupported, certificate, Optional.empty());
+ // Generate endpoint for each auth method, if not present
+ generatedForCluster = generateEndpoints(AuthMethod.mtls, certificate, Optional.empty(), generatedForCluster);
+ if (tokenSupported) {
+ generatedForCluster = generateEndpoints(AuthMethod.token, certificate, Optional.empty(), generatedForCluster);
}
- GeneratedEndpointList generatedEndpoints = generatedEndpointsEnabled ? GeneratedEndpointList.copyOf(generatedForCluster) : GeneratedEndpointList.EMPTY;
+ GeneratedEndpointList generatedEndpoints = config.supportsGenerated() ? GeneratedEndpointList.copyOf(generatedForCluster) : GeneratedEndpointList.EMPTY;
endpoints = endpoints.and(endpointsOf(deployment, clusterId, generatedEndpoints).scope(Scope.zone));
}
@@ -162,18 +180,34 @@ public class RoutingController {
ClusterSpec.Id clusterId = ClusterSpec.Id.from(container.id());
applicationPolicies.cluster(clusterId).asList().stream()
.flatMap(policy -> policy.generatedEndpoints().declared().asList().stream())
- .forEach(ge -> generatedForDeclaredEndpoints.computeIfAbsent(ge.endpoint().get(), (k) -> GeneratedEndpointList.of(ge)));
+ .forEach(ge -> {
+ List<GeneratedEndpoint> generated = generatedForDeclaredEndpoints.computeIfAbsent(ge.endpoint().get(), (k) -> new ArrayList<>());
+ if (!generated.contains(ge)) {
+ generated.add(ge);
+ }
+ });
}
// Generate endpoints if declared endpoint does not have any
Stream.concat(spec.endpoints().stream(), spec.instances().stream().flatMap(i -> i.endpoints().stream()))
.forEach(endpoint -> {
EndpointId endpointId = EndpointId.of(endpoint.endpointId());
- generatedForDeclaredEndpoints.computeIfAbsent(endpointId, (k) -> {
+ generatedForDeclaredEndpoints.compute(endpointId, (k, old) -> {
+ if (old == null) {
+ old = List.of();
+ }
+ List<GeneratedEndpoint> generatedEndpoints = generateEndpoints(AuthMethod.mtls, certificate, Optional.of(endpointId), old);
boolean tokenSupported = clustersWithToken.contains(ClusterSpec.Id.from(endpoint.containerId()));
- return generatedEndpointsEnabled ? GeneratedEndpointList.copyOf(generateEndpoints(tokenSupported, certificate, Optional.of(endpointId))) : null;
+ if (tokenSupported){
+ generatedEndpoints = generateEndpoints(AuthMethod.token, certificate, Optional.of(endpointId), generatedEndpoints);
+ }
+ return generatedEndpoints;
});
});
- Map<EndpointId, GeneratedEndpointList> generatedEndpoints = generatedEndpointsEnabled ? generatedForDeclaredEndpoints : Map.of();
+ Map<EndpointId, GeneratedEndpointList> generatedEndpoints = config.supportsGenerated()
+ ? generatedForDeclaredEndpoints.entrySet()
+ .stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, kv -> GeneratedEndpointList.copyOf(kv.getValue())))
+ : Map.of();
endpoints = endpoints.and(declaredEndpointsOf(application.get().id(), spec, generatedEndpoints).targets(deployment));
PreparedEndpoints prepared = new PreparedEndpoints(deployment,
endpoints,
@@ -183,13 +217,9 @@ public class RoutingController {
// Register rotation-backed endpoints in DNS
registerRotationEndpointsInDns(prepared);
- return prepared;
- }
+ LOG.log(Level.FINE, () -> "Prepared endpoints: " + prepared);
- private List<GeneratedEndpoint> generateEndpoints(boolean tokenSupported, Optional<EndpointCertificate> certificate, Optional<EndpointId> endpoint) {
- return certificate.flatMap(EndpointCertificate::randomizedId)
- .map(id -> generateEndpoints(id, tokenSupported, endpoint))
- .orElseGet(List::of);
+ return prepared;
}
// -------------- Implicit endpoints (scopes 'zone' and 'weighted') --------------
@@ -197,19 +227,22 @@ public class RoutingController {
/** Returns the zone- and region-scoped endpoints of given deployment */
public EndpointList endpointsOf(DeploymentId deployment, ClusterSpec.Id cluster, GeneratedEndpointList generatedEndpoints) {
requireGeneratedEndpoints(generatedEndpoints, false);
+ boolean generatedEndpointsAvailable = !generatedEndpoints.isEmpty();
boolean tokenSupported = !generatedEndpoints.authMethod(AuthMethod.token).isEmpty();
- RoutingMethod routingMethod = controller.zoneRegistry().routingMethod(deployment.zoneId());
boolean isProduction = deployment.zoneId().environment().isProduction();
+ RoutingMethod routingMethod = controller.zoneRegistry().routingMethod(deployment.zoneId());
List<Endpoint> endpoints = new ArrayList<>();
Endpoint.EndpointBuilder zoneEndpoint = Endpoint.of(deployment.applicationId())
.routingMethod(routingMethod)
.on(Port.fromRoutingMethod(routingMethod))
+ .legacy(generatedEndpointsAvailable)
.target(cluster, deployment);
endpoints.add(zoneEndpoint.in(controller.system()));
ZoneApi zone = controller.zoneRegistry().zones().all().get(deployment.zoneId()).get();
Endpoint.EndpointBuilder regionEndpoint = Endpoint.of(deployment.applicationId())
.routingMethod(routingMethod)
.on(Port.fromRoutingMethod(routingMethod))
+ .legacy(generatedEndpointsAvailable)
.targetRegion(cluster,
zone.getCloudNativeRegionName(),
zone.getCloudName());
@@ -226,12 +259,14 @@ public class RoutingController {
};
if (include) {
endpoints.add(zoneEndpoint.generatedFrom(generatedEndpoint)
+ .legacy(false)
.authMethod(generatedEndpoint.authMethod())
.in(controller.system()));
// Only a single region endpoint is needed, not one per auth method
if (isProduction && generatedEndpoint.authMethod() == AuthMethod.mtls) {
GeneratedEndpoint weightedGeneratedEndpoint = generatedEndpoint.withClusterPart(weightedClusterPart(cluster, deployment));
endpoints.add(regionEndpoint.generatedFrom(weightedGeneratedEndpoint)
+ .legacy(false)
.authMethod(AuthMethod.none)
.in(controller.system()));
}
@@ -257,6 +292,7 @@ public class RoutingController {
var endpoints = new ArrayList<Endpoint>();
var directMethods = 0;
var availableRoutingMethods = routingMethodsOfAll(deployments);
+ boolean generatedEndpointsAvailable = !generatedEndpoints.isEmpty();
for (var method : availableRoutingMethods) {
if (method.isDirect() && ++directMethods > 1) {
throw new IllegalArgumentException("Invalid routing methods for " + routingId + ": Exceeded maximum " +
@@ -265,10 +301,11 @@ public class RoutingController {
Endpoint.EndpointBuilder builder = Endpoint.of(routingId.instance())
.target(routingId.endpointId(), cluster, deployments)
.on(Port.fromRoutingMethod(method))
+ .legacy(generatedEndpointsAvailable)
.routingMethod(method);
endpoints.add(builder.in(controller.system()));
for (var ge : generatedEndpoints) {
- endpoints.add(builder.generatedFrom(ge).authMethod(ge.authMethod()).in(controller.system()));
+ endpoints.add(builder.generatedFrom(ge).legacy(false).authMethod(ge.authMethod()).in(controller.system()));
}
}
return filterEndpoints(routingId.instance(), EndpointList.copyOf(endpoints));
@@ -280,16 +317,18 @@ public class RoutingController {
requireGeneratedEndpoints(generatedEndpoints, true);
ZoneId zone = deployments.keySet().iterator().next().zoneId(); // Where multiple zones are possible, they all have the same routing method.
RoutingMethod routingMethod = usesSharedRouting(zone) ? RoutingMethod.sharedLayer4 : RoutingMethod.exclusive;
+ boolean generatedEndpointsAvailable = !generatedEndpoints.isEmpty();
Endpoint.EndpointBuilder builder = Endpoint.of(application)
.targetApplication(endpoint,
cluster,
deployments)
.routingMethod(routingMethod)
+ .legacy(generatedEndpointsAvailable)
.on(Port.fromRoutingMethod(routingMethod));
List<Endpoint> endpoints = new ArrayList<>();
endpoints.add(builder.in(controller.system()));
for (var ge : generatedEndpoints) {
- endpoints.add(builder.generatedFrom(ge).authMethod(ge.authMethod()).in(controller.system()));
+ endpoints.add(builder.generatedFrom(ge).legacy(false).authMethod(ge.authMethod()).in(controller.system()));
}
return EndpointList.copyOf(endpoints);
}
@@ -361,7 +400,24 @@ public class RoutingController {
}
/** Returns certificate DNS names (CN and SAN values) for given deployment */
- public List<String> certificateDnsNames(DeploymentId deployment, DeploymentSpec deploymentSpec) {
+ public List<String> certificateDnsNames(DeploymentId deployment, DeploymentSpec deploymentSpec, String generatedId, boolean legacy) {
+ List<String> endpointDnsNames = new ArrayList<>();
+ if (legacy) {
+ endpointDnsNames.addAll(legacyCertificateDnsNames(deployment, deploymentSpec));
+ }
+ for (Scope scope : List.of(Scope.zone, Scope.global, Scope.application)) {
+ endpointDnsNames.add(Endpoint.of(deployment.applicationId())
+ .wildcardGenerated(generatedId, scope)
+ .routingMethod(RoutingMethod.exclusive)
+ .on(Port.tls())
+ .certificateName()
+ .in(controller.system())
+ .dnsName());
+ }
+ return Collections.unmodifiableList(endpointDnsNames);
+ }
+
+ private List<String> legacyCertificateDnsNames(DeploymentId deployment, DeploymentSpec deploymentSpec) {
List<String> endpointDnsNames = new ArrayList<>();
// We add first an endpoint name based on a hash of the application ID,
@@ -428,10 +484,7 @@ public class RoutingController {
}
private EndpointList filterEndpoints(ApplicationId instance, EndpointList endpoints) {
- if (generatedEndpointsEnabled(instance) && !legacyEndpointsEnabled(instance)) {
- return endpoints.generated();
- }
- return endpoints;
+ return endpointConfig(instance) == EndpointConfig.generated ? endpoints.generated() : endpoints;
}
private void registerRotationEndpointsInDns(PreparedEndpoints prepared) {
@@ -471,19 +524,22 @@ public class RoutingController {
}
}
- /** Generate endpoints for all authentication methods, using given application part */
- private List<GeneratedEndpoint> generateEndpoints(String applicationPart, boolean token, Optional<EndpointId> endpoint) {
- return Arrays.stream(AuthMethod.values())
- .filter(method -> switch (method) {
- case token -> token;
- case mtls -> true;
- case none -> false;
- })
- .map(method -> new GeneratedEndpoint(GeneratedEndpoint.createPart(controller.random(true)),
- applicationPart,
- method,
- endpoint))
- .toList();
+ /** Returns generated endpoints. A new endpoint is generated if no matching endpoint already exists */
+ private List<GeneratedEndpoint> generateEndpoints(AuthMethod authMethod, EndpointCertificate certificate,
+ Optional<EndpointId> declaredEndpoint,
+ List<GeneratedEndpoint> current) {
+ if (current.stream().anyMatch(e -> e.authMethod() == authMethod && e.endpoint().equals(declaredEndpoint))) {
+ return current;
+ }
+ Optional<String> applicationPart = certificate.generatedId();
+ if (applicationPart.isPresent()) {
+ current = new ArrayList<>(current);
+ current.add(new GeneratedEndpoint(GeneratedEndpoint.createPart(controller.random(true)),
+ applicationPart.get(),
+ authMethod,
+ declaredEndpoint));
+ }
+ return current;
}
/** Generate the cluster part of a {@link GeneratedEndpoint} for use in a {@link Endpoint.Scope#weighted} endpoint */
@@ -550,20 +606,6 @@ public class RoutingController {
return Collections.unmodifiableList(routingMethods);
}
- public boolean generatedEndpointsEnabled(ApplicationId instance) {
- return generatedEndpoints.with(FetchVector.Dimension.INSTANCE_ID, instance.serializedForm())
- .with(FetchVector.Dimension.TENANT_ID, instance.tenant().value())
- .with(FetchVector.Dimension.APPLICATION_ID, TenantAndApplicationId.from(instance).serialized())
- .value();
- }
-
- public boolean legacyEndpointsEnabled(ApplicationId instance) {
- return legacyEndpoints.with(FetchVector.Dimension.INSTANCE_ID, instance.serializedForm())
- .with(FetchVector.Dimension.TENANT_ID, instance.tenant().value())
- .with(FetchVector.Dimension.APPLICATION_ID, TenantAndApplicationId.from(instance).serialized())
- .value();
- }
-
private static void requireGeneratedEndpoints(GeneratedEndpointList generatedEndpoints, boolean declared) {
if (generatedEndpoints.asList().stream().anyMatch(ge -> ge.declared() != declared)) {
throw new IllegalStateException("All generated endpoints require declared=" + declared +
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
index d11540b28dd..55269e2612f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/TenantController.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationActivity.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationActivity.java
index 09acb12d660..d89f786714d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationActivity.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationActivity.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import java.time.Instant;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
index 2d3c060c7b5..32aae5c041c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.collections.AbstractFilteringList;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java
index eeeb822ecf5..c2949e395e9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/AssignedRotation.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.config.provision.ClusterSpec;
@@ -23,16 +23,6 @@ public record AssignedRotation(ClusterSpec.Id clusterId, EndpointId endpointId,
this.regions = Set.copyOf(Objects.requireNonNull(regions));
}
- @Override
- public String toString() {
- return "AssignedRotation{" +
- "clusterId=" + clusterId +
- ", endpointId='" + endpointId + '\'' +
- ", rotationId=" + rotationId +
- ", regions=" + regions +
- '}';
- }
-
private static <T> T requireNonEmpty(T object, String value, String field) {
Objects.requireNonNull(object);
Objects.requireNonNull(value);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
index 5ebb3d53529..b41b02011b4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
index 6d4fddfbc0a..de26ca73cd8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Deployment.java
@@ -1,13 +1,17 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import java.time.Instant;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.OptionalDouble;
/**
@@ -27,9 +31,11 @@ public class Deployment {
private final DeploymentActivity activity;
private final QuotaUsage quota;
private final OptionalDouble cost;
+ private final Map<TokenId, Instant> dataPlaneTokens;
public Deployment(ZoneId zone, CloudAccount cloudAccount, RevisionId revision, Version version, Instant deployTime,
- DeploymentMetrics metrics, DeploymentActivity activity, QuotaUsage quota, OptionalDouble cost) {
+ DeploymentMetrics metrics, DeploymentActivity activity, QuotaUsage quota, OptionalDouble cost,
+ Map<TokenId, Instant> dataPlaneTokens) {
this.zone = Objects.requireNonNull(zone, "zone cannot be null");
this.cloudAccount = Objects.requireNonNull(cloudAccount, "cloudAccount cannot be null");
this.revision = Objects.requireNonNull(revision, "revision cannot be null");
@@ -39,6 +45,7 @@ public class Deployment {
this.activity = Objects.requireNonNull(activity, "activity cannot be null");
this.quota = Objects.requireNonNull(quota, "usage cannot be null");
this.cost = Objects.requireNonNull(cost, "cost cannot be null");
+ this.dataPlaneTokens = Map.copyOf(dataPlaneTokens);
}
/** Returns the zone this was deployed to */
@@ -70,23 +77,26 @@ public class Deployment {
/** Returns cost, in dollars per hour, for this */
public OptionalDouble cost() { return cost; }
+ /** Returns the data plane token IDs referenced by this deployment, and the last update time of this token at the time of deployment. */
+ public Map<TokenId, Instant> dataPlaneTokens() { return dataPlaneTokens; }
+
public Deployment recordActivityAt(Instant instant) {
return new Deployment(zone, cloudAccount, revision, version, deployTime, metrics,
- activity.recordAt(instant, metrics), quota, cost);
+ activity.recordAt(instant, metrics), quota, cost, dataPlaneTokens);
}
public Deployment withMetrics(DeploymentMetrics metrics) {
- return new Deployment(zone, cloudAccount, revision, version, deployTime, metrics, activity, quota, cost);
+ return new Deployment(zone, cloudAccount, revision, version, deployTime, metrics, activity, quota, cost, dataPlaneTokens);
}
public Deployment withCost(double cost) {
if (this.cost.isPresent() && Double.compare(this.cost.getAsDouble(), cost) == 0) return this;
- return new Deployment(zone, cloudAccount, revision, version, deployTime, metrics, activity, quota, OptionalDouble.of(cost));
+ return new Deployment(zone, cloudAccount, revision, version, deployTime, metrics, activity, quota, OptionalDouble.of(cost), dataPlaneTokens);
}
public Deployment withoutCost() {
if (cost.isEmpty()) return this;
- return new Deployment(zone, cloudAccount, revision, version, deployTime, metrics, activity, quota, OptionalDouble.empty());
+ return new Deployment(zone, cloudAccount, revision, version, deployTime, metrics, activity, quota, OptionalDouble.empty(), dataPlaneTokens);
}
@Override
@@ -94,20 +104,21 @@ public class Deployment {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Deployment that = (Deployment) o;
- return zone.equals(that.zone) &&
- cloudAccount.equals(that.cloudAccount) &&
- revision.equals(that.revision) &&
- version.equals(that.version) &&
- deployTime.equals(that.deployTime) &&
- metrics.equals(that.metrics) &&
- activity.equals(that.activity) &&
- quota.equals(that.quota) &&
- cost.equals(that.cost);
+ return Objects.equals(zone, that.zone)
+ && Objects.equals(cloudAccount, that.cloudAccount)
+ && Objects.equals(revision, that.revision)
+ && Objects.equals(version, that.version)
+ && Objects.equals(deployTime, that.deployTime)
+ && Objects.equals(metrics, that.metrics)
+ && Objects.equals(activity, that.activity)
+ && Objects.equals(quota, that.quota)
+ && Objects.equals(cost, that.cost)
+ && Objects.equals(dataPlaneTokens, that.dataPlaneTokens);
}
@Override
public int hashCode() {
- return Objects.hash(zone, cloudAccount, revision, version, deployTime, metrics, activity, quota, cost);
+ return Objects.hash(zone, cloudAccount, revision, version, deployTime, metrics, activity, quota, cost, dataPlaneTokens);
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentActivity.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentActivity.java
index fc2ac94ffed..d671f57f90f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentActivity.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentActivity.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import java.time.Instant;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentMetrics.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentMetrics.java
index 1e0946d07be..ce652521a9f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentMetrics.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentMetrics.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import java.time.Instant;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java
index cde971b490a..4132b560fae 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculator.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.config.application.api.DeploymentSpec;
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 5c6611f80c3..39e1c89c202 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.config.provision.ApplicationId;
@@ -65,7 +65,7 @@ public class Endpoint {
Objects.requireNonNull(generated, "generated must be non-null");
this.id = requireEndpointId(id, scope, certificateName);
this.cluster = requireCluster(cluster, certificateName);
- this.instance = requireInstance(instanceName, scope);
+ this.instance = requireInstance(instanceName, scope, certificateName, generated.isPresent());
this.url = url;
this.targets = List.copyOf(requireTargets(targets, application, instanceName, scope, certificateName));
this.scope = requireScope(scope, routingMethod);
@@ -122,7 +122,7 @@ public class Endpoint {
return scope;
}
- /** Returns whether this is considered a legacy DNS name that is due for removal */
+ /** Returns whether this is considered a legacy DNS name intended to be removed at some point */
public boolean legacy() {
return legacy;
}
@@ -259,7 +259,7 @@ public class Endpoint {
}
/** Returns the DNS suffix used for endpoints in given system */
- public static String dnsSuffix(SystemName system) {
+ private static String dnsSuffix(SystemName system) {
return switch (system) {
case cd -> CD_OATH_DNS_SUFFIX;
case main -> MAIN_OATH_DNS_SUFFIX;
@@ -316,7 +316,10 @@ public class Endpoint {
return endpointId;
}
- private static Optional<InstanceName> requireInstance(Optional<InstanceName> instanceName, Scope scope) {
+ private static Optional<InstanceName> requireInstance(Optional<InstanceName> instanceName, Scope scope, boolean certificateName, boolean generated) {
+ if (generated && certificateName) {
+ return instanceName;
+ }
if (scope == Scope.application) {
if (instanceName.isPresent()) throw new IllegalArgumentException("Instance cannot be set for scope " + scope);
} else {
@@ -331,7 +334,8 @@ public class Endpoint {
}
private static List<Target> requireTargets(List<Target> targets, TenantAndApplicationId application, Optional<InstanceName> instanceName, Scope scope, boolean certificateName) {
- if (!certificateName && targets.isEmpty()) throw new IllegalArgumentException("At least one target must be given for " + scope + " endpoints");
+ if (certificateName && targets.isEmpty()) return List.of();
+ if (targets.isEmpty()) throw new IllegalArgumentException("At least one target must be given for " + scope + " endpoints");
if (scope == Scope.zone && targets.size() != 1) throw new IllegalArgumentException("Exactly one target must be given for " + scope + " endpoints");
for (var target : targets) {
if (scope == Scope.application) {
@@ -524,6 +528,18 @@ public class Endpoint {
return target(ClusterSpec.Id.from("*"), deployment);
}
+ /** Sets the generated wildcard target for this */
+ public EndpointBuilder wildcardGenerated(String applicationPart, Scope scope) {
+ this.cluster = ClusterSpec.Id.from("*");
+ if (scope.multiDeployment()) {
+ this.endpointId = EndpointId.of("*");
+ }
+ this.targets = List.of();
+ this.scope = requireUnset(scope);
+ this.generated = Optional.of(new GeneratedEndpoint("*", applicationPart, AuthMethod.mtls, Optional.ofNullable(endpointId)));
+ return this;
+ }
+
/** Sets the application target with given ID, cluster, deployments and their weights */
public EndpointBuilder targetApplication(EndpointId endpointId, ClusterSpec.Id cluster, Map<DeploymentId, Integer> deployments) {
this.endpointId = endpointId;
@@ -557,9 +573,9 @@ public class Endpoint {
return this;
}
- /** Marks this as a legacy endpoint */
- public EndpointBuilder legacy() {
- this.legacy = true;
+ /** Set whether this is a legacy endpoint */
+ public EndpointBuilder legacy(boolean legacy) {
+ this.legacy = legacy;
return this;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java
index b7ca8587efa..ef1f43eee69 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointId.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import java.util.Objects;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
index 310a78e45f0..07fd6d9825d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/EndpointList.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.collections.AbstractFilteringList;
@@ -28,11 +28,6 @@ public class EndpointList extends AbstractFilteringList<Endpoint, EndpointList>
}
}
- /** Returns the primary (non-legacy) endpoint, if any */
- public Optional<Endpoint> primary() {
- return not().legacy().asList().stream().findFirst();
- }
-
/** Returns the subset of endpoints named according to given ID and scope */
public EndpointList named(EndpointId id, Endpoint.Scope scope) {
return matching(endpoint -> endpoint.scope() == scope && // ID is only unique within a scope
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java
index 28f9963f24c..5f75d6105b5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/GeneratedEndpoint.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import ai.vespa.validation.Validation;
@@ -16,7 +17,8 @@ import java.util.regex.Pattern;
*/
public record GeneratedEndpoint(String clusterPart, String applicationPart, AuthMethod authMethod, Optional<EndpointId> endpoint) {
- private static final Pattern PART_PATTERN = Pattern.compile("^[a-f][a-f0-9]{7}$");
+ private static final Pattern CLUSTER_PART_PATTERN = Pattern.compile("^([a-f][a-f0-9]{7}|\\*)$");
+ private static final Pattern APPLICATION_PART_PATTERN = Pattern.compile("^[a-f][a-f0-9]{7}$");
public GeneratedEndpoint {
Objects.requireNonNull(clusterPart);
@@ -24,8 +26,8 @@ public record GeneratedEndpoint(String clusterPart, String applicationPart, Auth
Objects.requireNonNull(authMethod);
Objects.requireNonNull(endpoint);
- Validation.requireMatch(clusterPart, "Cluster part", PART_PATTERN);
- Validation.requireMatch(applicationPart, "Application part", PART_PATTERN);
+ Validation.requireMatch(clusterPart, "Cluster part", CLUSTER_PART_PATTERN);
+ Validation.requireMatch(applicationPart, "Application part", APPLICATION_PART_PATTERN);
}
/** Returns whether this was generated for an endpoint declared in {@link com.yahoo.config.application.api.DeploymentSpec} */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
index b94779994e4..939b3df9502 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.collections.AbstractFilteringList;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/MailVerifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/MailVerifier.java
index afb0b61c23a..9ff3206ee06 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/MailVerifier.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/MailVerifier.java
@@ -1,27 +1,26 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.hosted.controller.LockedTenant;
import com.yahoo.vespa.hosted.controller.TenantController;
+import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Mail;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer;
+import com.yahoo.vespa.hosted.controller.notification.MailTemplating;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.PendingMailVerification;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.tenant.TenantContacts;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
-import com.yahoo.vespa.hosted.controller.tenant.PendingMailVerification;
-import java.net.URI;
import java.time.Clock;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
-import static com.yahoo.yolean.Exceptions.uncheck;
-
/**
* @author olaa
@@ -34,14 +33,14 @@ public class MailVerifier {
private final Mailer mailer;
private final CuratorDb curatorDb;
private final Clock clock;
- private final URI dashboardUri;
+ private final MailTemplating mailTemplating;
- public MailVerifier(URI dashboardUri, TenantController tenantController, Mailer mailer, CuratorDb curatorDb, Clock clock) {
+ public MailVerifier(ConsoleUrls consoleUrls, TenantController tenantController, Mailer mailer, CuratorDb curatorDb, Clock clock) {
this.tenantController = tenantController;
this.mailer = mailer;
this.curatorDb = curatorDb;
this.clock = clock;
- this.dashboardUri = dashboardUri;
+ this.mailTemplating = new MailTemplating(consoleUrls);
}
public PendingMailVerification sendMailVerification(TenantName tenantName, String email, PendingMailVerification.MailType mailType) {
@@ -86,6 +85,7 @@ public class MailVerifier {
case NOTIFICATIONS -> withTenantContacts(oldTenantInfo, pendingMailVerification);
case TENANT_CONTACT -> oldTenantInfo.withContact(oldTenantInfo.contact()
.withEmail(oldTenantInfo.contact().email().withVerification(true)));
+ case BILLING -> withVerifiedBillingMail(oldTenantInfo);
};
tenantController.lockOrThrow(tenant.name(), LockedTenant.Cloud.class, lockedTenant -> {
@@ -111,6 +111,13 @@ public class MailVerifier {
return oldInfo.withContacts(new TenantContacts(newContacts));
}
+ private TenantInfo withVerifiedBillingMail(TenantInfo oldInfo) {
+ var verifiedMail = oldInfo.billingContact().contact().email().withVerification(true);
+ var billingContact = oldInfo.billingContact()
+ .withContact(oldInfo.billingContact().contact().withEmail(verifiedMail));
+ return oldInfo.withBilling(billingContact);
+ }
+
private void writePendingVerification(PendingMailVerification pendingMailVerification) {
try (var lock = curatorDb.lockPendingMailVerification(pendingMailVerification.getVerificationCode())) {
curatorDb.writePendingMailVerification(pendingMailVerification);
@@ -125,12 +132,7 @@ public class MailVerifier {
}
private Mail mailOf(PendingMailVerification pendingMailVerification) {
- var classLoader = this.getClass().getClassLoader();
- var template = uncheck(() -> classLoader.getResourceAsStream("mail/mail-verification.tmpl").readAllBytes());
- var message = new String(template)
- .replaceAll("%\\{consoleUrl}", dashboardUri.getHost())
- .replaceAll("%\\{email}", pendingMailVerification.getMailAddress())
- .replaceAll("%\\{code}", pendingMailVerification.getVerificationCode());
+ var message = mailTemplating.generateMailVerificationHtml(pendingMailVerification);
return new Mail(List.of(pendingMailVerification.getMailAddress()), "Please verify your email", "", message);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/QuotaUsage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/QuotaUsage.java
index 2e0a2d48b78..f5642f44485 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/QuotaUsage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/QuotaUsage.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import java.util.Objects;
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 9d909cb5ebf..d3ab2216539 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/TenantAndApplicationId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/TenantAndApplicationId.java
index 695c8fb6764..9c9ec35fa80 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/TenantAndApplicationId.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/TenantAndApplicationId.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.config.provision.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/package-info.java
index 569ea0bfb1f..6a685281dbb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* Core application model
*
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 46ba22af512..27e45aa1e7d 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.google.common.hash.Hasher;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiff.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiff.java
index 4e4babfea5e..bd08def6cec 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiff.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiff.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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 java.io.BufferedReader;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageStream.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageStream.java
index 7c44a3d0f5c..e13dd2acbdb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageStream.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageStream.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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 java.io.ByteArrayOutputStream;
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 0c05d710763..5412fdf03a3 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,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java
index 33f20327d92..da08ce108e3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXml.java
@@ -1,6 +1,9 @@
+// Copyright Vespa.ai. 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.text.XML;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
+import com.yahoo.vespa.hosted.controller.application.pkg.BasicServicesXml.Container.AuthMethod;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -37,22 +40,30 @@ public record BasicServicesXml(List<Container> containers) {
for (var childNode : XML.getChildren(root)) {
if (childNode.getTagName().equals(CONTAINER_TAG)) {
String id = childNode.getAttribute("id");
- if (id.isEmpty()) throw new IllegalArgumentException(CONTAINER_TAG + " tag requires 'id' attribute");
- List<Container.AuthMethod> methods = parseAuthMethods(childNode);
- containers.add(new Container(id, methods));
+ if (id.isEmpty()) {
+ id = CONTAINER_TAG; // ID defaults to tag name when unset. See ConfigModelBuilder::getIdString
+ }
+ List<Container.AuthMethod> methods = new ArrayList<>();
+ List<TokenId> tokens = new ArrayList<>();
+ parseAuthMethods(childNode, methods, tokens);
+ containers.add(new Container(id, methods, tokens));
}
}
return new BasicServicesXml(containers);
}
- private static List<BasicServicesXml.Container.AuthMethod> parseAuthMethods(Element containerNode) {
- List<BasicServicesXml.Container.AuthMethod> methods = new ArrayList<>();
+ private static void parseAuthMethods(Element containerNode, List<AuthMethod> methods, List<TokenId> tokens) {
for (var node : XML.getChildren(containerNode)) {
if (node.getTagName().equals(CLIENTS_TAG)) {
for (var clientNode : XML.getChildren(node)) {
if (clientNode.getTagName().equals(CLIENT_TAG)) {
- boolean tokenEnabled = XML.getChildren(clientNode).stream()
- .anyMatch(n -> n.getTagName().equals(TOKEN_TAG));
+ boolean tokenEnabled = false;
+ for (var child : XML.getChildren(clientNode)) {
+ if (TOKEN_TAG.equals(child.getTagName())) {
+ tokenEnabled = true;
+ tokens.add(TokenId.of(child.getAttribute("id")));
+ }
+ }
methods.add(tokenEnabled ? Container.AuthMethod.token : Container.AuthMethod.mtls);
}
}
@@ -61,7 +72,6 @@ public record BasicServicesXml(List<Container> containers) {
if (methods.isEmpty()) {
methods.add(Container.AuthMethod.mtls);
}
- return methods;
}
/**
@@ -70,15 +80,16 @@ public record BasicServicesXml(List<Container> containers) {
* @param id ID of container
* @param authMethods Authentication methods supported by this container
*/
- public record Container(String id, List<AuthMethod> authMethods) {
+ public record Container(String id, List<AuthMethod> authMethods, List<TokenId> dataPlaneTokens) {
- public Container(String id, List<AuthMethod> authMethods) {
+ public Container(String id, List<AuthMethod> authMethods, List<TokenId> dataPlaneTokens) {
this.id = Objects.requireNonNull(id);
this.authMethods = Objects.requireNonNull(authMethods).stream()
.distinct()
.sorted()
.toList();
if (authMethods.isEmpty()) throw new IllegalArgumentException("Container must have at least one auth method");
+ this.dataPlaneTokens = dataPlaneTokens.stream().sorted().distinct().toList();
}
public enum AuthMethod {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/TestPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/TestPackage.java
index b4309e3aa00..dc55472bcc2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/TestPackage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/TestPackage.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.config.application.api.DeploymentInstanceSpec;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntries.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntries.java
index e034e9c7a33..90e7acf9e77 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntries.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ZipEntries.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.archive.ArchiveStreamReader;
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 962bd144a21..d2be561a520 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.archive;
import com.yahoo.config.provision.CloudAccount;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/HostedAthenzIdentities.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/HostedAthenzIdentities.java
index ef2f24bbf6d..f68c13ec0d4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/HostedAthenzIdentities.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/HostedAthenzIdentities.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.athenz;
import com.yahoo.vespa.athenz.api.AthenzDomain;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/config/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/config/package-info.java
index 02df7e5c2cf..aceee5f70f4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/config/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/config/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* Required for using {@link com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig} outside controller-server module.
*
@@ -7,4 +7,4 @@
@ExportPackage
package com.yahoo.vespa.hosted.controller.athenz.config;
-import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java
index aa50f9d3a87..e3f53b5606f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzClientFactoryImpl.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.athenz.impl;
import ai.vespa.metrics.ControllerMetrics;
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 65320a25984..ec5fb9af902 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.athenz.impl;
import com.google.common.cache.CacheBuilder;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java
index cbd0f685d80..dfc660442b9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.auditlog;
import java.time.Instant;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLogger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLogger.java
index 13b3d9d170f..ad541599475 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLogger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLogger.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.auditlog;
import com.yahoo.container.jdisc.HttpRequest;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggingRequestHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggingRequestHandler.java
index cb2fca3e411..d73b5ef1d15 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggingRequestHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggingRequestHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.auditlog;
import com.yahoo.container.jdisc.HttpRequest;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/package-info.java
index 711568a952a..daafbf7c767 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* @author mpolden
*/
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/AssignedCertificate.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/AssignedCertificate.java
index 7d3bcf8bdaa..49e2dc5bb0d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/AssignedCertificate.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/AssignedCertificate.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.certificate;
import com.yahoo.config.provision.InstanceName;
@@ -15,10 +15,19 @@ import java.util.Optional;
*/
public record AssignedCertificate(TenantAndApplicationId application,
Optional<InstanceName> instance,
- EndpointCertificate certificate) {
+ EndpointCertificate certificate,
+ boolean shouldValidate) {
public AssignedCertificate with(EndpointCertificate certificate) {
- return new AssignedCertificate(application, instance, certificate);
+ return new AssignedCertificate(application, instance, certificate, shouldValidate);
+ }
+
+ public AssignedCertificate withoutInstance() {
+ return new AssignedCertificate(application, Optional.empty(), certificate, shouldValidate);
+ }
+
+ public AssignedCertificate withShouldValidate(boolean shouldValidate) {
+ return new AssignedCertificate(application, instance, certificate, shouldValidate);
}
}
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 33af58a9790..391c9806f0a 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.certificate;
import com.yahoo.config.application.api.DeploymentInstanceSpec;
@@ -11,18 +11,18 @@ import com.yahoo.transaction.Mutex;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.flags.FetchVector;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.flags.StringFlag;
import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
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.application.GeneratedEndpoint;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
+import com.yahoo.vespa.hosted.controller.routing.EndpointConfig;
import java.time.Clock;
import java.time.Duration;
@@ -30,26 +30,29 @@ import java.time.Instant;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import static com.yahoo.vespa.hosted.controller.certificate.UnassignedCertificate.State;
/**
- * Looks up stored endpoint certificate, provisions new certificates if none is found,
- * and re-provisions the certificate if the deploying-to zone is not covered.
+ * This provisions, assigns and updates the certificate for a given deployment.
*
* See also {@link com.yahoo.vespa.hosted.controller.maintenance.EndpointCertificateMaintainer}, which handles
* refreshes, deletions and triggers deployments.
*
* @author andreer
+ * @author mpolden
*/
public class EndpointCertificates {
- private static final Logger log = Logger.getLogger(EndpointCertificates.class.getName());
+ private static final Logger LOG = Logger.getLogger(EndpointCertificates.class.getName());
+ private static final Duration GCP_CERTIFICATE_EXPIRY_TIME = Duration.ofDays(100); // 100 days, 10 more than notAfter time
private final Controller controller;
private final CuratorDb curator;
@@ -58,152 +61,216 @@ public class EndpointCertificates {
private final EndpointCertificateValidator certificateValidator;
private final BooleanFlag useAlternateCertProvider;
private final StringFlag endpointCertificateAlgo;
- private final BooleanFlag assignLegacyNames;
- private final static Duration GCP_CERTIFICATE_EXPIRY_TIME = Duration.ofDays(100); // 100 days, 10 more than notAfter time
public EndpointCertificates(Controller controller, EndpointCertificateProvider certificateProvider,
EndpointCertificateValidator certificateValidator) {
this.controller = controller;
this.useAlternateCertProvider = PermanentFlags.USE_ALTERNATIVE_ENDPOINT_CERTIFICATE_PROVIDER.bindTo(controller.flagSource());
this.endpointCertificateAlgo = PermanentFlags.ENDPOINT_CERTIFICATE_ALGORITHM.bindTo(controller.flagSource());
- this.assignLegacyNames = Flags.LEGACY_ENDPOINTS.bindTo(controller.flagSource());
this.curator = controller.curator();
this.clock = controller.clock();
this.certificateProvider = certificateProvider;
this.certificateValidator = certificateValidator;
}
- /** Returns a suitable certificate for endpoints of given instance and zone */
- public Optional<EndpointCertificate> get(Instance instance, ZoneId zone, DeploymentSpec deploymentSpec) {
+ /** Returns a suitable certificate for endpoints of given deployment */
+ public EndpointCertificate get(DeploymentId deployment, DeploymentSpec deploymentSpec, Mutex applicationLock) {
+ Objects.requireNonNull(applicationLock);
Instant start = clock.instant();
- Optional<EndpointCertificate> cert = getOrProvision(instance, zone, deploymentSpec);
+ EndpointConfig config = controller.routing().endpointConfig(deployment.applicationId());
+ EndpointCertificate certificate = assignTo(deployment, deploymentSpec, config);
Duration duration = Duration.between(start, clock.instant());
- if (duration.toSeconds() > 30)
- log.log(Level.INFO, Text.format("Getting endpoint certificate for %s took %d seconds!", instance.id().serializedForm(), duration.toSeconds()));
-
- if (controller.zoneRegistry().zones().all().in(CloudName.GCP).ids().contains(zone)) { // Until CKMS is available from GCP
- if (cert.isPresent()) {
- // Validate before copying cert to GCP. This will ensure we don't bug out on the first deployment, but will take more time
- certificateValidator.validate(cert.get(), instance.id().serializedForm(), zone, controller.routing().certificateDnsNames(new DeploymentId(instance.id(), zone), deploymentSpec));
- GcpSecretStore gcpSecretStore = controller.serviceRegistry().gcpSecretStore();
- String mangledCertName = "endpointCert_" + cert.get().certName().replace('.', '_') + "-v" + cert.get().version(); // Google cloud does not accept dots in secrets, but they accept underscores
- String mangledKeyName = "endpointCert_" + cert.get().keyName().replace('.', '_') + "-v" + cert.get().version(); // Google cloud does not accept dots in secrets, but they accept underscores
- if (gcpSecretStore.getLatestSecretVersion(mangledCertName) == null) {
- gcpSecretStore.setSecret(mangledCertName,
- Optional.of(GCP_CERTIFICATE_EXPIRY_TIME),
- "endpoint-cert-accessor");
- gcpSecretStore.addSecretVersion(mangledCertName,
- controller.secretStore().getSecret(cert.get().certName(), cert.get().version()));
- }
- if (gcpSecretStore.getLatestSecretVersion(mangledKeyName) == null) {
- gcpSecretStore.setSecret(mangledKeyName,
- Optional.of(GCP_CERTIFICATE_EXPIRY_TIME),
- "endpoint-cert-accessor");
- gcpSecretStore.addSecretVersion(mangledKeyName,
- controller.secretStore().getSecret(cert.get().keyName(), cert.get().version()));
- }
-
- return Optional.of(cert.get().withVersion(1).withKeyName(mangledKeyName).withCertName(mangledCertName));
- }
+ if (duration.toSeconds() > 30) {
+ LOG.log(Level.INFO, Text.format("Getting endpoint certificate for %s took %d seconds!", deployment.applicationId().serializedForm(), duration.toSeconds()));
}
+ if (isGcp(deployment)) {
+ // This is needed until CKMS is available from GCP
+ return validateGcpCertificate(deployment, deploymentSpec, certificate, config);
+ }
+ return certificate;
+ }
- return cert;
+ private boolean isGcp(DeploymentId deployment) {
+ return controller.zoneRegistry().zones().all().in(CloudName.GCP).ids().contains(deployment.zoneId());
}
- private EndpointCertificate assignFromPool(Instance instance, ZoneId zone) {
- // For deployments to manually deployed environments: use per instance certificate
- // For all other environments (apply in order):
- // * Use per instance certificate if it exists and is assigned a randomized id
- // * Use per application certificate if it exits and is assigned a randomized id
- // * Assign from pool
-
- Optional<AssignedCertificate> perInstanceAssignedCertificate = curator.readAssignedCertificate(TenantAndApplicationId.from(instance.id()), Optional.of(instance.name()));
- if (perInstanceAssignedCertificate.isPresent() && perInstanceAssignedCertificate.get().certificate().randomizedId().isPresent()) {
- return updateLastRequested(perInstanceAssignedCertificate.get()).certificate();
- } else if (! zone.environment().isManuallyDeployed()){
- TenantAndApplicationId application = TenantAndApplicationId.from(instance.id());
- Optional<AssignedCertificate> perApplicationAssignedCertificate = curator.readAssignedCertificate(TenantAndApplicationId.from(instance.id()), Optional.empty());
- if (perApplicationAssignedCertificate.isPresent() && perApplicationAssignedCertificate.get().certificate().randomizedId().isPresent()) {
- return updateLastRequested(perApplicationAssignedCertificate.get()).certificate();
- }
+ private EndpointCertificate validateGcpCertificate(DeploymentId deployment, DeploymentSpec deploymentSpec, EndpointCertificate certificate, EndpointConfig config) {
+ // Validate before copying cert to GCP. This will ensure we don't bug out on the first deployment, but will take more time
+ List<String> dnsNames = controller.routing().certificateDnsNames(deployment, deploymentSpec, certificate.generatedId().get(), config.supportsLegacy());
+ certificateValidator.validate(certificate, deployment.applicationId().serializedForm(), deployment.zoneId(), dnsNames);
+ GcpSecretStore gcpSecretStore = controller.serviceRegistry().gcpSecretStore();
+ String mangledCertName = "endpointCert_" + certificate.certName().replace('.', '_') + "-v" + certificate.version(); // Google cloud does not accept dots in secrets, but they accept underscores
+ String mangledKeyName = "endpointCert_" + certificate.keyName().replace('.', '_') + "-v" + certificate.version(); // Google cloud does not accept dots in secrets, but they accept underscores
+ if (gcpSecretStore.getLatestSecretVersion(mangledCertName) == null) {
+ gcpSecretStore.setSecret(mangledCertName,
+ Optional.of(GCP_CERTIFICATE_EXPIRY_TIME),
+ "endpoint-cert-accessor");
+ gcpSecretStore.addSecretVersion(mangledCertName,
+ controller.secretStore().getSecret(certificate.certName(), certificate.version()));
}
+ if (gcpSecretStore.getLatestSecretVersion(mangledKeyName) == null) {
+ gcpSecretStore.setSecret(mangledKeyName,
+ Optional.of(GCP_CERTIFICATE_EXPIRY_TIME),
+ "endpoint-cert-accessor");
+ gcpSecretStore.addSecretVersion(mangledKeyName,
+ controller.secretStore().getSecret(certificate.keyName(), certificate.version()));
+ }
+ return certificate.withVersion(1).withKeyName(mangledKeyName).withCertName(mangledCertName);
+ }
- // For new applications which is assigned from pool we follow these rules:
- // Assign certificate per instance only in manually deployed environments. In other environments, we share the
- // certificate because application endpoints can span instances
- Optional<InstanceName> instanceName = zone.environment().isManuallyDeployed() ? Optional.of(instance.name()) : Optional.empty();
- TenantAndApplicationId application = TenantAndApplicationId.from(instance.id());
-
+ private AssignedCertificate assignFromPool(TenantAndApplicationId application, Optional<InstanceName> instanceName, ZoneId zone) {
try (Mutex lock = controller.curator().lockCertificatePool()) {
Optional<UnassignedCertificate> candidate = curator.readUnassignedCertificates().stream()
.filter(pc -> pc.state() == State.ready)
.min(Comparator.comparingLong(pc -> pc.certificate().lastRequested()));
if (candidate.isEmpty()) {
- throw new IllegalArgumentException("No endpoint certificate available in pool, for deployment of " + instance.id() + " in " + zone);
+ throw new IllegalArgumentException("No endpoint certificate available in pool, for deployment of " +
+ application + instanceName.map(i -> "." + i.value()).orElse("")
+ + " in " + zone);
}
try (NestedTransaction transaction = new NestedTransaction()) {
curator.removeUnassignedCertificate(candidate.get(), transaction);
- EndpointCertificate certificate = candidate.get().certificate().withLastRequested(clock.instant().getEpochSecond());
- curator.writeAssignedCertificate(new AssignedCertificate(application, instanceName, certificate),
- transaction);
+ AssignedCertificate assigned = new AssignedCertificate(application, instanceName, candidate.get().certificate(), false);
+ curator.writeAssignedCertificate(assigned, transaction);
transaction.commit();
- return certificate;
+ return assigned;
}
}
}
- AssignedCertificate updateLastRequested(AssignedCertificate assignedCertificate) {
- AssignedCertificate updated = assignedCertificate.with(assignedCertificate.certificate().withLastRequested(clock.instant().getEpochSecond()));
- curator.writeAssignedCertificate(updated);
- return updated;
+ private AssignedCertificate instanceLevelCertificate(DeploymentId deployment, DeploymentSpec deploymentSpec, boolean allowPool) {
+ TenantAndApplicationId application = TenantAndApplicationId.from(deployment.applicationId());
+ Optional<InstanceName> instance = Optional.of(deployment.applicationId().instance());
+ Optional<AssignedCertificate> currentCertificate = curator.readAssignedCertificate(application, instance);
+ final AssignedCertificate assignedCertificate;
+ if (currentCertificate.isEmpty()) {
+ Optional<String> generatedId = Optional.empty();
+ // Re-use the generated ID contained in an existing certificate (matching this application, this instance,
+ // or any other instance present in deployment sec), if any. If this exists we provision a new certificate
+ // containing the same ID
+ if (!deployment.zoneId().environment().isManuallyDeployed()) {
+ generatedId = curator.readAssignedCertificates().stream()
+ .filter(ac -> {
+ boolean matchingInstance = ac.instance().isPresent() &&
+ deploymentSpec.instance(ac.instance().get()).isPresent();
+ return (matchingInstance || ac.instance().isEmpty()) &&
+ ac.application().equals(application);
+ })
+ .map(AssignedCertificate::certificate)
+ .flatMap(ac -> ac.generatedId().stream())
+ .findFirst();
+ }
+ if (allowPool && generatedId.isEmpty()) {
+ assignedCertificate = assignFromPool(application, instance, deployment.zoneId());
+ } else {
+ if (generatedId.isEmpty()) {
+ generatedId = Optional.of(generateId());
+ }
+ EndpointCertificate provisionedCertificate = provision(deployment, Optional.empty(), deploymentSpec, generatedId.get());
+ // We do not validate the certificate if one has never existed before - because we do not want to
+ // wait for it to be available before we deploy. This allows the config server to start
+ // provisioning nodes ASAP, and the risk is small for a new deployment.
+ assignedCertificate = new AssignedCertificate(application, instance, provisionedCertificate, false);
+ }
+ } else {
+ assignedCertificate = currentCertificate.get().withShouldValidate(!allowPool);
+ }
+ return assignedCertificate;
}
- private Optional<EndpointCertificate> getOrProvision(Instance instance, ZoneId zone, DeploymentSpec deploymentSpec) {
- if (controller.routing().generatedEndpointsEnabled(instance.id())) {
- return Optional.of(assignFromPool(instance, zone));
+ private AssignedCertificate applicationLevelCertificate(DeploymentId deployment) {
+ if (deployment.zoneId().environment().isManuallyDeployed()) {
+ throw new IllegalArgumentException(deployment + " is manually deployed and cannot assign an application-level certificate");
}
- Optional<AssignedCertificate> assignedCertificate = curator.readAssignedCertificate(TenantAndApplicationId.from(instance.id()), Optional.of(instance.id().instance()));
- DeploymentId deployment = new DeploymentId(instance.id(), zone);
-
- if (assignedCertificate.isEmpty()) {
- var provisionedCertificate = provisionEndpointCertificate(deployment, Optional.empty(), deploymentSpec);
- // We do not verify the certificate if one has never existed before - because we do not want to
- // wait for it to be available before we deploy. This allows the config server to start
- // provisioning nodes ASAP, and the risk is small for a new deployment.
- curator.writeAssignedCertificate(new AssignedCertificate(TenantAndApplicationId.from(instance.id()), Optional.of(instance.id().instance()), provisionedCertificate));
- return Optional.of(provisionedCertificate);
- } else {
- AssignedCertificate updated = assignedCertificate.get().with(assignedCertificate.get().certificate().withLastRequested(clock.instant().getEpochSecond()));
- curator.writeAssignedCertificate(updated);
+ TenantAndApplicationId application = TenantAndApplicationId.from(deployment.applicationId());
+ Optional<AssignedCertificate> applicationLevelCertificate = curator.readAssignedCertificate(application, Optional.empty());
+ if (applicationLevelCertificate.isEmpty()) {
+ Optional<AssignedCertificate> instanceLevelCertificate = curator.readAssignedCertificate(application, Optional.of(deployment.applicationId().instance()));
+ // Migrate from instance-level certificate
+ if (instanceLevelCertificate.isPresent()) {
+ try (var transaction = new NestedTransaction()) {
+ AssignedCertificate assignedCertificate = instanceLevelCertificate.get().withoutInstance();
+ curator.removeAssignedCertificate(application, Optional.of(deployment.applicationId().instance()), transaction);
+ curator.writeAssignedCertificate(assignedCertificate, transaction);
+ transaction.commit();
+ return assignedCertificate;
+ }
+ } else {
+ return assignFromPool(application, Optional.empty(), deployment.zoneId());
+ }
+ }
+ return applicationLevelCertificate.get();
+ }
+
+ /** Assign a certificate to given deployment. A new certificate is provisioned (possibly from a pool) and reconfigured as necessary */
+ private EndpointCertificate assignTo(DeploymentId deployment, DeploymentSpec deploymentSpec, EndpointConfig config) {
+ // Assign certificate based on endpoint config
+ AssignedCertificate assignedCertificate = switch (config) {
+ case legacy, combined -> instanceLevelCertificate(deployment, deploymentSpec, false);
+ case generated -> deployment.zoneId().environment().isManuallyDeployed()
+ ? instanceLevelCertificate(deployment, deploymentSpec, true)
+ : applicationLevelCertificate(deployment);
+ };
+
+ // Generate ID if not already present in certificate
+ Optional<String> generatedId = assignedCertificate.certificate().generatedId();
+ if (generatedId.isEmpty()) {
+ generatedId = Optional.of(generateId());
+ assignedCertificate = assignedCertificate.with(assignedCertificate.certificate().withGeneratedId(generatedId.get()));
+ }
+
+ // Ensure all wanted names are present in certificate
+ List<String> wantedNames = controller.routing().certificateDnsNames(deployment, deploymentSpec, generatedId.get(), config.supportsLegacy());
+ Set<String> currentNames = Set.copyOf(assignedCertificate.certificate().requestedDnsSans());
+ // TODO(mpolden): Consider requiring exact match for generated as we likely want to remove any legacy names in this case
+ if (!currentNames.containsAll(wantedNames)) {
+ EndpointCertificate updatedCertificate = provision(deployment, Optional.of(assignedCertificate.certificate()), deploymentSpec, generatedId.get());
+ // Validation is unlikely to succeed in this case, as certificate must be available first. Controller will retry
+ assignedCertificate = assignedCertificate.with(updatedCertificate)
+ .withShouldValidate(true);
}
- // Re-provision certificate if it is missing SANs for the zone we are deploying to
- // Skip this validation for now if the cert has a randomized id and should not provision legacy names
- Optional<EndpointCertificate> currentCertificate = assignedCertificate.map(AssignedCertificate::certificate);
- boolean legacyNames = assignLegacyNames.with(FetchVector.Dimension.INSTANCE_ID, instance.id().serializedForm())
- .with(FetchVector.Dimension.APPLICATION_ID, instance.id().toSerializedFormWithoutInstance()).value();
-
- var requiredSansForZone = legacyNames || currentCertificate.get().randomizedId().isEmpty() ?
- controller.routing().certificateDnsNames(deployment, deploymentSpec) :
- List.<String>of();
-
- if (!currentCertificate.get().requestedDnsSans().containsAll(requiredSansForZone)) {
- var reprovisionedCertificate =
- provisionEndpointCertificate(deployment, currentCertificate, deploymentSpec)
- .withRootRequestId(currentCertificate.get().rootRequestId()); // We're required to keep the original request ID
- curator.writeAssignedCertificate(assignedCertificate.get().with(reprovisionedCertificate));
- // Verification is unlikely to succeed in this case, as certificate must be available first - controller will retry
- certificateValidator.validate(reprovisionedCertificate, instance.id().serializedForm(), zone, requiredSansForZone);
- return Optional.of(reprovisionedCertificate);
+ // Require that generated ID is always set, for any kind of certificate
+ if (assignedCertificate.certificate().generatedId().isEmpty()) {
+ throw new IllegalArgumentException("Certificate for " + deployment + " does not contain generated ID: " +
+ assignedCertificate.certificate());
}
- certificateValidator.validate(currentCertificate.get(), instance.id().serializedForm(), zone, requiredSansForZone);
- return currentCertificate;
+ // Update the time we last requested this certificate. This field is used by EndpointCertificateMaintainer to
+ // determine stale certificates
+ assignedCertificate = assignedCertificate.with(assignedCertificate.certificate().withLastRequested(clock.instant().getEpochSecond()));
+ curator.writeAssignedCertificate(assignedCertificate);
+
+ // Validate if we're re-assigned an existing certificate, or if we updated the names of an existing certificate
+ if (assignedCertificate.shouldValidate()) {
+ certificateValidator.validate(assignedCertificate.certificate(), deployment.applicationId().serializedForm(),
+ deployment.zoneId(), wantedNames);
+ }
+
+ return assignedCertificate.certificate();
}
- private EndpointCertificate provisionEndpointCertificate(DeploymentId deployment,
- Optional<EndpointCertificate> currentCert,
- DeploymentSpec deploymentSpec) {
+ private String generateId() {
+ List<String> unassignedIds = curator.readUnassignedCertificates().stream()
+ .map(UnassignedCertificate::id)
+ .toList();
+ List<String> assignedIds = curator.readAssignedCertificates().stream()
+ .map(AssignedCertificate::certificate)
+ .map(EndpointCertificate::generatedId)
+ .flatMap(Optional::stream)
+ .toList();
+ Set<String> allIds = Stream.concat(unassignedIds.stream(), assignedIds.stream()).collect(Collectors.toSet());
+ String id;
+ do {
+ id = GeneratedEndpoint.createPart(controller.random(true));
+ } while (allIds.contains(id));
+ return id;
+ }
+
+ private EndpointCertificate provision(DeploymentId deployment,
+ Optional<EndpointCertificate> current,
+ DeploymentSpec deploymentSpec,
+ String generatedId) {
List<ZoneId> zonesInSystem = controller.zoneRegistry().zones().controllerUpgraded().ids();
Set<ZoneId> requiredZones = new LinkedHashSet<>();
requiredZones.add(deployment.zoneId());
@@ -216,39 +283,36 @@ public class EndpointCertificates {
instanceSpec.get().deploysTo(zone.environment(), zone.region())))
.forEach(requiredZones::add);
}
- /* TODO(andreer/mpolden): To allow a seamless transition of existing deployments to using generated endpoints,
- we need to something like this:
- 1) All current certificates must be re-provisioned to contain the same wildcard names
- as CertificatePoolMaintainer, and a randomized ID
- 2) Generated endpoints must be exposed *before* switching deployment to a
- pre-provisioned certificate
- 3) Tenants must shift their traffic to generated endpoints
- 4) We can switch to the pre-provisioned certificate. This will invalidate
- non-generated endpoints
- */
- Set<String> requiredNames = requiredZones.stream()
+ Set<String> wantedNames = requiredZones.stream()
.flatMap(zone -> controller.routing().certificateDnsNames(new DeploymentId(deployment.applicationId(), zone),
- deploymentSpec)
+ deploymentSpec, generatedId, true)
.stream())
.collect(Collectors.toCollection(LinkedHashSet::new));
- // Preserve any currently present names that are still valid
- List<String> currentNames = currentCert.map(EndpointCertificate::requestedDnsSans)
- .orElseGet(List::of);
- zonesInSystem.stream()
- .map(zone -> controller.routing().certificateDnsNames(new DeploymentId(deployment.applicationId(), zone), deploymentSpec))
- .filter(currentNames::containsAll)
- .forEach(requiredNames::addAll);
+ // Preserve any currently present names that are still valid (i.e. the name points to a zone found in this system)
+ Set<String> currentNames = current.map(EndpointCertificate::requestedDnsSans)
+ .map(Set::copyOf)
+ .orElseGet(Set::of);
+ for (var zone : zonesInSystem) {
+ List<String> wantedNamesZone = controller.routing().certificateDnsNames(new DeploymentId(deployment.applicationId(), zone),
+ deploymentSpec,
+ generatedId,
+ true);
+ if (currentNames.containsAll(wantedNamesZone)) {
+ wantedNames.addAll(wantedNamesZone);
+ }
+ }
- log.log(Level.INFO, String.format("Requesting new endpoint certificate from Cameo for application %s", deployment.applicationId().serializedForm()));
- String algo = this.endpointCertificateAlgo.with(FetchVector.Dimension.INSTANCE_ID, deployment.applicationId().serializedForm()).value();
+ // Request certificate
+ LOG.log(Level.INFO, String.format("Requesting new endpoint certificate for application %s", deployment.applicationId().serializedForm()));
+ String algo = endpointCertificateAlgo.with(FetchVector.Dimension.INSTANCE_ID, deployment.applicationId().serializedForm()).value();
boolean useAlternativeProvider = useAlternateCertProvider.with(FetchVector.Dimension.INSTANCE_ID, deployment.applicationId().serializedForm()).value();
String keyPrefix = deployment.applicationId().toFullString();
- var t0 = Instant.now();
- EndpointCertificate endpointCertificate = certificateProvider.requestCaSignedCertificate(keyPrefix, List.copyOf(requiredNames), currentCert, algo, useAlternativeProvider);
- var t1 = Instant.now();
- log.log(Level.INFO, String.format("Endpoint certificate request for application %s returned after %s", deployment.applicationId().serializedForm(), Duration.between(t0, t1)));
- return endpointCertificate;
+ Instant t0 = controller.clock().instant();
+ EndpointCertificate endpointCertificate = certificateProvider.requestCaSignedCertificate(keyPrefix, List.copyOf(wantedNames), current, algo, useAlternativeProvider);
+ Instant t1 = controller.clock().instant();
+ LOG.log(Level.INFO, String.format("Endpoint certificate request for application %s returned after %s", deployment.applicationId().serializedForm(), Duration.between(t0, t1)));
+ return endpointCertificate.withGeneratedId(generatedId);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/UnassignedCertificate.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/UnassignedCertificate.java
index 3a8580b7eb5..1d1f4938758 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/UnassignedCertificate.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/certificate/UnassignedCertificate.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.certificate;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
@@ -14,13 +15,13 @@ import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCe
public record UnassignedCertificate(EndpointCertificate certificate, UnassignedCertificate.State state) {
public UnassignedCertificate {
- if (certificate.randomizedId().isEmpty()) {
- throw new IllegalArgumentException("randomizedId must be set for a pooled certificate");
+ if (certificate.generatedId().isEmpty()) {
+ throw new IllegalArgumentException("generatedId must be set for a pooled certificate");
}
}
public String id() {
- return certificate.randomizedId().get();
+ return certificate.generatedId().get();
}
public UnassignedCertificate withState(State state) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/concurrent/Once.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/concurrent/Once.java
index dbcd8bf1459..2e717f16d0e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/concurrent/Once.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/concurrent/Once.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.concurrent;
import java.time.Duration;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ConvergenceSummary.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ConvergenceSummary.java
index 6f0b01e5df5..2b1d00ada95 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ConvergenceSummary.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ConvergenceSummary.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import java.util.Objects;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
index 02ecdcaad21..223ba546b3e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.google.common.collect.ImmutableMap;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java
index 4a00a272c75..16bd5bd9bb2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatusList.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.collections.AbstractFilteringList;
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 1b40781fe0f..834efa81d26 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.config.application.api.DeploymentInstanceSpec;
@@ -430,6 +430,7 @@ public class DeploymentTrigger {
private boolean acceptNewRevision(DeploymentStatus status, InstanceName instance, RevisionId revision) {
if (status.application().deploymentSpec().instance(instance).isEmpty()) return false; // Unknown instance.
+ if (status.application().get(instance).map(Instance::change).map(Change::isRevisionPinned).orElse(false)) return false;
if ( ! status.jobs().failingWithBrokenRevisionSince(revision, clock.instant().minus(maxFailingRevisionTime))
.isEmpty()) return false; // Don't deploy a broken revision.
boolean isChangingRevision = status.application().require(instance).change().revision().isPresent();
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 11c47d8f481..9bfa2674754 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import ai.vespa.http.HttpURL;
@@ -86,7 +86,6 @@ import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.running;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.success;
import static com.yahoo.vespa.hosted.controller.deployment.RunStatus.testFailure;
import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.succeeded;
-import static com.yahoo.vespa.hosted.controller.deployment.Step.Status.unfinished;
import static com.yahoo.vespa.hosted.controller.deployment.Step.copyVespaLogs;
import static com.yahoo.vespa.hosted.controller.deployment.Step.deactivateReal;
import static com.yahoo.vespa.hosted.controller.deployment.Step.deactivateTester;
@@ -128,7 +127,7 @@ public class InternalStepRunner implements StepRunner {
public InternalStepRunner(Controller controller) {
this.controller = controller;
this.testConfigSerializer = new TestConfigSerializer(controller.system());
- this.mails = new DeploymentFailureMails(controller.zoneRegistry());
+ this.mails = new DeploymentFailureMails(controller.serviceRegistry().consoleUrls());
this.timeouts = Timeouts.of(controller.system());
}
@@ -186,7 +185,7 @@ public class InternalStepRunner implements StepRunner {
return deploy(() -> controller.applications().deploy(id.job(),
setTheStage,
logger::log,
- account -> getCloudAccountWithOverrideForStaging(id, account)),
+ account -> getAndSetCloudAccountWithOverrideForStaging(id, account)),
controller.jobController().run(id)
.stepInfo(setTheStage ? deployInitialReal : deployReal).get()
.startTime().get(),
@@ -224,7 +223,7 @@ public class InternalStepRunner implements StepRunner {
return account;
}
- private Optional<CloudAccount> getCloudAccountWithOverrideForStaging(RunId id, Optional<CloudAccount> account) {
+ private Optional<CloudAccount> getAndSetCloudAccountWithOverrideForStaging(RunId id, Optional<CloudAccount> account) {
if (id.type().environment() == Environment.staging) {
Instant doom = controller.clock().instant().plusSeconds(60); // Sleeping is bad, but we're already in a sleepy code path: deployment.
while (true) {
@@ -233,10 +232,6 @@ public class InternalStepRunner implements StepRunner {
if (stored.isPresent())
return stored.filter(not(CloudAccount.empty::equals));
- // TODO jonmv: remove with next release
- if (run.stepStatus(deployTester).get() != unfinished)
- return account; // Use original value for runs which started prior to this code change, and resumed after. Extremely unlikely :>
-
long millisToDoom = Duration.between(controller.clock().instant(), doom).toMillis();
if (millisToDoom > 0)
uncheckInterruptedAndRestoreFlag(() -> Thread.sleep(min(millisToDoom, 5000)));
@@ -244,6 +239,7 @@ public class InternalStepRunner implements StepRunner {
throw new CloudAccountNotSetException("Cloud account not yet set; must deploy tests first");
}
}
+ account.ifPresent(cloudAccount -> controller.jobController().locked(id, run -> run.with(cloudAccount)));
return account;
}
@@ -273,7 +269,8 @@ public class InternalStepRunner implements StepRunner {
case CERTIFICATE_NOT_READY -> {
logger.log("No valid CA signed certificate for app available to config server");
if (startTime.plus(timeouts.endpointCertificate()).isBefore(controller.clock().instant())) {
- logger.log(WARNING, "CA signed certificate for app not available to config server within " + timeouts.endpointCertificate());
+ logger.log(WARNING, "CA signed certificate for app not available to config server within " +
+ timeouts.endpointCertificate().toMinutes() + " minutes");
return Optional.of(RunStatus.endpointCertificateTimeout);
}
return result;
@@ -291,8 +288,8 @@ public class InternalStepRunner implements StepRunner {
case LOAD_BALANCER_NOT_READY, PARENT_HOST_NOT_READY -> {
logger.log(e.message()); // Consider splitting these messages in summary and details, on config server.
Instant someTimeAfterStart = startTime.plusSeconds(200);
- Instant inALittleWhile = controller.clock().instant().plusSeconds(60);
- controller.jobController().locked(id, run -> run.sleepingUntil(someTimeAfterStart.isAfter(inALittleWhile) ? someTimeAfterStart : inALittleWhile));
+ if (someTimeAfterStart.isAfter(controller.clock().instant()))
+ controller.jobController().locked(id, run -> run.sleepingUntil(someTimeAfterStart));
return result;
}
case NODE_ALLOCATION_FAILURE -> {
@@ -330,10 +327,10 @@ public class InternalStepRunner implements StepRunner {
case CERT_NOT_AVAILABLE:
// Same as CERTIFICATE_NOT_READY above, only from the controller
logger.log("Retrieving CA signed certificate for the application. " +
- "This may take up to " + timeouts.endpointCertificate() + " on first deployment.");
+ "This may take up to " + timeouts.endpointCertificate().toMinutes() + " minutes on first deployment.");
if (startTime.plus(timeouts.endpointCertificate()).isBefore(controller.clock().instant())) {
logger.log(WARNING, "CA signed certificate for app not available within " +
- timeouts.endpointCertificate() + ": " + Exceptions.toMessageString(e));
+ timeouts.endpointCertificate().toMinutes() + " minutes: " + Exceptions.toMessageString(e));
return Optional.of(RunStatus.endpointCertificateTimeout);
}
return Optional.empty();
@@ -408,7 +405,7 @@ public class InternalStepRunner implements StepRunner {
}
if (summary.converged()) {
controller.jobController().locked(id, lockedRun -> lockedRun.withSummary(null));
- Availability availability = endpointsAvailable(id.application(), id.type().zone(), deployment.get(), logger);
+ Availability availability = endpointsAvailable(id.application(), id.type().zone(), deployment.get(), run.versions().sourceRevision().isEmpty(), logger);
if (availability.status() == Status.available) {
if (controller.routing().policies().processDnsChallenges(new DeploymentId(id.application(), id.type().zone()))) {
logger.log("Installation succeeded!");
@@ -479,7 +476,7 @@ public class InternalStepRunner implements StepRunner {
.toList());
controller.jobController().locked(id, lockedRun -> {
- Instant noNodesDownSince = nodeList.allowedDown().size() == 0 ? lockedRun.noNodesDownSince().orElse(controller.clock().instant()) : null;
+ Instant noNodesDownSince = nodeList.allowedDown().isEmpty() ? lockedRun.noNodesDownSince().orElse(controller.clock().instant()) : null;
return lockedRun.noNodesDownSince(noNodesDownSince).withSummary(summary);
});
@@ -550,7 +547,7 @@ public class InternalStepRunner implements StepRunner {
}
}
- private Availability endpointsAvailable(ApplicationId id, ZoneId zone, Deployment deployment, DualLogger logger) {
+ private Availability endpointsAvailable(ApplicationId id, ZoneId zone, Deployment deployment, boolean initialDeployment, DualLogger logger) {
DeploymentId deploymentId = new DeploymentId(id, zone);
Map<ZoneId, List<Endpoint>> endpoints = controller.routing().readStepRunnerEndpointsOf(Set.of(deploymentId));
logEndpoints(endpoints, logger);
@@ -570,7 +567,8 @@ public class InternalStepRunner implements StepRunner {
policy.canonicalName().filter(__ -> resolveEndpoints),
policy.isPublic(),
deployment.cloudAccount());
- }).toList());
+ }).toList(),
+ initialDeployment);
}
private void logEndpoints(Map<ZoneId, List<Endpoint>> zoneEndpoints, DualLogger logger) {
@@ -725,6 +723,8 @@ public class InternalStepRunner implements StepRunner {
DeploymentSpec spec = controller.applications().requireApplication(TenantAndApplicationId.from(id.application())).deploymentSpec();
boolean requireTests = spec.steps().stream().anyMatch(step -> step.concerns(id.type().environment()));
+ logger.log(WARNING, "No tests were actually run, but this test suite is explicitly declared in 'deployment.xml'. " +
+ "Either add tests, ensure they're correctly configured, or remove the test declaration.");
return Optional.of(requireTests ? testFailure : noTests);
}
case SUCCESS:
@@ -856,7 +856,7 @@ public class InternalStepRunner implements StepRunner {
private void updateConsoleNotification(Run run, boolean isRemoved) {
NotificationSource source = NotificationSource.from(run.id());
- Consumer<String> updater = msg -> controller.notificationsDb().setNotification(source, Notification.Type.deployment, Notification.Level.error, msg);
+ Consumer<String> updater = msg -> controller.notificationsDb().setDeploymentNotification(run.id(), msg);
switch (isRemoved ? success : run.status()) {
case aborted, cancelled: return; // wait and see how the next run goes.
case noTests:
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 1c417e750e3..ae6bcdea00c 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.google.common.collect.ImmutableSortedMap;
@@ -37,7 +37,6 @@ import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackageDiff;
import com.yahoo.vespa.hosted.controller.application.pkg.TestPackage;
import com.yahoo.vespa.hosted.controller.deployment.Run.Reason;
-import com.yahoo.vespa.hosted.controller.notification.Notification;
import com.yahoo.vespa.hosted.controller.notification.Notification.Type;
import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
import com.yahoo.vespa.hosted.controller.persistence.BufferedLogStore;
@@ -625,19 +624,15 @@ public class JobController {
private void validateTests(TenantAndApplicationId id, Submission submission) {
var testSummary = TestPackage.validateTests(submission.applicationPackage().deploymentSpec(), submission.testPackage());
if ( ! testSummary.problems().isEmpty())
- controller.notificationsDb().setNotification(NotificationSource.from(id),
- Type.testPackage,
- Notification.Level.warning,
- testSummary.problems());
-
+ controller.notificationsDb().setTestPackageNotification(id, testSummary.problems());
}
private void validateMajorVersion(TenantAndApplicationId id, Submission submission) {
submission.applicationPackage().deploymentSpec().majorVersion().ifPresent(explicitMajor -> {
if ( ! controller.readVersionStatus().isOnCurrentMajor(new Version(explicitMajor)))
- controller.notificationsDb().setNotification(NotificationSource.from(id), Type.submission, Notification.Level.warning,
- "Vespa " + explicitMajor + " will soon reach end of life, upgrade to Vespa " + (explicitMajor + 1) + " now: " +
- "https://cloud.vespa.ai/en/vespa" + (explicitMajor + 1) + "-release-notes.html"); // ∠( ᐛ 」∠)_
+ controller.notificationsDb().setSubmissionNotification(id,
+ "Vespa " + explicitMajor + " will soon reach end of life, upgrade to [Vespa " + (explicitMajor + 1) + " now](" +
+ "https://cloud.vespa.ai/en/vespa" + (explicitMajor + 1) + "-release-notes.html)"); // ∠( ᐛ 」∠)_
});
}
@@ -771,9 +766,10 @@ public class JobController {
controller.applications().applicationStore().putDev(deploymentId, version.id(), applicationPackage.zippedContent(), diff);
controller.applications().store(application.withRevisions(revisions -> revisions.with(version)));
+ Optional<Deployment> existing = application.get().get(id.instance()).map(instance -> instance.deployments().get(type.zone()));
start(id,
type,
- new Versions(targetPlatform, version.id(), lastRun.map(run -> run.versions().targetPlatform()), lastRun.map(run -> run.versions().targetRevision())),
+ new Versions(targetPlatform, version.id(), existing.map(Deployment::version), existing.map(Deployment::revision)),
false,
dryRun ? JobProfile.developmentDryRun : JobProfile.development,
Reason.empty());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobList.java
index 3318f76df6a..95ea3ff1ffb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobList.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.collections.AbstractFilteringList;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java
index 2924bb83104..6a0f5e44c9e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobMetrics.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import ai.vespa.metrics.ControllerMetrics;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobProfile.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobProfile.java
index f0ec39b8d1c..1f8d2090471 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobProfile.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobProfile.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.api.integration.deployment.JobType;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobStatus.java
index 45bf508f026..3770c9cd694 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobStatus.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.api.integration.deployment.JobId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/LockedStep.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/LockedStep.java
index 8147ccb3180..9f471116e22 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/LockedStep.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/LockedStep.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.transaction.Mutex;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeList.java
index e3f61afec89..a3aefa55f4e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeList.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.collections.AbstractFilteringList;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeWithServices.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeWithServices.java
index d8f88d31759..39addbd3b63 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeWithServices.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeWithServices.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RetriggerEntry.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RetriggerEntry.java
index c8e851fc375..f3bf5b2062d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RetriggerEntry.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RetriggerEntry.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RetriggerEntrySerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RetriggerEntrySerializer.java
index 5aaa187055d..8ed36215cac 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RetriggerEntrySerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RetriggerEntrySerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
index a8dd1c442fe..0d086aa7012 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import ai.vespa.validation.Validation;
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 76ab154688f..2b207e6662b 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.config.provision.CloudAccount;
@@ -276,7 +276,7 @@ public class Run {
/** Whether this is a dry run deployment. */
public boolean isDryRun() { return dryRun; }
- /** Cloud account override to use for this run, if set. This should only be used by staging tests. */
+ /** Cloud account used for deployments in this run. This is set by the first deployment. */
public Optional<CloudAccount> cloudAccount() { return cloudAccount; }
/** The specific reason for triggering this run, if any. This should be empty for jobs triggered bvy deployment orchestration. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunList.java
index 80c6552d3d4..b3846dca2c0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunList.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.collections.AbstractFilteringList;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunLog.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunLog.java
index f388a148ffb..371607ec1c7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunLog.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunLog.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.google.common.collect.ImmutableMap;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java
index 5d625285a7d..7e1806ad9ac 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RunStatus.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
/**
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 b9375eeac18..e975f5874f4 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import java.util.Collection;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/StepInfo.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/StepInfo.java
index 24723f84897..60743e45434 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/StepInfo.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/StepInfo.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import java.time.Instant;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/StepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/StepRunner.java
index b3964c8e422..87df1e925f0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/StepRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/StepRunner.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.api.integration.deployment.RunId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Submission.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Submission.java
index e59b4eb0a07..ce346f5ba74 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Submission.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Submission.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
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 2394f293170..a5a91e7cdd2 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,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
index f752e396c09..9b4fbf06e21 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ZipBuilder.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ZipBuilder.java
index b5307b07603..17d347bda17 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ZipBuilder.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/ZipBuilder.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import java.io.ByteArrayInputStream;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/package-info.java
index b69e8401eb8..4619f5d32c2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
package com.yahoo.vespa.hosted.controller.deployment;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/AbstractNameServiceRequest.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/AbstractNameServiceRequest.java
index 9d21f5b26bd..f4223ad90bc 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/AbstractNameServiceRequest.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/AbstractNameServiceRequest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.vespa.hosted.controller.api.integration.dns.RecordName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecord.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecord.java
index 6f4ee3dfc06..c4c76bc7954 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecord.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecord.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java
index ef7b74a4d4b..d560dbd8db9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/CreateRecords.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.vespa.hosted.controller.api.integration.dns.AliasTarget;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java
index d0d5ddf55ef..40d2667b9ae 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceForwarder.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.transaction.Mutex;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java
index 3996ef671aa..033a019f35f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueue.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceRequest.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceRequest.java
index dd3cca9a4fa..d86c2ce565b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceRequest.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/NameServiceRequest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.vespa.hosted.controller.api.integration.dns.NameService;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java
index 273136ba0a6..0ed835f32bd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/dns/RemoveRecords.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.vespa.hosted.controller.api.integration.dns.AliasTarget;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationMetaDataGarbageCollector.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationMetaDataGarbageCollector.java
index c8c5a1834c7..29e251a9de3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationMetaDataGarbageCollector.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationMetaDataGarbageCollector.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Controller;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
index 1a944cfd5d7..d998413e675 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Application;
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 33a4802360e..b6f73d6e5e3 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
import ai.vespa.metrics.ControllerMetrics;
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 a8d025dbb6a..8913d6e7166 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.CloudAccount;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java
index 1a2fc2f71c2..02cf7a85445 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdater.java
index d06dcd8e0b6..92aaacaa1f0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdater.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Bcp;
@@ -71,6 +71,24 @@ public class BcpGroupUpdater extends ControllerMaintainer {
var patch = new ApplicationPatch();
addTrafficShare(deployment, bcpGroups, patch);
addBcpGroupInfo(deployment.zone().region(), metrics.get(instance.id()), bcpGroups, patch);
+
+ StringBuilder patchAsStringBuilder = new StringBuilder("Patch of instance ").append(instance.id().serializedForm()).append(": ")
+ .append("\n\tcurrentReadShare: ")
+ .append(patch.currentReadShare)
+ .append("\n\tmaxReadShare: ")
+ .append(patch.maxReadShare);
+ for (Map.Entry<String, ApplicationPatch.ClusterPatch> entry : patch.clusters.entrySet()) {
+ String key = entry.getKey();
+ ApplicationPatch.ClusterPatch value = entry.getValue();
+ patchAsStringBuilder.append("\n\tbcpGroupInfo for ").append(key).append(": ")
+ .append("\n\t\tcpuCostPerQuery: ")
+ .append(value.bcpGroupInfo.cpuCostPerQuery)
+ .append("\n\t\tqueryRate: ")
+ .append(value.bcpGroupInfo.queryRate)
+ .append("\n\t\tgrowthRateHeadroom: ")
+ .append(value.bcpGroupInfo.growthRateHeadroom);
+ }
+ log.log(Level.FINER, patchAsStringBuilder.toString());
nodeRepository.patchApplication(deployment.zone(), instance.id(), patch);
}
catch (Exception e) {
@@ -84,7 +102,7 @@ public class BcpGroupUpdater extends ControllerMaintainer {
double successFactorDeviation = asSuccessFactorDeviation(attempts, failures);
if ( successFactorDeviation == -successFactorBaseline )
log.log(Level.WARNING, "Could not update traffic share on any applications", lastException);
- else if ( successFactorDeviation < -0.1 )
+ else if ( successFactorDeviation < 0 )
log.log(Level.FINE, "Could not update traffic share on all applications", lastException);
return successFactorDeviation;
}
@@ -103,7 +121,9 @@ public class BcpGroupUpdater extends ControllerMaintainer {
currentReadShare += groupQps == 0 ? 0 : fraction * deploymentQps / groupQps;
maxReadShare += group.size() == 1
? currentReadShare
- : fraction * ( deploymentQps + group.maxQpsExcluding(deployment.zone().region()) / (group.size() - 1) ) / groupQps;
+ : groupQps != 0
+ ? fraction * (deploymentQps + group.maxQpsExcluding(deployment.zone().region()) / (group.size() - 1)) / groupQps
+ : 0;
}
patch.currentReadShare = currentReadShare;
patch.maxReadShare = maxReadShare;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingDatabaseMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingDatabaseMaintainer.java
index b40078eef51..426abb16549 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingDatabaseMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingDatabaseMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainer.java
index e7ec6675a82..7868c3fe611 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainer.java
@@ -1,12 +1,15 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.LockedTenant;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.BillStatus;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingDatabaseClient;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingReporter;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.InvoiceUpdate;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Plan;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistry;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
@@ -23,18 +26,25 @@ public class BillingReportMaintainer extends ControllerMaintainer {
private final BillingReporter reporter;
private final BillingController billing;
+ private final BillingDatabaseClient databaseClient;
+
private final PlanRegistry plans;
public BillingReportMaintainer(Controller controller, Duration interval) {
- super(controller, interval, null, Set.of(SystemName.PublicCd));
- this.reporter = controller.serviceRegistry().billingReporter();
- this.billing = controller.serviceRegistry().billingController();
- this.plans = controller.serviceRegistry().planRegistry();
+ super(controller, interval, null, Set.of(SystemName.Public, SystemName.PublicCd));
+ reporter = controller.serviceRegistry().billingReporter();
+ billing = controller.serviceRegistry().billingController();
+ databaseClient = controller.serviceRegistry().billingDatabase();
+ plans = controller.serviceRegistry().planRegistry();
}
@Override
protected double maintain() {
maintainTenants();
+
+ var updates = maintainInvoices();
+ log.fine("Updated invoices: " + updates);
+
return 0.0;
}
@@ -53,6 +63,19 @@ public class BillingReportMaintainer extends ControllerMaintainer {
});
}
+ InvoiceUpdate maintainInvoices() {
+ var billsNeedingMaintenance = databaseClient.readBills().stream()
+ .filter(bill -> bill.getExportedId().isPresent())
+ .filter(exported -> exported.status() == BillStatus.OPEN)
+ .toList();
+
+ var updates = new InvoiceUpdate.Counter();
+ for (var bill : billsNeedingMaintenance) {
+ updates.add(reporter.maintainInvoice(bill));
+ }
+ return updates.finish();
+ }
+
private Map<TenantName, CloudTenant> cloudTenants() {
return controller().tenants().asList()
.stream()
@@ -74,4 +97,5 @@ public class BillingReportMaintainer extends ControllerMaintainer {
.flatMap(p -> billing.tenantsWithPlan(tenants, p.id()).stream())
.toList();
}
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java
index ed383175cc3..5e6e495e473 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainer.java
@@ -1,7 +1,10 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
import ai.vespa.metrics.ControllerMetrics;
+import com.yahoo.config.application.api.DeploymentSpec;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.secretstore.SecretNotFoundException;
import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.jdisc.Metric;
@@ -11,9 +14,9 @@ import com.yahoo.vespa.flags.IntFlag;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.flags.StringFlag;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider;
-import com.yahoo.vespa.hosted.controller.application.Endpoint;
import com.yahoo.vespa.hosted.controller.application.GeneratedEndpoint;
import com.yahoo.vespa.hosted.controller.certificate.AssignedCertificate;
import com.yahoo.vespa.hosted.controller.certificate.UnassignedCertificate;
@@ -30,7 +33,7 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
- * Manages pool of ready-to-use randomized endpoint certificates
+ * Manages a pool of ready-to-use endpoint certificates.
*
* @author andreer
*/
@@ -44,7 +47,6 @@ public class CertificatePoolMaintainer extends ControllerMaintainer {
private final Metric metric;
private final Controller controller;
private final IntFlag certPoolSize;
- private final String dnsSuffix;
private final StringFlag endpointCertificateAlgo;
private final BooleanFlag useAlternateCertProvider;
@@ -58,7 +60,6 @@ public class CertificatePoolMaintainer extends ControllerMaintainer {
this.curator = controller.curator();
this.endpointCertificateProvider = controller.serviceRegistry().endpointCertificateProvider();
this.metric = metric;
- this.dnsSuffix = Endpoint.dnsSuffix(controller.system());
}
protected double maintain() {
@@ -72,10 +73,10 @@ public class CertificatePoolMaintainer extends ControllerMaintainer {
metric.set(ControllerMetrics.CERTIFICATE_POOL_AVAILABLE.baseName(), (poolSize > 0 ? ((double)available/poolSize) : 1.0), metric.createContext(Map.of()));
if (certificatePool.size() < poolSize) {
- provisionRandomizedCertificate();
+ provisionCertificate();
}
} catch (Exception e) {
- log.log(Level.SEVERE, "Exception caught while maintaining pool of unused randomized endpoint certs", e);
+ log.log(Level.SEVERE, "Failed to maintain certificate pool", e);
return 1.0;
}
return 0.0;
@@ -90,46 +91,49 @@ public class CertificatePoolMaintainer extends ControllerMaintainer {
OptionalInt maxCertVersion = secretStore.listSecretVersions(cert.certificate().certName()).stream().mapToInt(i -> i).max();
if (maxKeyVersion.isPresent() && maxCertVersion.equals(maxKeyVersion)) {
curator.writeUnassignedCertificate(cert.withState(UnassignedCertificate.State.ready));
- log.log(Level.INFO, "Randomized endpoint cert %s now ready for use".formatted(cert.id()));
+ log.log(Level.INFO, "Readied certificate %s".formatted(cert.id()));
}
} catch (SecretNotFoundException s) {
// Likely because the certificate is very recently provisioned - ignore till next time - should we log?
- log.log(Level.INFO, "Could not yet read secrets for randomized endpoint cert %s - maybe next time ...".formatted(cert.id()));
+ log.log(Level.INFO, "Cannot ready certificate %s yet, will retry in %s".formatted(cert.id(), interval()));
}
}
}
}
- private void provisionRandomizedCertificate() {
+ private void provisionCertificate() {
try (Mutex lock = controller.curator().lockCertificatePool()) {
Set<String> existingNames = controller.curator().readUnassignedCertificates().stream().map(UnassignedCertificate::id).collect(Collectors.toSet());
curator.readAssignedCertificates().stream()
.map(AssignedCertificate::certificate)
- .map(EndpointCertificate::randomizedId)
+ .map(EndpointCertificate::generatedId)
.forEach(id -> id.ifPresent(existingNames::add));
- String id = generateRandomId();
- while (existingNames.contains(id)) id = generateRandomId();
-
- EndpointCertificate f = endpointCertificateProvider.requestCaSignedCertificate(
- "preprovisioned.%s".formatted(id),
- List.of(
- "*.%s.z%s".formatted(id, dnsSuffix),
- "*.%s.g%s".formatted(id, dnsSuffix),
- "*.%s.a%s".formatted(id, dnsSuffix)
- ),
- Optional.empty(),
- endpointCertificateAlgo.value(),
- useAlternateCertProvider.value())
- .withRandomizedId(id);
-
- UnassignedCertificate certificate = new UnassignedCertificate(f, UnassignedCertificate.State.requested);
+ String id = generateId();
+ while (existingNames.contains(id)) id = generateId();
+ List<String> dnsNames = wildcardDnsNames(id);
+ EndpointCertificate cert = endpointCertificateProvider.requestCaSignedCertificate(
+ "preprovisioned.%s".formatted(id),
+ dnsNames,
+ Optional.empty(),
+ endpointCertificateAlgo.value(),
+ useAlternateCertProvider.value()).withGeneratedId(id);
+
+ UnassignedCertificate certificate = new UnassignedCertificate(cert, UnassignedCertificate.State.requested);
curator.writeUnassignedCertificate(certificate);
}
}
- private String generateRandomId() {
+ private List<String> wildcardDnsNames(String id) {
+ DeploymentId defaultDeployment = new DeploymentId(ApplicationId.defaultId(), ZoneId.defaultId());
+ return controller.routing().certificateDnsNames(defaultDeployment, // Not used for non-legacy names
+ DeploymentSpec.empty, // Not used for non-legacy names
+ id,
+ false);
+ }
+
+ private String generateId() {
return GeneratedEndpoint.createPart(controller.random(true));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeManagementAssessor.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeManagementAssessor.java
index 5be20f9a994..51720806371 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeManagementAssessor.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeManagementAssessor.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeRequestMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeRequestMaintainer.java
index 320f15a8b18..9f687249f38 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeRequestMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeRequestMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Environment;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudAccountVerifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudAccountVerifier.java
index f0fc8985bdf..fedfea792f3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudAccountVerifier.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudAccountVerifier.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.SystemName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudDatabaseMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudDatabaseMaintainer.java
index 68fd5c8bafe..73204fb1655 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudDatabaseMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudDatabaseMaintainer.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Controller;
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 18ef47759f4..55428e80493 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
@@ -1,26 +1,42 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.flags.BooleanFlag;
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.ListFlag;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
+import com.yahoo.vespa.hosted.controller.notification.MailTemplating;
+import com.yahoo.vespa.hosted.controller.notification.Notification;
+import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
+import com.yahoo.vespa.hosted.controller.persistence.TrialNotifications;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
+import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
+import static com.yahoo.vespa.hosted.controller.persistence.TrialNotifications.State.EXPIRED;
+import static com.yahoo.vespa.hosted.controller.persistence.TrialNotifications.State.EXPIRES_IMMEDIATELY;
+import static com.yahoo.vespa.hosted.controller.persistence.TrialNotifications.State.EXPIRES_SOON;
+import static com.yahoo.vespa.hosted.controller.persistence.TrialNotifications.State.MID_CHECK_IN;
+import static com.yahoo.vespa.hosted.controller.persistence.TrialNotifications.State.SIGNED_UP;
+import static com.yahoo.vespa.hosted.controller.persistence.TrialNotifications.State.UNKNOWN;
+
/**
* Expires unused tenants from Vespa Cloud.
- * <p>
- * TODO: Should support sending notifications some time before the various expiry events happen.
*
* @author ogronnesby
*/
@@ -28,19 +44,22 @@ public class CloudTrialExpirer extends ControllerMaintainer {
private static final Logger log = Logger.getLogger(CloudTrialExpirer.class.getName());
private static final Duration nonePlanAfter = Duration.ofDays(14);
- private static final Duration tombstoneAfter = Duration.ofDays(183);
+ private static final Duration tombstoneAfter = Duration.ofDays(91);
private final ListFlag<String> extendedTrialTenants;
+ private final BooleanFlag cloudTrialNotificationEnabled;
public CloudTrialExpirer(Controller controller, Duration interval) {
super(controller, interval, null, SystemName.allOf(SystemName::isPublic));
this.extendedTrialTenants = PermanentFlags.EXTENDED_TRIAL_TENANTS.bindTo(controller().flagSource());
+ this.cloudTrialNotificationEnabled = Flags.CLOUD_TRIAL_NOTIFICATIONS.bindTo(controller().flagSource());
}
@Override
protected double maintain() {
var a = tombstoneNonePlanTenants();
var b = moveInactiveTenantsToNonePlan();
- return (a ? 0.5 : 0.0) + (b ? 0.5 : 0.0);
+ var c = notifyTenants();
+ return (a ? 0.0 : -(1D/3)) + (b ? 0.0 : -(1D/3) + (c ? 0.0 : -(1D/3)));
}
private boolean moveInactiveTenantsToNonePlan() {
@@ -76,6 +95,116 @@ public class CloudTrialExpirer extends ControllerMaintainer {
return tombstoneTenants(idleOldPlanTenants);
}
+ private boolean notifyTenants() {
+ try {
+ var currentStatus = controller().curator().readTrialNotifications()
+ .map(TrialNotifications::tenants).orElse(List.of());
+ log.fine(() -> "Current: %s".formatted(currentStatus));
+ var currentStatusByTenant = new HashMap<TenantName, TrialNotifications.Status>();
+ currentStatus.forEach(status -> currentStatusByTenant.put(status.tenant(), status));
+ var updatedStatus = new ArrayList<TrialNotifications.Status>();
+ var now = controller().clock().instant();
+
+ for (var tenant : controller().tenants().asList()) {
+
+ var status = currentStatusByTenant.get(tenant.name());
+ var state = status == null ? UNKNOWN : status.state();
+ var plan = controller().serviceRegistry().billingController().getPlan(tenant.name()).value();
+ var ageInDays = Duration.between(tenant.createdAt(), now).toDays();
+
+ // TODO Replace stubs with proper email content stored in templates.
+
+ var enabled = cloudTrialNotificationEnabled.with(FetchVector.Dimension.TENANT_ID, tenant.name().value()).value();
+ if (!enabled) {
+ if (status != null) updatedStatus.add(status);
+ } else if (!List.of("none", "trial").contains(plan)) {
+ // Ignore tenants that are on a paid plan and skip from inclusion in updated data structure
+ } else if (status == null && "trial".equals(plan) && ageInDays <= 1) {
+ updatedStatus.add(updatedStatus(tenant, now, SIGNED_UP));
+ notifySignup(tenant);
+ } else if ("none".equals(plan) && !List.of(EXPIRED).contains(state)) {
+ updatedStatus.add(updatedStatus(tenant, now, EXPIRED));
+ notifyExpired(tenant);
+ } else if ("trial".equals(plan) && ageInDays >= 13
+ && !List.of(EXPIRES_IMMEDIATELY, EXPIRED).contains(state)) {
+ updatedStatus.add(updatedStatus(tenant, now, EXPIRES_IMMEDIATELY));
+ notifyExpiresImmediately(tenant);
+ } else if ("trial".equals(plan) && ageInDays >= 12
+ && !List.of(EXPIRES_SOON, EXPIRES_IMMEDIATELY, EXPIRED).contains(state)) {
+ updatedStatus.add(updatedStatus(tenant, now, EXPIRES_SOON));
+ notifyExpiresSoon(tenant);
+ } else if ("trial".equals(plan) && ageInDays >= 7
+ && !List.of(MID_CHECK_IN, EXPIRES_SOON, EXPIRES_IMMEDIATELY, EXPIRED).contains(state)) {
+ updatedStatus.add(updatedStatus(tenant, now, MID_CHECK_IN));
+ notifyMidCheckIn(tenant);
+ } else {
+ updatedStatus.add(status);
+ }
+ }
+ log.fine(() -> "Updated: %s".formatted(updatedStatus));
+ controller().curator().writeTrialNotifications(new TrialNotifications(updatedStatus));
+ return true;
+ } catch (Exception e) {
+ log.log(Level.WARNING, "Failed to process trial notifications", e);
+ return false;
+ }
+ }
+
+ private void notifySignup(Tenant tenant) {
+ var consoleMsg = "Welcome to Vespa Cloud trial! [Manage plan](%s)".formatted(billingUrl(tenant));
+ queueNotification(tenant, consoleMsg, "Welcome to Vespa Cloud",
+ "Welcome to Vespa Cloud! We hope you will enjoy your trial. " +
+ "Please reach out to us if you have any questions or feedback.");
+ }
+
+ private void notifyMidCheckIn(Tenant tenant) {
+ var consoleMsg = "You're halfway through the **14 day** trial period. [Manage plan](%s)".formatted(billingUrl(tenant));
+ queueNotification(tenant, consoleMsg, "How is your Vespa Cloud trial going?",
+ "How is your Vespa Cloud trial going? " +
+ "Please reach out to us if you have any questions or feedback.");
+ }
+
+ private void notifyExpiresSoon(Tenant tenant) {
+ var consoleMsg = "Your Vespa Cloud trial expires in **2** days. [Manage plan](%s)".formatted(billingUrl(tenant));
+ queueNotification(tenant, consoleMsg, "Your Vespa Cloud trial expires in 2 days",
+ "Your Vespa Cloud trial expires in 2 days. " +
+ "Please reach out to us if you have any questions or feedback.");
+ }
+
+ private void notifyExpiresImmediately(Tenant tenant) {
+ var consoleMsg = "Your Vespa Cloud trial expires **tomorrow**. [Manage plan](%s)".formatted(billingUrl(tenant));
+ queueNotification(tenant, consoleMsg, "Your Vespa Cloud trial expires tomorrow",
+ "Your Vespa Cloud trial expires tomorrow. " +
+ "Please reach out to us if you have any questions or feedback.");
+ }
+
+ private void notifyExpired(Tenant tenant) {
+ var consoleMsg = "Your Vespa Cloud trial has expired. [Upgrade plan](%s)".formatted(billingUrl(tenant));
+ queueNotification(tenant, consoleMsg, "Your Vespa Cloud trial has expired",
+ "Your Vespa Cloud trial has expired. " +
+ "Please reach out to us if you have any questions or feedback.");
+ }
+
+ private void queueNotification(Tenant tenant, String consoleMsg, String emailSubject, String emailMsg) {
+ var mail = Optional.of(Notification.MailContent.fromTemplate(MailTemplating.Template.DEFAULT_MAIL_CONTENT)
+ .subject(emailSubject)
+ .with("mailMessageTemplate", "cloud-trial-notification")
+ .with("cloudTrialMessage", emailMsg)
+ .with("mailTitle", emailSubject)
+ .with("consoleLink", controller().serviceRegistry().consoleUrls().tenantOverview(tenant.name()))
+ .build());
+ var source = NotificationSource.from(tenant.name());
+ // Remove previous notification to ensure new notification is sent by email
+ controller().notificationsDb().removeNotification(source, Notification.Type.account);
+ controller().notificationsDb().setNotification(
+ source, Notification.Type.account, Notification.Level.info, consoleMsg, List.of(), mail);
+ }
+
+ private String billingUrl(Tenant t) { return controller().serviceRegistry().consoleUrls().tenantBilling(t.name()); }
+
+ private static TrialNotifications.Status updatedStatus(Tenant t, Instant i, TrialNotifications.State s) {
+ return new TrialNotifications.Status(t.name(), s, i);
+ }
private boolean tenantIsCloudTenant(Tenant tenant) {
return tenant.type() == Tenant.Type.cloud;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
index f6da3609fbb..e0db7780fbb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java
index ff8fdf7ace4..3bc9126f835 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.concurrent.maintenance.JobMetrics;
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 7afa10ab8d5..8d45fcb8878 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,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.AbstractComponent;
@@ -86,6 +86,7 @@ public class ControllerMaintenance extends AbstractComponent {
maintainers.add(new CertificatePoolMaintainer(controller, metric, intervals.certificatePoolMaintainer));
maintainers.add(new BillingReportMaintainer(controller, intervals.billingReportMaintainer));
maintainers.add(new CloudAccountVerifier(controller, intervals.cloudAccountVerifier));
+ maintainers.add(new DataPlaneTokenRedeployer(controller, intervals.dataPlaneTokenRedeployer));
}
public Upgrader upgrader() { return upgrader; }
@@ -149,6 +150,7 @@ public class ControllerMaintenance extends AbstractComponent {
private final Duration certificatePoolMaintainer;
private final Duration billingReportMaintainer;
private final Duration cloudAccountVerifier;
+ private final Duration dataPlaneTokenRedeployer;
public Intervals(SystemName system) {
this.system = Objects.requireNonNull(system);
@@ -187,6 +189,7 @@ public class ControllerMaintenance extends AbstractComponent {
this.certificatePoolMaintainer = duration(15, MINUTES);
this.billingReportMaintainer = duration(60, MINUTES);
this.cloudAccountVerifier = duration(10, MINUTES);
+ this.dataPlaneTokenRedeployer = duration(1, MINUTES);
}
private Duration duration(long amount, TemporalUnit unit) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
index 668893d5a7e..af8248c399c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DataPlaneTokenRedeployer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DataPlaneTokenRedeployer.java
new file mode 100644
index 00000000000..e9d2dc0714b
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DataPlaneTokenRedeployer.java
@@ -0,0 +1,24 @@
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Controller;
+
+import java.time.Duration;
+
+/**
+ * @author jonmv
+ */
+public class DataPlaneTokenRedeployer extends ControllerMaintainer {
+
+ public DataPlaneTokenRedeployer(Controller controller, Duration interval) {
+ super(controller, interval);
+ }
+
+ @Override
+ protected double maintain() {
+ controller().dataplaneTokenService().triggerTokenChangeDeployments();
+ return 0;
+ }
+
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
index c22cb1efdb3..aea23e6def8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainer.java
index b2b06cf281f..7b4ed9e1e98 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainer.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Application;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
index cd0f4be7a48..ae9eb1dc2b5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
index 29266a25c5e..df1f793914e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.text.Text;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java
index 82cac1e7520..270c388d73c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java
index 6c1c4daa1bb..8fd9dc919fb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.CloudAccount;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
index 805bf3d7ada..e3e3e347c04 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
@@ -1,35 +1,26 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.google.common.collect.Sets;
import com.yahoo.component.annotation.Inject;
-import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.container.jdisc.secretstore.SecretNotFoundException;
import com.yahoo.container.jdisc.secretstore.SecretStore;
import com.yahoo.transaction.Mutex;
import com.yahoo.transaction.NestedTransaction;
-import com.yahoo.vespa.flags.BooleanFlag;
-import com.yahoo.vespa.flags.FetchVector;
-import com.yahoo.vespa.flags.Flags;
-import com.yahoo.vespa.flags.IntFlag;
-import com.yahoo.vespa.flags.PermanentFlags;
-import com.yahoo.vespa.flags.StringFlag;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateDetails;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProvider;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateRequest;
-import com.yahoo.vespa.hosted.controller.application.Endpoint;
-import com.yahoo.vespa.hosted.controller.application.GeneratedEndpoint;
-import com.yahoo.vespa.hosted.controller.certificate.UnassignedCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.EndpointSecretManager;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.certificate.AssignedCertificate;
+import com.yahoo.vespa.hosted.controller.certificate.UnassignedCertificate;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
@@ -43,11 +34,9 @@ import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
-import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* Updates refreshed endpoint certificates and triggers redeployment, and deletes unused certificates.
@@ -67,9 +56,6 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer {
private final EndpointSecretManager endpointSecretManager;
private final EndpointCertificateProvider endpointCertificateProvider;
final Comparator<EligibleJob> oldestFirst = Comparator.comparing(e -> e.deployment.at());
- private final StringFlag endpointCertificateAlgo;
- private final BooleanFlag useAlternateCertProvider;
- private final IntFlag assignRandomizedIdRate;
@Inject
public EndpointCertificateMaintainer(Controller controller, Duration interval) {
@@ -80,9 +66,6 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer {
this.endpointSecretManager = controller.serviceRegistry().secretManager();
this.curator = controller().curator();
this.endpointCertificateProvider = controller.serviceRegistry().endpointCertificateProvider();
- this.useAlternateCertProvider = PermanentFlags.USE_ALTERNATIVE_ENDPOINT_CERTIFICATE_PROVIDER.bindTo(controller.flagSource());
- this.endpointCertificateAlgo = PermanentFlags.ENDPOINT_CERTIFICATE_ALGORITHM.bindTo(controller.flagSource());
- this.assignRandomizedIdRate = Flags.ASSIGNED_RANDOMIZED_ID_RATE.bindTo(controller.flagSource());
}
@Override
@@ -93,12 +76,10 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer {
updateRefreshedCertificates();
deleteUnusedCertificates();
deleteOrReportUnmanagedCertificates();
- assignRandomizedIds();
} catch (Exception e) {
log.log(Level.SEVERE, "Exception caught while maintaining endpoint certificates", e);
return 1.0;
}
-
return 0.0;
}
@@ -270,115 +251,6 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer {
}
}
- private void assignRandomizedIds() {
- List<AssignedCertificate> assignedCertificates = curator.readAssignedCertificates();
- /*
- only assign randomized id if:
- * instance is present
- * randomized id is not already assigned
- * feature flag is enabled
- */
- assignedCertificates.stream()
- .filter(c -> c.instance().isPresent())
- .filter(c -> c.certificate().randomizedId().isEmpty())
- .filter(c -> controller().applications().getApplication(c.application()).isPresent()) // In case application has been deleted, but certificate is pending deletion
- .limit(assignRandomizedIdRate.value())
- .forEach(c -> assignRandomizedId(c.application(), c.instance().get()));
- }
-
- /*
- Assign randomized id according to these rules:
- * Instance is not mentioned in the deployment spec for this application
- -> assume this is a manual deployment. Assign a randomized id to the certificate, save using instance only
- * Instance is mentioned in deployment spec:
- -> If there is a random endpoint assigned to tenant:application -> use this also for the "instance" certificate
- -> Otherwise assign a random endpoint and write to the application and the instance.
- */
- private void assignRandomizedId(TenantAndApplicationId tenantAndApplicationId, InstanceName instanceName) {
- Optional<AssignedCertificate> assignedCertificate = curator.readAssignedCertificate(tenantAndApplicationId, Optional.of(instanceName));
- if (assignedCertificate.isEmpty()) {
- log.log(Level.INFO, "Assigned certificate missing for " + tenantAndApplicationId.instance(instanceName).toFullString() + " when assigning randomized id");
- }
- // Verify that the assigned certificate still does not have randomized id assigned
- if (assignedCertificate.get().certificate().randomizedId().isPresent()) return;
-
- controller().applications().lockApplicationOrThrow(tenantAndApplicationId, application -> {
- DeploymentSpec deploymentSpec = application.get().deploymentSpec();
- if (deploymentSpec.instance(instanceName).isPresent()) {
- Optional<AssignedCertificate> applicationLevelAssignedCertificate = curator.readAssignedCertificate(tenantAndApplicationId, Optional.empty());
- assignApplicationRandomId(assignedCertificate.get(), applicationLevelAssignedCertificate);
- } else {
- assignInstanceRandomId(assignedCertificate.get());
- }
- });
- }
-
- private void assignApplicationRandomId(AssignedCertificate instanceLevelAssignedCertificate, Optional<AssignedCertificate> applicationLevelAssignedCertificate) {
- TenantAndApplicationId tenantAndApplicationId = instanceLevelAssignedCertificate.application();
- if (applicationLevelAssignedCertificate.isPresent()) {
- // Application level assigned certificate with randomized id already exists. Copy randomized id to instance level certificate and request with random names.
- EndpointCertificate withRandomNames = requestRandomNames(
- tenantAndApplicationId,
- instanceLevelAssignedCertificate.instance(),
- applicationLevelAssignedCertificate.get().certificate().randomizedId()
- .orElseThrow(() -> new IllegalArgumentException("Application certificate already assigned to " + tenantAndApplicationId.toString() + ", but random id is missing")),
- Optional.of(instanceLevelAssignedCertificate.certificate()));
- AssignedCertificate assignedCertWithRandomNames = instanceLevelAssignedCertificate.with(withRandomNames);
- curator.writeAssignedCertificate(assignedCertWithRandomNames);
- } else {
- // No application level certificate exists, generate new assigned certificate with the randomized id based names only, then request same names also for instance level cert
- String randomId = generateRandomId();
- EndpointCertificate applicationLevelEndpointCert = requestRandomNames(tenantAndApplicationId, Optional.empty(), randomId, Optional.empty());
- AssignedCertificate applicationLevelCert = new AssignedCertificate(tenantAndApplicationId, Optional.empty(), applicationLevelEndpointCert);
-
- EndpointCertificate instanceLevelEndpointCert = requestRandomNames(tenantAndApplicationId, instanceLevelAssignedCertificate.instance(), randomId, Optional.of(instanceLevelAssignedCertificate.certificate()));
- instanceLevelAssignedCertificate = instanceLevelAssignedCertificate.with(instanceLevelEndpointCert);
-
- // Save both in transaction
- try (NestedTransaction transaction = new NestedTransaction()) {
- curator.writeAssignedCertificate(instanceLevelAssignedCertificate, transaction);
- curator.writeAssignedCertificate(applicationLevelCert, transaction);
- transaction.commit();
- }
- }
- }
-
- private void assignInstanceRandomId(AssignedCertificate assignedCertificate) {
- String randomId = generateRandomId();
- EndpointCertificate withRandomNames = requestRandomNames(assignedCertificate.application(), assignedCertificate.instance(), randomId, Optional.of(assignedCertificate.certificate()));
- AssignedCertificate assignedCertWithRandomNames = assignedCertificate.with(withRandomNames);
- curator.writeAssignedCertificate(assignedCertWithRandomNames);
- }
-
- private EndpointCertificate requestRandomNames(TenantAndApplicationId tenantAndApplicationId, Optional<InstanceName> instanceName, String randomId, Optional<EndpointCertificate> previousRequest) {
- String dnsSuffix = Endpoint.dnsSuffix(controller().system());
- List<String> newSanDnsEntries = List.of(
- "*.%s.z%s".formatted(randomId, dnsSuffix),
- "*.%s.g%s".formatted(randomId, dnsSuffix),
- "*.%s.a%s".formatted(randomId, dnsSuffix));
- List<String> existingSanDnsEntries = previousRequest.map(EndpointCertificate::requestedDnsSans).orElse(List.of());
- List<String> requestNames = Stream.concat(existingSanDnsEntries.stream(), newSanDnsEntries.stream()).toList();
- String key = instanceName.map(tenantAndApplicationId::instance).map(ApplicationId::toFullString).orElseGet(tenantAndApplicationId::toString);
- return endpointCertificateProvider.requestCaSignedCertificate(
- key,
- requestNames,
- previousRequest,
- endpointCertificateAlgo.value(),
- useAlternateCertProvider.value())
- .withRandomizedId(randomId);
- }
-
- private String generateRandomId() {
- List<String> unassignedIds = curator.readUnassignedCertificates().stream().map(UnassignedCertificate::id).toList();
- List<String> assignedIds = curator.readAssignedCertificates().stream().map(AssignedCertificate::certificate).map(EndpointCertificate::randomizedId).filter(Optional::isPresent).map(Optional::get).toList();
- Set<String> allIds = Stream.concat(unassignedIds.stream(), assignedIds.stream()).collect(Collectors.toSet());
- String randomId;
- do {
- randomId = GeneratedEndpoint.createPart(controller().random(true));
- } while (allIds.contains(randomId));
- return randomId;
- }
-
private static String asString(TenantAndApplicationId application, Optional<InstanceName> instanceName) {
return application.toString() + instanceName.map(name -> "." + name.value()).orElse("");
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java
index 31236f4fcda..5d6e60ee0bf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
index b1ea4584497..97bb709d423 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
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 94c3201edbe..0f482b1a015 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
@@ -1,7 +1,9 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
+import ai.vespa.metrics.ControllerMetrics;
import com.yahoo.concurrent.DaemonThreadFactory;
+import com.yahoo.jdisc.Metric;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.deployment.InternalStepRunner;
@@ -11,11 +13,14 @@ import com.yahoo.vespa.hosted.controller.deployment.Step;
import com.yahoo.vespa.hosted.controller.deployment.StepRunner;
import java.time.Duration;
+import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -32,22 +37,29 @@ public class JobRunner extends ControllerMaintainer {
private final JobController jobs;
private final ExecutorService executors;
private final StepRunner runner;
+ private final Metrics metrics;
public JobRunner(Controller controller, Duration duration) {
- this(controller, duration, Executors.newFixedThreadPool(32, new DaemonThreadFactory("job-runner-")), new InternalStepRunner(controller));
+ this(controller, duration, Executors.newFixedThreadPool(32, new DaemonThreadFactory("job-runner-")),
+ new InternalStepRunner(controller));
}
public JobRunner(Controller controller, Duration duration, ExecutorService executors, StepRunner runner) {
+ this(controller, duration, executors, runner, new Metrics(controller.metric(), Duration.ofMillis(100)));
+ }
+
+ JobRunner(Controller controller, Duration duration, ExecutorService executors, StepRunner runner, Metrics metrics) {
super(controller, duration);
this.jobs = controller.jobController();
this.jobs.setRunner(this::advance);
this.executors = executors;
this.runner = runner;
+ this.metrics = metrics;
}
@Override
protected double maintain() {
- executors.execute(() -> jobs.active().forEach(this::advance));
+ execute(() -> jobs.active().forEach(this::advance));
jobs.collectGarbage();
return 1.0;
}
@@ -55,6 +67,7 @@ public class JobRunner extends ControllerMaintainer {
@Override
public void shutdown() {
super.shutdown();
+ metrics.shutdown();
executors.shutdown();
}
@@ -62,9 +75,9 @@ public class JobRunner extends ControllerMaintainer {
public void awaitShutdown() {
super.awaitShutdown();
try {
- if ( ! executors.awaitTermination(10, TimeUnit.SECONDS)) {
+ if ( ! executors.awaitTermination(40, TimeUnit.SECONDS)) {
executors.shutdownNow();
- if ( ! executors.awaitTermination(40, TimeUnit.SECONDS))
+ if ( ! executors.awaitTermination(10, TimeUnit.SECONDS))
throw new IllegalStateException("Failed shutting down " + JobRunner.class.getName());
}
}
@@ -83,14 +96,14 @@ public class JobRunner extends ControllerMaintainer {
jobs.locked(id, run -> {
if ( ! run.hasFailed()
&& controller().clock().instant().isAfter(run.sleepUntil().orElse(run.start()).plus(jobTimeout)))
- executors.execute(() -> {
+ execute(() -> {
jobs.abort(run.id(), "job timeout of " + jobTimeout + " reached", false);
advance(run.id());
});
else if (run.readySteps().isEmpty())
- executors.execute(() -> finish(run.id()));
+ execute(() -> finish(run.id()));
else if (run.hasFailed() || run.sleepUntil().map(sleepUntil -> ! sleepUntil.isAfter(controller().clock().instant())).orElse(true))
- run.readySteps().forEach(step -> executors.execute(() -> advance(run.id(), step)));
+ run.readySteps().forEach(step -> execute(() -> advance(run.id(), step)));
return null;
});
@@ -145,4 +158,39 @@ public class JobRunner extends ControllerMaintainer {
}
}
+ private void execute(Runnable task) {
+ metrics.queued.incrementAndGet();
+ executors.execute(() -> {
+ metrics.queued.decrementAndGet();
+ metrics.active.incrementAndGet();
+ try { task.run(); }
+ finally { metrics.active.decrementAndGet(); }
+ });
+ }
+
+ static class Metrics {
+
+ private final AtomicInteger queued = new AtomicInteger();
+ private final AtomicInteger active = new AtomicInteger();
+ private final ScheduledExecutorService reporter = Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory("job-runner-metrics-"));
+ private final Metric metric;
+ private final Metric.Context context;
+
+ Metrics(Metric metric, Duration interval) {
+ this.metric = metric;
+ this.context = metric.createContext(Map.of());
+ reporter.scheduleAtFixedRate(this::report, interval.toMillis(), interval.toMillis(), TimeUnit.MILLISECONDS);
+ }
+
+ void report() {
+ metric.set(ControllerMetrics.DEPLOYMENT_JOBS_QUEUED.baseName(), queued.get(), context);
+ metric.set(ControllerMetrics.DEPLOYMENT_JOBS_ACTIVE.baseName(), active.get(), context);
+ }
+
+ void shutdown() {
+ reporter.shutdown();
+ }
+
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MeteringMonitorMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MeteringMonitorMaintainer.java
index 519b1001be4..396ec1ec6f9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MeteringMonitorMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MeteringMonitorMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
index 6a280e71e98..6f070cbba84 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
import ai.vespa.metrics.ConfigServerMetrics;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java
index e4841618852..3ee9650d4ca 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcher.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Controller;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java
index dbca6681281..a712c4f35d9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java
index 44f0bcecf5f..25a0abbce90 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgrader.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java
index a3a866d2036..2fd92970bc9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Controller;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
index 37b06fea066..6c414e44a96 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Application;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReadyJobsTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReadyJobsTrigger.java
index 974345330aa..e35bb139142 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReadyJobsTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReadyJobsTrigger.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Controller;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggerer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggerer.java
index 945b6d32a30..0668f8c481c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggerer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggerer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
index 6ee1a8b56d7..5cadd13309b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
@@ -1,9 +1,10 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
import ai.vespa.metrics.ControllerMetrics;
import com.yahoo.concurrent.UncheckedTimeoutException;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.NodeResources;
@@ -18,11 +19,9 @@ import com.yahoo.vespa.hosted.controller.Instance;
import com.yahoo.vespa.hosted.controller.api.identifiers.ClusterId;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.Cluster;
-import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
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 com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceAllocation;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceDatabaseClient;
import com.yahoo.vespa.hosted.controller.api.integration.resource.ResourceSnapshot;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
@@ -199,14 +198,9 @@ public class ResourceMeterMaintainer extends ControllerMaintainer {
.filter(this::unlessNodeOwnerIsSystemApplication)
.filter(this::isNodeStateMeterable)
.filter(this::isClusterTypeMeterable)
- // Grouping by ApplicationId -> Architecture -> ResourceSnapshot
- .collect(Collectors.groupingBy(node ->
- node.owner().get(),
- groupSnapshotsByArchitectureAndMajorVersion(zoneId)))
+ .collect(groupSnapshots(zoneId))
.values()
.stream()
- .flatMap(byArch -> byArch.values().stream())
- .flatMap(byMajor -> byMajor.values().stream())
.toList();
}
@@ -281,17 +275,15 @@ public class ResourceMeterMaintainer extends ControllerMaintainer {
));
}
- private Collector<Node, ?, Map<NodeResources.Architecture, Map<Integer, ResourceSnapshot>>> groupSnapshotsByArchitectureAndMajorVersion(ZoneId zoneId) {
- return Collectors.groupingBy(
- (Node node) -> node.resources().architecture(),
- Collectors.collectingAndThen(
- Collectors.groupingBy(
- (Node node) -> node.wantedVersion().getMajor(),
- Collectors.toList()),
- convertNodeListToResourceSnapshot(zoneId)));
+ private Collector<Node, ?, Map<ResourceKey, ResourceSnapshot>> groupSnapshots(ZoneId zoneId) {
+ return Collectors.collectingAndThen(
+ Collectors.groupingBy(
+ (Node node) -> new ResourceKey(node.owner().get(), node.resources().architecture(), node.wantedVersion().getMajor(), node.cloudAccount()),
+ Collectors.toList()),
+ convertNodeListToResourceSnapshot(zoneId));
}
- private Function<Map<Integer, List<Node>>, Map<Integer, ResourceSnapshot>> convertNodeListToResourceSnapshot(ZoneId zoneId) {
+ private Function<Map<ResourceKey, List<Node>>, Map<ResourceKey, ResourceSnapshot>> convertNodeListToResourceSnapshot(ZoneId zoneId) {
return nodesByMajor -> {
return nodesByMajor.entrySet().stream()
.collect(Collectors.toMap(
@@ -299,4 +291,10 @@ public class ResourceMeterMaintainer extends ControllerMaintainer {
entry -> ResourceSnapshot.from(entry.getValue(), clock.instant(), zoneId)));
};
}
+
+ private record ResourceKey(
+ ApplicationId applicationId,
+ NodeResources.Architecture architecture,
+ int majorVersion,
+ CloudAccount account) {}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java
index 59871f716e0..a0c94c0b9a7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainer.java
index aaf730cc158..3cbd7b3e0e6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java
index effcc4dd4df..c31f81497e6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgrader.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleCleanupMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleCleanupMaintainer.java
index e3a3415e170..5539c62be98 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleCleanupMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleCleanupMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Controller;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java
index 5f7dae5a352..f76b7634c62 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
index a929a1d7af8..dceb3921061 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
@@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Random;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.logging.Level;
@@ -42,10 +43,16 @@ public class Upgrader extends ControllerMaintainer {
private static final Logger log = Logger.getLogger(Upgrader.class.getName());
private final CuratorDb curator;
+ private final Random random;
public Upgrader(Controller controller, Duration interval) {
+ this(controller, interval, controller.random(false));
+ }
+
+ Upgrader(Controller controller, Duration interval, Random random) {
super(controller, interval);
this.curator = controller.curator();
+ this.random = random;
}
/**
@@ -75,7 +82,7 @@ public class Upgrader extends ControllerMaintainer {
private InstanceList instances(DeploymentStatusList deploymentStatuses) {
return InstanceList.from(deploymentStatuses)
.withDeclaredJobs()
- .shuffle(controller().random(false))
+ .shuffle(random)
.byIncreasingDeployedVersion()
.unpinned();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainer.java
index 7c4645a6e48..0f39ef7d0f0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainer.java
index df6e89fb5ef..b0d7a0c47e9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Environment;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java
index 1c4d13aa16d..721819522f5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.Controller;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/package-info.java
index 6ff3e2d1c52..f8eed5804bb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
package com.yahoo.vespa.hosted.controller.maintenance;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/ApplicationMetrics.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/ApplicationMetrics.java
index 4569a1bb1f9..79d196b07fa 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/ApplicationMetrics.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/ApplicationMetrics.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.metric;
/**
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/CostCalculator.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/CostCalculator.java
index 957eada62b4..23b81ebcd34 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/CostCalculator.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/metric/CostCalculator.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.metric;
import com.yahoo.config.provision.CloudName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/FormattedNotification.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/FormattedNotification.java
index 5e36d6d6499..bed053d592f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/FormattedNotification.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/FormattedNotification.java
@@ -1,6 +1,6 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.notification;
-import java.net.URI;
import java.util.Objects;
/**
@@ -9,7 +9,7 @@ import java.util.Objects;
*
* @author enygaard
*/
-public record FormattedNotification(Notification notification, String prettyType, String messagePrefix, URI uri) {
+public record FormattedNotification(Notification notification, String prettyType, String messagePrefix, String uri) {
public FormattedNotification {
Objects.requireNonNull(prettyType);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/MailTemplating.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/MailTemplating.java
new file mode 100644
index 00000000000..1c05330702e
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/MailTemplating.java
@@ -0,0 +1,101 @@
+// 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.yahoo.config.provision.TenantName;
+import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls;
+import com.yahoo.vespa.hosted.controller.tenant.PendingMailVerification;
+import com.yahoo.yolean.Exceptions;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.runtime.resource.util.StringResourceRepository;
+import org.apache.velocity.tools.generic.EscapeTool;
+
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author bjorncs
+ */
+public class MailTemplating {
+
+ public enum Template {
+ MAIL("mail"), DEFAULT_MAIL_CONTENT("default-mail-content"), NOTIFICATION_MESSAGE("notification-message"),
+ CLOUD_TRIAL_NOTIFICATION("cloud-trial-notification"), MAIL_VERIFICATION("mail-verification");
+
+ public static Optional<Template> fromId(String id) {
+ return Arrays.stream(values()).filter(t -> t.id.equals(id)).findAny();
+ }
+
+ private final String id;
+
+ Template(String id) { this.id = id; }
+
+ public String getId() { return id; }
+ }
+
+ private final VelocityEngine velocity;
+ private final EscapeTool escapeTool = new EscapeTool();
+ private final ConsoleUrls consoleUrls;
+
+ public MailTemplating(ConsoleUrls consoleUrls) {
+ this.velocity = createTemplateEngine();
+ this.consoleUrls = consoleUrls;
+ }
+
+ public String generateDefaultMailHtml(Template mailBodyTemplate, Map<String, Object> params, TenantName tenant) {
+ var ctx = createVelocityContext();
+ ctx.put("accountNotificationLink", consoleUrls.tenantNotifications(tenant));
+ ctx.put("privacyPolicyLink", "https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html");
+ ctx.put("termsOfServiceLink", consoleUrls.termsOfService());
+ ctx.put("supportLink", consoleUrls.support());
+ ctx.put("mailBodyTemplate", mailBodyTemplate.getId());
+ params.forEach(ctx::put);
+ return render(ctx, Template.MAIL);
+ }
+
+ public String generateMailVerificationHtml(PendingMailVerification pmf) {
+ var ctx = createVelocityContext();
+ ctx.put("verifyLink", consoleUrls.verifyEmail(pmf.getVerificationCode()));
+ ctx.put("email", pmf.getMailAddress());
+ return render(ctx, Template.MAIL_VERIFICATION);
+ }
+
+ public String escapeHtml(String s) { return escapeTool.html(s); }
+
+ private VelocityContext createVelocityContext() {
+ var ctx = new VelocityContext();
+ ctx.put("esc", escapeTool);
+ return ctx;
+ }
+
+ private String render(VelocityContext ctx, Template template) {
+ var writer = new StringWriter();
+ // Ignoring return value - implementation either returns 'true' or throws, never 'false'
+ velocity.mergeTemplate(template.getId(), StandardCharsets.UTF_8.name(), ctx, writer);
+ return writer.toString();
+ }
+
+ private static VelocityEngine createTemplateEngine() {
+ var v = new VelocityEngine();
+ v.setProperty(Velocity.RESOURCE_LOADERS, "string");
+ v.setProperty(Velocity.RESOURCE_LOADER + ".string.class", StringResourceLoader.class.getName());
+ v.setProperty(Velocity.RESOURCE_LOADER + ".string.repository.static", "false");
+ v.init();
+ var repo = (StringResourceRepository) v.getApplicationAttribute(StringResourceLoader.REPOSITORY_NAME_DEFAULT);
+ Arrays.stream(Template.values()).forEach(t -> registerTemplate(repo, t.getId()));
+ return v;
+ }
+
+ private static void registerTemplate(StringResourceRepository repo, String name) {
+ var templateStr = Exceptions.uncheck(() -> {
+ var in = MailTemplating.class.getResourceAsStream("/mail/%s.vm".formatted(name));
+ return new String(in.readAllBytes());
+ });
+ repo.putStringResource(name, templateStr);
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/MissingOptionalException.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/MissingOptionalException.java
index 1379ab4654f..50e4cd40af7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/MissingOptionalException.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/MissingOptionalException.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.notification;
/**
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java
index 53450783c8e..897e0be2d22 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notification.java
@@ -1,9 +1,16 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.notification;
import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
+import java.util.SortedMap;
+import java.util.TreeMap;
/**
* Represents an event that we want to notify the tenant about. The message(s) should be short
@@ -13,15 +20,30 @@ import java.util.Objects;
*
* @author freva
*/
-public record Notification(Instant at, com.yahoo.vespa.hosted.controller.notification.Notification.Type type, com.yahoo.vespa.hosted.controller.notification.Notification.Level level, NotificationSource source, List<String> messages) {
+public record Notification(Instant at, Notification.Type type, Notification.Level level, NotificationSource source,
+ String title, List<String> messages, Optional<MailContent> mailContent) {
+
+ public Notification(Instant at, Type type, Level level, NotificationSource source, String title, List<String> messages) {
+ this(at, type, level, source, title, messages, Optional.empty());
+ }
public Notification(Instant at, Type type, Level level, NotificationSource source, List<String> messages) {
- this.at = Objects.requireNonNull(at, "at cannot be null");
- this.type = Objects.requireNonNull(type, "type cannot be null");
- this.level = Objects.requireNonNull(level, "level cannot be null");
- this.source = Objects.requireNonNull(source, "source cannot be null");
- this.messages = List.copyOf(Objects.requireNonNull(messages, "messages cannot be null"));
- if (messages.size() < 1) throw new IllegalArgumentException("messages cannot be empty");
+ this(at, type, level, source, "", messages);
+ }
+
+ public Notification {
+ Objects.requireNonNull(at, "at cannot be null");
+ Objects.requireNonNull(type, "type cannot be null");
+ Objects.requireNonNull(level, "level cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(title, "title cannot be null");
+ messages = List.copyOf(Objects.requireNonNull(messages, "messages cannot be null"));
+
+ // Allowing empty title temporarily until all notifications have a title
+ // if (title.isBlank()) throw new IllegalArgumentException("title cannot be empty");
+ if (messages.isEmpty() && title.isBlank()) throw new IllegalArgumentException("messages cannot be empty when title is empty");
+
+ Objects.requireNonNull(mailContent);
}
public enum Level {
@@ -31,36 +53,81 @@ public record Notification(Instant at, com.yahoo.vespa.hosted.controller.notific
public enum Type {
- /**
- * Related to contents of application package, e.g., usage of deprecated features/syntax
- */
+ /** Related to contents of application package, e.g., usage of deprecated features/syntax */
applicationPackage,
- /**
- * Related to contents of application package detectable by the controller on submission
- */
+ /** Related to contents of application package detectable by the controller on submission */
submission,
- /**
- * Related to contents of application test package, e.g., mismatch between deployment spec and provided tests
- */
+ /** Related to contents of application test package, e.g., mismatch between deployment spec and provided tests */
testPackage,
- /**
- * Related to deployment of application, e.g., system test failure, node allocation failure, internal errors, etc.
- */
+ /** Related to deployment of application, e.g., system test failure, node allocation failure, internal errors, etc. */
deployment,
- /**
- * Application cluster is (near) external feed blocked
- */
+ /** Application cluster is (near) external feed blocked */
feedBlock,
- /**
- * Application cluster is reindexing document(s)
- */
- reindex
+ /** Application cluster is reindexing document(s) */
+ reindex,
+
+ /** Account, e.g. expiration of trial plan */
+ account,
+ }
+
+ public static class MailContent {
+ private final MailTemplating.Template template;
+ private final SortedMap<String, Object> values;
+ private final String subject;
+
+ private MailContent(Builder b) {
+ template = Objects.requireNonNull(b.template);
+ values = new TreeMap<>(b.values);
+ subject = b.subject;
+ }
+
+ public MailTemplating.Template template() { return template; }
+ public SortedMap<String, Object> values() { return Collections.unmodifiableSortedMap(values); }
+ public Optional<String> subject() { return Optional.ofNullable(subject); }
+
+ public static Builder fromTemplate(MailTemplating.Template template) { return new Builder(template); }
+
+ public static class Builder {
+ private final MailTemplating.Template template;
+ private final Map<String, Object> values = new HashMap<>();
+ private String subject;
+
+ private Builder(MailTemplating.Template template) {
+ this.template = template;
+ }
+
+ public Builder with(String name, String value) { values.put(name, value); return this; }
+ public Builder with(String name, Collection<String> items) { values.put(name, List.copyOf(items)); return this; }
+ public Builder subject(String s) { this.subject = s; return this; }
+ public MailContent build() { return new MailContent(this); }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MailContent that = (MailContent) o;
+ return Objects.equals(template, that.template) && Objects.equals(values, that.values) && Objects.equals(subject, that.subject);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(template, values, subject);
+ }
+ @Override
+ public String toString() {
+ return "MailContent{" +
+ "template='" + template + '\'' +
+ ", values=" + values +
+ ", subject='" + subject + '\'' +
+ '}';
+ }
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatter.java
index f753f22608d..e9b38f7a122 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatter.java
@@ -1,17 +1,14 @@
+// Copyright Vespa.ai. 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.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Environment;
import com.yahoo.text.Text;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
-import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
-import org.apache.http.client.utils.URIBuilder;
+import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Function;
+
+import static com.yahoo.vespa.hosted.controller.notification.Notifier.notificationLink;
/**
* Created a NotificationContent for a given Notification.
@@ -21,10 +18,10 @@ import java.util.function.Function;
* @author enygaard
*/
public class NotificationFormatter {
- private final ZoneRegistry zoneRegistry;
+ private final ConsoleUrls consoleUrls;
- public NotificationFormatter(ZoneRegistry zoneRegistry) {
- this.zoneRegistry = Objects.requireNonNull(zoneRegistry);
+ public NotificationFormatter(ConsoleUrls consoleUrls) {
+ this.consoleUrls = Objects.requireNonNull(consoleUrls);
}
public FormattedNotification format(Notification n) {
@@ -34,20 +31,18 @@ public class NotificationFormatter {
case testPackage -> testPackage(n);
case reindex -> reindex(n);
case feedBlock -> feedBlock(n);
- default -> new FormattedNotification(n, n.type().name(), "", zoneRegistry.dashboardUrl(n.source().tenant()));
+ default -> new FormattedNotification(n, n.type().name(), "", consoleUrls.tenantOverview(n.source().tenant()));
};
}
private FormattedNotification applicationPackage(Notification n) {
var source = n.source();
var application = requirePresent(source.application(), "application");
- var instance = requirePresent(source.instance(), "instance");
- var message = Text.format("Application package for %s.%s has %s",
+ var message = Text.format("Application package for %s%s has %s",
application,
- instance,
+ source.instance().map(instance -> "." + instance.value()).orElse(""),
levelText(n.level(), n.messages().size()));
- var uri = zoneRegistry.dashboardUrl(ApplicationId.from(source.tenant(), application, instance));
- return new FormattedNotification(n, "Application package", message, uri);
+ return new FormattedNotification(n, "Application package", message, notificationLink(consoleUrls, n.source()));
}
private FormattedNotification deployment(Notification n) {
@@ -57,7 +52,7 @@ public class NotificationFormatter {
requirePresent(source.application(), "application"),
requirePresent(source.instance(), "instance"),
levelText(n.level(), n.messages().size()));
- return new FormattedNotification(n,"Deployment", message, jobLink(n.source()));
+ return new FormattedNotification(n,"Deployment", message, notificationLink(consoleUrls, n.source()));
}
private FormattedNotification testPackage(Notification n) {
@@ -67,68 +62,23 @@ public class NotificationFormatter {
n.messages().size() > 1 ? "are problems" : "is a problem",
application,
source.instance().map(i -> "."+i).orElse(""));
- var uri = zoneRegistry.dashboardUrl(source.tenant(), application);
- return new FormattedNotification(n, "Test package", message, uri);
+ return new FormattedNotification(n, "Test package", message, notificationLink(consoleUrls, n.source()));
}
private FormattedNotification reindex(Notification n) {
var message = Text.format("%s is reindexing", clusterInfo(n.source()));
- var source = n.source();
- var application = requirePresent(source.application(), "application");
- var instance = requirePresent(source.instance(), "instance");
- var clusterId = requirePresent(source.clusterId(), "clusterId");
- var zone = requirePresent(source.zoneId(), "zoneId");
- var instanceURI = zoneRegistry.dashboardUrl(ApplicationId.from(source.tenant(), application, instance));
- try {
- var uri = new URIBuilder(instanceURI)
- .setParameter(
- String.format("%s.%s.%s", instance, zone.environment(), zone.region()),
- String.format("clusters,%s=status", clusterId.value()))
- .build();
- return new FormattedNotification(n, "Reindex", message, uri);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException(e);
- }
+ var application = requirePresent(n.source().application(), "application");
+ var instance = requirePresent(n.source().instance(), "instance");
+ var clusterId = requirePresent(n.source().clusterId(), "clusterId");
+ var zone = requirePresent(n.source().zoneId(), "zoneId");
+ return new FormattedNotification(n, "Reindex", message,
+ consoleUrls.clusterReindexing(ApplicationId.from(n.source().tenant(), application, instance), zone, clusterId));
}
private FormattedNotification feedBlock(Notification n) {
- String type;
- if (n.level() == Notification.Level.warning) {
- type = "Nearly feed blocked";
- } else {
- type = "Feed blocked";
- }
+ String type = n.level() == Notification.Level.warning ? "Nearly feed blocked" : "Feed blocked";
var message = Text.format("%s is %s", clusterInfo(n.source()), type.toLowerCase());
- var source = n.source();
- var application = requirePresent(source.application(), "application");
- var instance = requirePresent(source.instance(), "instance");
- var clusterId = requirePresent(source.clusterId(), "clusterId");
- var zone = requirePresent(source.zoneId(), "zoneId");
- var instanceURI = zoneRegistry.dashboardUrl(ApplicationId.from(source.tenant(), application, instance));
- try {
- var uri = new URIBuilder(instanceURI)
- .setParameter(
- String.format("%s.%s.%s", instance, zone.environment(), zone.region()),
- String.format("clusters,%s", clusterId.value()))
- .build();
- return new FormattedNotification(n, type, message, uri);
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException(e);
- }
- }
-
- private URI jobLink(NotificationSource source) {
- var application = requirePresent(source.application(), "application");
- var instance = requirePresent(source.instance(), "instance");
- var jobType = requirePresent(source.jobType(), "jobType");
- var runNumber = source.runNumber().orElseThrow(() -> new MissingOptionalException("runNumber"));
- var applicationId = ApplicationId.from(source.tenant(), application, instance);
- Function<Environment, URI> link = (Environment env) -> zoneRegistry.dashboardUrl(new RunId(applicationId, jobType, runNumber));
- var environment = jobType.zone().environment();
- return switch (environment) {
- case dev, perf -> link.apply(environment);
- default -> link.apply(Environment.prod);
- };
+ return new FormattedNotification(n, type, message, notificationLink(consoleUrls, n.source()));
}
private String jobText(NotificationSource source) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationSource.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationSource.java
index c414e24a187..72d3dd933aa 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationSource.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationSource.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.yahoo.config.provision.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java
index f8505775d26..e279e4feacd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDb.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.yahoo.collections.Pair;
@@ -9,7 +9,11 @@ import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ApplicationReindexing;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
+import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.notification.Notification.MailContent;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import java.time.Clock;
@@ -18,12 +22,14 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.yahoo.vespa.hosted.controller.api.integration.configserver.ApplicationReindexing.Cluster;
import static com.yahoo.vespa.hosted.controller.notification.Notification.Level;
import static com.yahoo.vespa.hosted.controller.notification.Notification.Type;
+import static com.yahoo.vespa.hosted.controller.notification.Notifier.notificationLink;
/**
* Adds, updates and removes tenant notifications in ZK
@@ -32,18 +38,22 @@ import static com.yahoo.vespa.hosted.controller.notification.Notification.Type;
*/
public class NotificationsDb {
+ private static final Logger log = Logger.getLogger(NotificationsDb.class.getName());
+
private final Clock clock;
private final CuratorDb curatorDb;
private final Notifier notifier;
+ private final ConsoleUrls consoleUrls;
public NotificationsDb(Controller controller) {
- this(controller.clock(), controller.curator(), controller.notifier());
+ this(controller.clock(), controller.curator(), controller.notifier(), controller.serviceRegistry().consoleUrls());
}
- NotificationsDb(Clock clock, CuratorDb curatorDb, Notifier notifier) {
+ NotificationsDb(Clock clock, CuratorDb curatorDb, Notifier notifier, ConsoleUrls consoleUrls) {
this.clock = clock;
this.curatorDb = curatorDb;
this.notifier = notifier;
+ this.consoleUrls = consoleUrls;
}
public List<TenantName> listTenantsWithNotifications() {
@@ -56,29 +66,69 @@ public class NotificationsDb {
.toList();
}
- public void setNotification(NotificationSource source, Type type, Level level, String message) {
- setNotification(source, type, level, List.of(message));
+ public void setSubmissionNotification(TenantAndApplicationId tenantApp, String message) {
+ NotificationSource source = NotificationSource.from(tenantApp);
+ String title = "Application package for [%s](%s) has a warning".formatted(
+ tenantApp.application().value(), notificationLink(consoleUrls, source));
+ setNotification(source, Type.submission, Level.warning, title, List.of(message), Optional.empty());
+ }
+
+ public void setApplicationPackageNotification(NotificationSource source, List<String> messages) {
+ String title = "Application package for [%s%s](%s) has %s".formatted(
+ source.application().get().value(), source.instance().map(i -> "." + i.value()).orElse(""), notificationLink(consoleUrls, source),
+ messages.size() == 1 ? "a warning" : "warnings");
+ setNotification(source, Type.applicationPackage, Level.warning, title, messages, Optional.empty());
+ }
+
+ public void setTestPackageNotification(TenantAndApplicationId tenantApp, List<String> messages) {
+ NotificationSource source = NotificationSource.from(tenantApp);
+ String title = "There %s with tests for [%s](%s)".formatted(
+ messages.size() == 1 ? "is a problem" : "are problems", tenantApp.application().value(),
+ notificationLink(consoleUrls, source));
+ setNotification(source, Type.testPackage, Level.warning, title, messages, Optional.empty());
+ }
+
+ public void setDeploymentNotification(RunId runId, String message) {
+ String description, linkText;
+ if (runId.type().isProduction()) {
+ description = runId.type().isTest() ? "Test job " : "Deployment job ";
+ linkText = "#" + runId.number() + " to " + runId.type().zone().region().value();
+ } else if (runId.type().isTest()) {
+ description = "";
+ linkText = (runId.type().isStagingTest() ? "Staging" : "System") + " test #" + runId.number();
+ } else if (runId.type().isDeployment()) {
+ description = "Deployment job ";
+ linkText = "#" + runId.number() + " to " + runId.type().zone().value();
+ } else throw new IllegalStateException("Unexpected job type " + runId.type());
+ NotificationSource source = NotificationSource.from(runId);
+ String title = "%s[%s](%s) for application **%s.%s** has failed".formatted(
+ description, linkText, notificationLink(consoleUrls, source), runId.application().application().value(), runId.application().instance().value());
+ setNotification(source, Type.deployment, Level.error, title, List.of(message), Optional.empty());
}
/**
* Add a notification with given source and type. If a notification with same source and type
- * already exists, it'll be replaced by this one instead
+ * already exists, it'll be replaced by this one instead.
*/
- public void setNotification(NotificationSource source, Type type, Level level, List<String> messages) {
+ public void setNotification(NotificationSource source, Type type, Level level, String title, List<String> messages,
+ Optional<MailContent> mailContent) {
Optional<Notification> changed = Optional.empty();
try (Mutex lock = curatorDb.lockNotifications(source.tenant())) {
var existingNotifications = curatorDb.readNotifications(source.tenant());
List<Notification> notifications = existingNotifications.stream()
.filter(notification -> !source.equals(notification.source()) || type != notification.type())
.collect(Collectors.toCollection(ArrayList::new));
- var notification = new Notification(clock.instant(), type, level, source, messages);
+ var notification = new Notification(clock.instant(), type, level, source, title, messages, mailContent);
if (!notificationExists(notification, existingNotifications, false)) {
changed = Optional.of(notification);
}
notifications.add(notification);
curatorDb.writeNotifications(source.tenant(), notifications);
}
- changed.ifPresent(notifier::dispatch);
+ changed.ifPresent(c -> {
+ log.fine(() -> "New notification %s".formatted(c));
+ notifier.dispatch(c);
+ });
}
/** Remove the notification with the given source and type */
@@ -122,14 +172,9 @@ public class NotificationsDb {
Instant now = clock.instant();
List<Notification> changed = List.of();
List<Notification> newNotifications = Stream.concat(
- clusterMetrics.stream().map(metric -> {
- NotificationSource source = NotificationSource.from(deploymentId, ClusterSpec.Id.from(metric.getClusterId()));
- return createFeedBlockNotification(source, now, metric);
- }),
- applicationReindexing.clusters().entrySet().stream().map(entry -> {
- NotificationSource source = NotificationSource.from(deploymentId, ClusterSpec.Id.from(entry.getKey()));
- return createReindexNotification(source, now, entry.getValue());
- }))
+ clusterMetrics.stream().map(metric -> createFeedBlockNotification(consoleUrls, deploymentId, metric.getClusterId(), now, metric)),
+ applicationReindexing.clusters().entrySet().stream().map(entry ->
+ createReindexNotification(consoleUrls, deploymentId, entry.getKey(), now, entry.getValue())))
.flatMap(Optional::stream)
.toList();
@@ -156,30 +201,41 @@ public class NotificationsDb {
private boolean notificationExists(Notification notification, List<Notification> existing, boolean mindHigherLevel) {
// Be conservative for now, only dispatch notifications if they are from new source or with new type.
// the message content and level is ignored for now
- return existing.stream().anyMatch(e ->
- notification.source().contains(e.source()) && notification.type().equals(e.type()) &&
+ boolean exists = existing.stream()
+ .anyMatch(e -> notification.source().contains(e.source()) && notification.type().equals(e.type()) &&
(!mindHigherLevel || notification.level().ordinal() <= e.level().ordinal()));
+ log.fine(() -> "%s in %s == %b".formatted(notification, existing, exists));
+ return exists;
}
- private static Optional<Notification> createFeedBlockNotification(NotificationSource source, Instant at, ClusterMetrics metric) {
+ private static Optional<Notification> createFeedBlockNotification(ConsoleUrls consoleUrls, DeploymentId deployment, String clusterId, Instant at, ClusterMetrics metric) {
Optional<Pair<Level, String>> memoryStatus =
resourceUtilToFeedBlockStatus("memory", metric.memoryUtil(), metric.memoryFeedBlockLimit());
Optional<Pair<Level, String>> diskStatus =
resourceUtilToFeedBlockStatus("disk", metric.diskUtil(), metric.diskFeedBlockLimit());
if (memoryStatus.isEmpty() && diskStatus.isEmpty()) return Optional.empty();
+ NotificationSource source = NotificationSource.from(deployment, ClusterSpec.Id.from(clusterId));
// Find the max among levels
Level level = Stream.of(memoryStatus, diskStatus)
.flatMap(status -> status.stream().map(Pair::getFirst))
.max(Comparator.comparing(Enum::ordinal)).get();
+ String title = "Cluster [%s](%s) in **%s** for **%s.%s** is %sfeed blocked".formatted(
+ clusterId, notificationLink(consoleUrls, source), deployment.zoneId().value(), deployment.applicationId().application().value(),
+ deployment.applicationId().instance().value(), level == Level.warning ? "nearly " : "");
List<String> messages = Stream.concat(memoryStatus.stream(), diskStatus.stream())
.filter(status -> status.getFirst() == level) // Do not mix message from different levels
.map(Pair::getSecond)
.toList();
- return Optional.of(new Notification(at, Type.feedBlock, level, source, messages));
+
+ return Optional.of(new Notification(at, Type.feedBlock, level, source, title, messages));
}
- private static Optional<Notification> createReindexNotification(NotificationSource source, Instant at, Cluster cluster) {
+ private static Optional<Notification> createReindexNotification(ConsoleUrls consoleUrls, DeploymentId deployment, String clusterId, Instant at, Cluster cluster) {
+ NotificationSource source = NotificationSource.from(deployment, ClusterSpec.Id.from(clusterId));
+ String title = "Cluster [%s](%s) in **%s** for **%s.%s** is [reindexing](https://docs.vespa.ai/en/operations/reindexing.html)".formatted(
+ clusterId, consoleUrls.clusterReindexing(deployment.applicationId(), deployment.zoneId(), source.clusterId().get()),
+ deployment.zoneId().value(), deployment.applicationId().application().value(), deployment.applicationId().instance().value());
List<String> messages = cluster.ready().entrySet().stream()
.filter(entry -> entry.getValue().progress().isPresent())
.map(entry -> Text.format("document type '%s'%s (%.1f%% done)",
@@ -187,7 +243,7 @@ public class NotificationsDb {
.sorted()
.toList();
if (messages.isEmpty()) return Optional.empty();
- return Optional.of(new Notification(at, Type.reindex, Level.info, source, messages));
+ return Optional.of(new Notification(at, Type.reindex, Level.info, source, title, messages));
}
/**
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 82dc333d178..f27e69c4636 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,24 +1,23 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.ApplicationId;
import com.yahoo.config.provision.Environment;
-import com.yahoo.config.provision.TenantName;
-import com.yahoo.restapi.UriBuilder;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.text.Text;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagSource;
-import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.PermanentFlags;
+import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Mail;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer;
import com.yahoo.vespa.hosted.controller.api.integration.organization.MailerException;
-import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.TenantContacts;
-import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -27,8 +26,6 @@ import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import static com.yahoo.yolean.Exceptions.uncheck;
-
/**
* Notifier is responsible for dispatching user notifications to their chosen Contact points.
*
@@ -38,20 +35,22 @@ public class Notifier {
private final CuratorDb curatorDb;
private final Mailer mailer;
private final FlagSource flagSource;
+ private final ConsoleUrls consoleUrls;
private final NotificationFormatter formatter;
- private final URI dashboardUri;
+ private final MailTemplating mailTemplating;
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) {
+ public Notifier(CuratorDb curatorDb, ConsoleUrls consoleUrls, Mailer mailer, FlagSource flagSource) {
this.curatorDb = Objects.requireNonNull(curatorDb);
this.mailer = Objects.requireNonNull(mailer);
this.flagSource = Objects.requireNonNull(flagSource);
- this.formatter = new NotificationFormatter(zoneRegistry);
- this.dashboardUri = zoneRegistry.dashboardUrl();
+ this.consoleUrls = Objects.requireNonNull(consoleUrls);
+ this.formatter = new NotificationFormatter(consoleUrls);
+ this.mailTemplating = new MailTemplating(consoleUrls);
}
public void dispatch(List<Notification> notifications, NotificationSource source) {
@@ -99,11 +98,16 @@ public class Notifier {
private void dispatch(Notification notification, Collection<TenantContacts.EmailContact> contacts) {
try {
+ log.fine(() -> "Sending notification " + notification + " to " +
+ contacts.stream().map(c -> c.email().getEmailAddress()).toList());
var content = formatter.format(notification);
- mailer.send(mailOf(content, contacts.stream()
- .filter(c -> c.email().isVerified())
- .map(c -> c.email().getEmailAddress())
- .toList()));
+ var verifiedContacts = contacts.stream()
+ .filter(c -> c.email().isVerified()).map(c -> c.email().getEmailAddress()).toList();
+ if (verifiedContacts.isEmpty()) {
+ log.fine(() -> "None of the %d contact(s) are verified - skipping delivery of %s".formatted(contacts.size(), notification));
+ return;
+ }
+ mailer.send(mailOf(content, verifiedContacts));
} catch (MailerException e) {
log.log(Level.SEVERE, "Failed sending email", e);
} catch (MissingOptionalException e) {
@@ -113,23 +117,30 @@ public class Notifier {
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 template = uncheck(() -> Notifier.class.getResourceAsStream("/mail/mail-notification.tmpl").readAllBytes());
- var html = new String(template)
- .replace("[[NOTIFICATION_HEADER]]", content.messagePrefix())
- .replace("[[NOTIFICATION_ITEMS]]", notification.messages().stream()
- .map(Notifier::linkify)
- .map(Notifier::capitalise)
- .map(m -> "<p>" + m + "</p>")
- .collect(Collectors.joining()))
- .replace("[[LINK_TO_NOTIFICATION]]", notificationLink(notification.source()))
- .replace("[[LINK_TO_ACCOUNT_NOTIFICATIONS]]", accountNotificationsUri(content.notification().source().tenant()))
- .replace("[[LINK_TO_PRIVACY_POLICY]]", "https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html")
- .replace("[[LINK_TO_TERMS_OF_SERVICE]]", consoleUri("terms-of-service-trial.html"))
- .replace("[[LINK_TO_SUPPORT]]", consoleUri("support"));
+ var subject = content.notification().mailContent().flatMap(Notification.MailContent::subject)
+ .orElseGet(() -> Text.format(
+ "[%s] %s Vespa Notification for %s", notification.level().toString().toUpperCase(),
+ content.prettyType(), applicationIdSource(notification.source())));
+ var html = generateHtml(content);
return new Mail(recipients, subject, "", html);
}
+ private String generateHtml(FormattedNotification content) {
+ var mailContent = content.notification().mailContent().orElseGet(() -> generateContentFromMessages(content));
+ return mailTemplating.generateDefaultMailHtml(mailContent.template(), mailContent.values(), content.notification().source().tenant());
+ }
+
+ private Notification.MailContent generateContentFromMessages(FormattedNotification f) {
+ var items = f.notification().messages().stream().map(m -> capitalise(linkify(mailTemplating.escapeHtml(m)))).toList();
+ return Notification.MailContent.fromTemplate(MailTemplating.Template.DEFAULT_MAIL_CONTENT)
+ .with("mailMessageTemplate", "notification-message")
+ .with("mailTitle", "Vespa Cloud Notifications")
+ .with("notificationHeader", f.messagePrefix())
+ .with("notificationItems", items)
+ .with("consoleLink", notificationLink(consoleUrls, f.notification().source()))
+ .build();
+ }
+
@VisibleForTesting
static String linkify(String text) {
return urlPattern.matcher(text).replaceAll((res) -> String.format("<a href=\"%s\">%s</a>", res.group(), res.group()));
@@ -143,36 +154,16 @@ public class Notifier {
return sb.toString();
}
- private String accountNotificationsUri(TenantName tenant) {
- return new UriBuilder(dashboardUri)
- .append("tenant/")
- .append(tenant.value())
- .append("account/notifications")
- .toString();
- }
+ static String notificationLink(ConsoleUrls consoleUrls, NotificationSource source) {
+ if (source.application().isEmpty()) return consoleUrls.tenantOverview(source.tenant());
+ if (source.instance().isEmpty()) return consoleUrls.prodApplicationOverview(source.tenant(), source.application().get());
- private String consoleUri(String path) {
- return new UriBuilder(dashboardUri).append(path).toString();
- }
-
- private String notificationLink(NotificationSource source) {
- var uri = new UriBuilder(dashboardUri);
- uri = uri.append("tenant").append(source.tenant().value());
- if (source.application().isPresent())
- uri = uri.append("application").append(source.application().get().value());
- if (source.isProduction()) {
- uri = uri.append("prod/instance");
- if (source.jobType().isPresent()) {
- uri = uri.append(source.instance().get().value());
- }
- }
- else {
- uri = uri.append("dev/instance/").append(source.instance().get().value());
- }
- if (source.jobType().isPresent()) {
- uri = uri.append("job").append(source.jobType().get().jobName()).append("run").append(String.valueOf(source.runNumber().getAsLong()));
- }
- return uri.toString();
+ ApplicationId application = ApplicationId.from(source.tenant(), source.application().get(), source.instance().get());
+ if (source.jobType().isPresent())
+ return consoleUrls.deploymentRun(new RunId(application, source.jobType().get(), source.runNumber().getAsLong()));
+ if (source.clusterId().isPresent())
+ return consoleUrls.clusterOverview(application, source.zoneId().get(), source.clusterId().get());
+ return consoleUrls.instanceOverview(application, source.zoneId().map(ZoneId::environment).orElse(Environment.prod));
}
private static String capitalise(String m) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/package-info.java
index 26e6a6b89e1..22d10386d7f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* The root package of the controller
*
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
index e6b3dd74abc..07fac67100f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
@@ -19,6 +19,7 @@ import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
@@ -55,6 +56,9 @@ import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.toMap;
/**
* Serializes {@link Application}s to/from slime.
@@ -134,6 +138,9 @@ public class ApplicationSerializer {
private static final String lastWrittenField = "lastWritten";
private static final String lastQueriesPerSecondField = "lastQueriesPerSecond";
private static final String lastWritesPerSecondField = "lastWritesPerSecond";
+ private static final String dataPlaneTokensField = "dataPlaneTokens";
+ private static final String tokenIdField = "id";
+ private static final String tokenUpdatedField = "updated";
// DeploymentJobs fields
private static final String jobStatusField = "jobStatus";
@@ -221,6 +228,12 @@ public class ApplicationSerializer {
deployment.activity().lastWritesPerSecond().ifPresent(value -> object.setDouble(lastWritesPerSecondField, value));
object.setDouble(quotaUsageRateField, deployment.quota().rate());
deployment.cost().ifPresent(cost -> object.setDouble(deploymentCostField, cost));
+ Cursor dataPlaneTokensArray = object.setArray(dataPlaneTokensField);
+ deployment.dataPlaneTokens().forEach((id, updated) -> {
+ Cursor tokenObject = dataPlaneTokensArray.addObject();
+ tokenObject.setString(tokenIdField, id.value());
+ tokenObject.setLong(tokenUpdatedField, updated.toEpochMilli());
+ });
}
private void deploymentMetricsToSlime(DeploymentMetrics metrics, Cursor object) {
@@ -433,7 +446,10 @@ public class ApplicationSerializer {
SlimeUtils.optionalDouble(deploymentObject.field(lastQueriesPerSecondField)),
SlimeUtils.optionalDouble(deploymentObject.field(lastWritesPerSecondField))),
QuotaUsage.create(SlimeUtils.optionalDouble(deploymentObject.field(quotaUsageRateField))),
- SlimeUtils.optionalDouble(deploymentObject.field(deploymentCostField)));
+ SlimeUtils.optionalDouble(deploymentObject.field(deploymentCostField)),
+ SlimeUtils.entriesStream(deploymentObject.field(dataPlaneTokensField))
+ .collect(toMap(entry -> TokenId.of(entry.field(tokenIdField).asString()),
+ entry -> Instant.ofEpochMilli(entry.field(tokenUpdatedField).asLong()))));
}
private DeploymentMetrics deploymentMetricsFromSlime(Inspector object) {
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 f40193510ce..40a3e35cb25 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.CloudAccount;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializer.java
index 92be728afc8..92766ed4506 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.slime.ArrayTraverser;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStore.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStore.java
index 21255ae83bf..9e202ea30f2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStore.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStore.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CertifiedOsVersionSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CertifiedOsVersionSerializer.java
index 8fd696cffc4..f3b3cb0a1bf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CertifiedOsVersionSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CertifiedOsVersionSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializer.java
index ba11ad1756f..f43be77b82c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.zone.ZoneId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ConfidenceOverrideSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ConfidenceOverrideSerializer.java
index f9306103e71..91e12b9cb15 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ConfidenceOverrideSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ConfidenceOverrideSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerVersionSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerVersionSerializer.java
index 1ec349b7dab..f19d7f68b3d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerVersionSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ControllerVersionSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
index dc9c4650191..cef62438a53 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/CuratorDb.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.collections.Pair;
@@ -6,7 +6,6 @@ import com.yahoo.component.Version;
import com.yahoo.component.annotation.Inject;
import com.yahoo.concurrent.UncheckedTimeoutException;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.ClusterSpec.Id;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.InstanceName;
@@ -112,6 +111,7 @@ public class CuratorDb {
private static final Path mailVerificationRoot = root.append("mailVerification");
private static final Path dataPlaneTokenRoot = root.append("dataplaneTokens");
private static final Path certificatePoolRoot = root.append("certificatePool");
+ private static final Path trialNotificationsRoot = root.append("trialNotifications");
private final NodeVersionSerializer nodeVersionSerializer = new NodeVersionSerializer();
private final VersionStatusSerializer versionStatusSerializer = new VersionStatusSerializer(nodeVersionSerializer);
@@ -643,6 +643,10 @@ public class CuratorDb {
curator.delete(endpointCertificatePath(application, instanceName));
}
+ public void removeAssignedCertificate(TenantAndApplicationId application, Optional<InstanceName> instanceName, NestedTransaction transaction) {
+ transaction.add(CuratorTransaction.from(CuratorOperations.delete(endpointCertificatePath(application, instanceName).getAbsolute()), curator));
+ }
+
// TODO(mpolden): Remove this. Caller should make an explicit decision to read certificate for a particular instance
public Optional<AssignedCertificate> readAssignedCertificate(ApplicationId applicationId) {
return readAssignedCertificate(TenantAndApplicationId.from(applicationId), Optional.of(applicationId.instance()));
@@ -651,7 +655,7 @@ public class CuratorDb {
public Optional<AssignedCertificate> readAssignedCertificate(TenantAndApplicationId application, Optional<InstanceName> instance) {
return readSlime(endpointCertificatePath(application, instance)).map(Slime::get)
.map(EndpointCertificateSerializer::fromSlime)
- .map(cert -> new AssignedCertificate(application, instance, cert));
+ .map(cert -> new AssignedCertificate(application, instance, cert, false));
}
public List<AssignedCertificate> readAssignedCertificates() {
@@ -813,6 +817,16 @@ public class CuratorDb {
return curator.getChildren(certificatePoolRoot).stream().flatMap(id -> readUnassignedCertificate(id).stream()).toList();
}
+ // -------------- Cloud trial notification --------------------------------
+
+ public void writeTrialNotifications(TrialNotifications tn) {
+ curator.set(trialNotificationsRoot, asJson(tn.toSlime()));
+ }
+
+ public Optional<TrialNotifications> readTrialNotifications() {
+ return readSlime(trialNotificationsRoot).map(TrialNotifications::fromSlime);
+ }
+
// -------------- Paths ---------------------------------------------------
private static Path upgradesPerMinutePath() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DataplaneTokenSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DataplaneTokenSerializer.java
index fbdab67869a..6537bde467a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DataplaneTokenSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DataplaneTokenSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.slime.Cursor;
@@ -25,6 +25,7 @@ public class DataplaneTokenSerializer {
private static final String creationTimeField = "creationTime";
private static final String authorField = "author";
private static final String expirationField = "expiration";
+ private static final String lastUpdatedField = "lastUpdated";
public static Slime toSlime(List<DataplaneTokenVersions> dataplaneTokenVersions) {
Slime slime = new Slime();
@@ -33,6 +34,7 @@ public class DataplaneTokenSerializer {
dataplaneTokenVersions.forEach(tokenMetadata -> {
Cursor tokenCursor = array.addObject();
tokenCursor.setString(idField, tokenMetadata.tokenId().value());
+ tokenCursor.setLong(lastUpdatedField, tokenMetadata.lastUpdated().toEpochMilli());
Cursor versionArray = tokenCursor.setArray(tokenVersionsField);
tokenMetadata.tokenVersions().forEach(version -> {
Cursor versionCursor = versionArray.addObject();
@@ -65,7 +67,7 @@ public class DataplaneTokenSerializer {
return new DataplaneTokenVersions.Version(fingerPrint, checkAccessHash, creationTime, expiration, author);
})
.toList();
- return new DataplaneTokenVersions(id, versions);
+ return new DataplaneTokenVersions(id, versions, Instant.ofEpochMilli(entry.field(lastUpdatedField).asLong()));
})
.toList();
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DnsChallengeSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DnsChallengeSerializer.java
index bb3b2c5035f..4991d03d7df 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DnsChallengeSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/DnsChallengeSerializer.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.CloudAccount;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializer.java
index fae9ea1e0e3..b204e2fe328 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.slime.Cursor;
@@ -35,7 +35,7 @@ public class EndpointCertificateSerializer {
private final static String issuerField = "issuer";
private final static String expiryField = "expiry";
private final static String lastRefreshedField = "lastRefreshed";
- private final static String randomizedIdField = "randomizedId";
+ private final static String generatedIdField = "randomizedId";
public static Slime toSlime(EndpointCertificate cert) {
Slime slime = new Slime();
@@ -56,7 +56,7 @@ public class EndpointCertificateSerializer {
object.setString(issuerField, cert.issuer());
cert.expiry().ifPresent(expiry -> object.setLong(expiryField, expiry));
cert.lastRefreshed().ifPresent(refreshTime -> object.setLong(lastRefreshedField, refreshTime));
- cert.randomizedId().ifPresent(randomizedId -> object.setString(randomizedIdField, randomizedId));
+ cert.generatedId().ifPresent(id -> object.setString(generatedIdField, id));
}
public static EndpointCertificate fromSlime(Inspector inspector) {
@@ -79,8 +79,8 @@ public class EndpointCertificateSerializer {
inspector.field(lastRefreshedField).valid() ?
Optional.of(inspector.field(lastRefreshedField).asLong()) :
Optional.empty(),
- inspector.field(randomizedIdField).valid() ?
- Optional.of(inspector.field(randomizedIdField).asString()) :
+ inspector.field(generatedIdField).valid() ?
+ Optional.of(inspector.field(generatedIdField).asString()) :
Optional.empty());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/JobControlFlags.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/JobControlFlags.java
index 41bf85f021b..f699133ca53 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/JobControlFlags.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/JobControlFlags.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.concurrent.maintenance.JobControlState;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializer.java
index 9f648675cd0..69fe9bb8fa1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.slime.ArrayTraverser;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MailVerificationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MailVerificationSerializer.java
index e5ee695e4e8..44325853c15 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MailVerificationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MailVerificationSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MockCuratorDb.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MockCuratorDb.java
index 3ec639a5529..6ad77af08e2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MockCuratorDb.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/MockCuratorDb.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.cloud.config.ConfigserverConfig;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java
index d02d27b5293..4192f19298f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NodeVersionSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NodeVersionSerializer.java
index 0f1f531d589..1ac8aad74ba 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NodeVersionSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NodeVersionSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializer.java
index 3d28f35fc26..d5be4d22dc2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.ApplicationName;
@@ -8,13 +8,16 @@ import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
+import com.yahoo.slime.ObjectTraverser;
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.notification.MailTemplating;
import com.yahoo.vespa.hosted.controller.notification.Notification;
import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
import java.util.List;
+import java.util.Optional;
/**
* (de)serializes notifications for a tenant
@@ -34,6 +37,7 @@ public class NotificationsSerializer {
private static final String atFieldName = "at";
private static final String typeField = "type";
private static final String levelField = "level";
+ private static final String titleField = "title";
private static final String messagesField = "messages";
private static final String applicationField = "application";
private static final String instanceField = "instance";
@@ -51,6 +55,7 @@ public class NotificationsSerializer {
notificationObject.setLong(atFieldName, notification.at().toEpochMilli());
notificationObject.setString(typeField, asString(notification.type()));
notificationObject.setString(levelField, asString(notification.level()));
+ notificationObject.setString(titleField, notification.title());
Cursor messagesArray = notificationObject.setArray(messagesField);
notification.messages().forEach(messagesArray::addString);
@@ -60,6 +65,22 @@ public class NotificationsSerializer {
notification.source().clusterId().ifPresent(clusterId -> notificationObject.setString(clusterIdField, clusterId.value()));
notification.source().jobType().ifPresent(jobType -> notificationObject.setString(jobTypeField, jobType.serialized()));
notification.source().runNumber().ifPresent(runNumber -> notificationObject.setLong(runNumberField, runNumber));
+
+ notification.mailContent().ifPresent(mc -> {
+ notificationObject.setString("mail-template", mc.template().getId());
+ mc.subject().ifPresent(s -> notificationObject.setString("mail-subject", s));
+ var mailParamsCursor = notificationObject.setObject("mail-params");
+ mc.values().forEach((key, value) -> {
+ if (value instanceof String str) {
+ mailParamsCursor.setString(key, str);
+ } else if (value instanceof List<?> l) {
+ var array = mailParamsCursor.setArray(key);
+ l.forEach(elem -> array.addString((String) elem));
+ } else {
+ throw new ClassCastException("Unsupported param type: " + value.getClass());
+ }
+ });
+ });
}
return slime;
@@ -92,7 +113,24 @@ public class NotificationsSerializer {
SlimeUtils.optionalString(inspector.field(clusterIdField)).map(ClusterSpec.Id::from),
SlimeUtils.optionalString(inspector.field(jobTypeField)).map(jobName -> JobType.ofSerialized(jobName)),
SlimeUtils.optionalLong(inspector.field(runNumberField))),
- SlimeUtils.entriesStream(inspector.field(messagesField)).map(Inspector::asString).toList());
+ SlimeUtils.optionalString(inspector.field(titleField)).orElse(""),
+ SlimeUtils.entriesStream(inspector.field(messagesField)).map(Inspector::asString).toList(),
+ mailContentFrom(inspector));
+ }
+
+ private Optional<Notification.MailContent> mailContentFrom(final Inspector inspector) {
+ return SlimeUtils.optionalString(inspector.field("mail-template")).map(template -> {
+ var builder = Notification.MailContent.fromTemplate(MailTemplating.Template.fromId(template).orElseThrow());
+ SlimeUtils.optionalString(inspector.field("mail-subject")).ifPresent(builder::subject);
+ inspector.field("mail-params").traverse((ObjectTraverser) (name, insp) -> {
+ switch (insp.type()) {
+ case STRING -> builder.with(name, insp.asString());
+ case ARRAY -> builder.with(name, SlimeUtils.entriesStream(insp).map(Inspector::asString).toList());
+ default -> throw new IllegalArgumentException("Unsupported param type: " + insp.type());
+ }
+ });
+ return builder.build();
+ });
}
private static String asString(Notification.Type type) {
@@ -103,6 +141,7 @@ public class NotificationsSerializer {
case deployment -> "deployment";
case feedBlock -> "feedBlock";
case reindex -> "reindex";
+ case account -> "account";
};
}
@@ -114,6 +153,7 @@ public class NotificationsSerializer {
case "deployment" -> Notification.Type.deployment;
case "feedBlock" -> Notification.Type.feedBlock;
case "reindex" -> Notification.Type.reindex;
+ case "account" -> Notification.Type.account;
default -> throw new IllegalArgumentException("Unknown serialized notification type value '" + field.asString() + "'");
};
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializer.java
index 7551799ec85..173ebf151aa 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java
index a4278b76200..40826079efd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.google.common.collect.ImmutableMap;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionTargetSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionTargetSerializer.java
index a5e5d925865..968cea33162 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionTargetSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionTargetSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.slime.ArrayTraverser;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
index 08d603204b0..5e3f6675955 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import ai.vespa.http.DomainName;
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 b1ca6c63816..1d28432039b 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/SupportAccessSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/SupportAccessSerializer.java
index af7c03f8657..33f4709cfdd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/SupportAccessSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/SupportAccessSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.security.X509CertificateUtils;
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 760fb9b0366..eae8f86f289 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.google.common.collect.BiMap;
@@ -15,6 +15,7 @@ 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.billing.PlanId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.BillingInfo;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
@@ -27,6 +28,8 @@ import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
import com.yahoo.vespa.hosted.controller.tenant.Email;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
+import com.yahoo.vespa.hosted.controller.tenant.PurchaseOrder;
+import com.yahoo.vespa.hosted.controller.tenant.TaxId;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.tenant.TenantAddress;
import com.yahoo.vespa.hosted.controller.tenant.TenantBilling;
@@ -88,9 +91,13 @@ public class TenantSerializer {
private static final String invalidateUserSessionsBeforeField = "invalidateUserSessionsBefore";
private static final String tenantRolesLastMaintainedField = "tenantRolesLastMaintained";
private static final String billingReferenceField = "billingReference";
+ private static final String planIdField = "planId";
private static final String cloudAccountsField = "cloudAccounts";
private static final String accountField = "account";
private static final String templateVersionField = "templateVersion";
+ private static final String taxIdField = "taxId";
+ private static final String purchaseOrderField = "purchaseOrder";
+ private static final String invoiceEmailField = "invoiceEmail";
private static final String awsIdField = "awsId";
private static final String roleField = "role";
@@ -137,6 +144,7 @@ public class TenantSerializer {
toSlime(tenant.archiveAccess(), root);
tenant.billingReference().ifPresent(b -> toSlime(b, root));
tenant.invalidateUserSessionsBefore().ifPresent(instant -> root.setLong(invalidateUserSessionsBeforeField, instant.toEpochMilli()));
+ root.setString(planIdField, tenant.planId().value());
}
private void toSlime(ArchiveAccess archiveAccess, Cursor root) {
@@ -215,7 +223,10 @@ public class TenantSerializer {
Instant tenantRolesLastMaintained = SlimeUtils.instant(tenantObject.field(tenantRolesLastMaintainedField));
List<CloudAccountInfo> cloudAccountInfos = cloudAccountsFromSlime(tenantObject.field(cloudAccountsField));
Optional<BillingReference> billingReference = billingReferenceFrom(tenantObject.field(billingReferenceField));
- return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores, archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained, cloudAccountInfos, billingReference);
+ PlanId planId = planId(tenantObject.field(planIdField));
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info, tenantSecretStores,
+ archiveAccess, invalidateUserSessionsBefore, tenantRolesLastMaintained,
+ cloudAccountInfos, billingReference, planId);
}
private DeletedTenant deletedTenantFrom(Inspector tenantObject) {
@@ -250,6 +261,7 @@ public class TenantSerializer {
.withAWSRole(awsArchiveAccessRole)
.withGCPMember(gcpArchiveAccessMember);
}
+
TenantInfo tenantInfoFromSlime(Inspector infoObject) {
if (!infoObject.valid()) return TenantInfo.empty();
@@ -275,12 +287,19 @@ public class TenantSerializer {
}
private TenantBilling tenantInfoBillingContactFromSlime(Inspector billingObject) {
+ var taxId = new TaxId(billingObject.field(taxIdField).asString());
+ var purchaseOrder = new PurchaseOrder(billingObject.field(purchaseOrderField).asString());
+ var invoiceEmail = new Email(billingObject.field(invoiceEmailField).asString(), false);
+
return TenantBilling.empty()
.withContact(TenantContact.from(
billingObject.field("name").asString(),
- new Email(billingObject.field("email").asString(), true),
+ new Email(billingObject.field("email").asString(), billingObject.field("emailVerified").asBool()),
billingObject.field("phone").asString()))
- .withAddress(tenantInfoAddressFromSlime(billingObject.field("address")));
+ .withAddress(tenantInfoAddressFromSlime(billingObject.field("address")))
+ .withTaxId(taxId)
+ .withPurchaseOrder(purchaseOrder)
+ .withInvoiceEmail(invoiceEmail);
}
private List<TenantSecretStore> secretStoresFromSlime(Inspector secretStoresObject) {
@@ -337,11 +356,15 @@ public class TenantSerializer {
private void toSlime(TenantBilling billingContact, Cursor parentCursor) {
if (billingContact.isEmpty()) return;
- Cursor addressCursor = parentCursor.setObject("billingContact");
- addressCursor.setString("name", billingContact.contact().name());
- addressCursor.setString("email", billingContact.contact().email().getEmailAddress());
- addressCursor.setString("phone", billingContact.contact().phone());
- toSlime(billingContact.address(), addressCursor);
+ Cursor billingCursor = parentCursor.setObject("billingContact");
+ billingCursor.setString("name", billingContact.contact().name());
+ billingCursor.setString("email", billingContact.contact().email().getEmailAddress());
+ billingCursor.setBool("emailVerified", billingContact.contact().email().isVerified());
+ billingCursor.setString("phone", billingContact.contact().phone());
+ billingCursor.setString(taxIdField, billingContact.getTaxId().value());
+ billingCursor.setString(purchaseOrderField, billingContact.getPurchaseOrder().value());
+ billingCursor.setString(invoiceEmailField, billingContact.getInvoiceEmail().getEmailAddress());
+ toSlime(billingContact.address(), billingCursor);
}
private void toSlime(List<TenantSecretStore> tenantSecretStores, Cursor parentCursor) {
@@ -375,6 +398,12 @@ public class TenantSerializer {
SlimeUtils.instant(object.field("updated"))));
}
+ private PlanId planId(Inspector object) {
+ if (! object.valid()) return PlanId.from("none");
+
+ return PlanId.from(object.asString());
+ }
+
private TenantContacts tenantContactsFrom(Inspector object) {
List<TenantContacts.Contact> contacts = SlimeUtils.entriesStream(object)
.map(this::readContact)
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TrialNotifications.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TrialNotifications.java
new file mode 100644
index 00000000000..a205e6c4173
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TrialNotifications.java
@@ -0,0 +1,57 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.vespa.hosted.controller.persistence;
+
+import com.yahoo.config.provision.TenantName;
+import com.yahoo.slime.Slime;
+import com.yahoo.slime.SlimeUtils;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * @author bjorncs
+ */
+public record TrialNotifications(List<Status> tenants) {
+ private static final Logger log = Logger.getLogger(TrialNotifications.class.getName());
+
+ public TrialNotifications { tenants = List.copyOf(tenants); }
+
+ public record Status(TenantName tenant, State state, Instant lastUpdate) {}
+ public enum State { SIGNED_UP, MID_CHECK_IN, EXPIRES_SOON, EXPIRES_IMMEDIATELY, EXPIRED, UNKNOWN }
+
+ public Slime toSlime() {
+ var slime = new Slime();
+ var rootCursor = slime.setObject();
+ var tenantsCursor = rootCursor.setArray("tenants");
+ for (Status t : tenants) {
+ var tenantCursor = tenantsCursor.addObject();
+ tenantCursor.setString("tenant", t.tenant().value());
+ tenantCursor.setString("state", t.state().name());
+ tenantCursor.setString("lastUpdate", t.lastUpdate().toString());
+ }
+ log.fine(() -> "Generated json '%s' from '%s'".formatted(SlimeUtils.toJson(slime), this));
+ return slime;
+ }
+
+ public static TrialNotifications fromSlime(Slime slime) {
+ var rootCursor = slime.get();
+ var tenantsCursor = rootCursor.field("tenants");
+ var tenants = new ArrayList<Status>();
+ for (int i = 0; i < tenantsCursor.entries(); i++) {
+ var tenantCursor = tenantsCursor.entry(i);
+ var name = TenantName.from(tenantCursor.field("tenant").asString());
+ var stateStr = tenantCursor.field("state").asString();
+ var state = Arrays.stream(State.values())
+ .filter(s -> s.name().equals(stateStr)).findFirst().orElse(State.UNKNOWN);
+ var lastUpdate = Instant.parse(tenantCursor.field("lastUpdate").asString());
+ tenants.add(new Status(name, state, lastUpdate));
+ }
+ var tn = new TrialNotifications(tenants);
+ log.fine(() -> "Parsed '%s' from '%s'".formatted(tn, SlimeUtils.toJson(slime)));
+ return tn;
+ }
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/UnassignedCertificateSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/UnassignedCertificateSerializer.java
index 2f8a0ea585c..44f50800561 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/UnassignedCertificateSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/UnassignedCertificateSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.slime.Cursor;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java
index 9643de52c29..e4de073e2c6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializer.java
index d6342bc355f..97b0e340025 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.zone.ZoneId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/package-info.java
index b9c6290f582..abb8ab08d89 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* Persistence layer for the controller.
*
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutor.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutor.java
index c02cfc2ce65..e623b7e440c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutor.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutor.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.proxy;
import com.yahoo.container.jdisc.HttpResponse;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
index c4f0800a8f1..ba8dddc889a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImpl.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.proxy;
import ai.vespa.util.http.hc4.SslConnectionSocketFactory;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
index fdd93eedbff..2a29e2b590d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.proxy;
import ai.vespa.http.HttpURL;
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 c57133d8efd..caf2ff05814 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.proxy;
import ai.vespa.http.HttpURL;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/package-info.java
index ccc874f8f7a..0acb064f52a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/proxy/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* @author Haakon Dybdahl
*/
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/ErrorResponses.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/ErrorResponses.java
index 4c4633df0ec..56844887caf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/ErrorResponses.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/ErrorResponses.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi;
import com.yahoo.container.jdisc.HttpRequest;
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 16d862a66ef..5548928b9d0 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import ai.vespa.hosted.api.Signatures;
@@ -74,7 +74,6 @@ 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 com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.DataplaneToken;
-import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.DataplaneTokenVersions;
import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.FingerPrint;
import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
@@ -112,6 +111,7 @@ import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
import com.yahoo.vespa.hosted.controller.persistence.SupportAccessSerializer;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponses;
import com.yahoo.vespa.hosted.controller.restapi.dataplanetoken.DataplaneTokenService;
+import com.yahoo.vespa.hosted.controller.restapi.dataplanetoken.DataplaneTokenService.State;
import com.yahoo.vespa.hosted.controller.routing.RoutingStatus;
import com.yahoo.vespa.hosted.controller.routing.context.DeploymentRoutingContext;
import com.yahoo.vespa.hosted.controller.routing.rotation.RotationId;
@@ -127,6 +127,8 @@ import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
import com.yahoo.vespa.hosted.controller.tenant.Email;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.PendingMailVerification;
+import com.yahoo.vespa.hosted.controller.tenant.PurchaseOrder;
+import com.yahoo.vespa.hosted.controller.tenant.TaxId;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.tenant.TenantAddress;
import com.yahoo.vespa.hosted.controller.tenant.TenantBilling;
@@ -692,7 +694,11 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
var contact = root.setObject("contact");
contact.setString("name", billingContact.contact().name());
contact.setString("email", billingContact.contact().email().getEmailAddress());
+ contact.setBool("emailVerified", billingContact.contact().email().isVerified());
contact.setString("phone", billingContact.contact().phone());
+ root.setString("taxId", billingContact.getTaxId().value());
+ root.setString("purchaseOrder", billingContact.getPurchaseOrder().value());
+ root.setString("invoiceEmail", billingContact.getInvoiceEmail().getEmailAddress());
toSlime(billingContact.address(), root); // will create "address" on the parent
}
@@ -702,15 +708,22 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
private SlimeJsonResponse putTenantInfoBilling(CloudTenant cloudTenant, Inspector inspector) {
var info = cloudTenant.info();
- var contact = info.billingContact().contact();
- var address = info.billingContact().address();
+ var billing = info.billingContact();
+ var contact = billing.contact();
+ var address = billing.address();
- var mergedContact = updateTenantInfoContact(inspector.field("contact"), cloudTenant.name(), contact, false);
- var mergedAddress = updateTenantInfoAddress(inspector.field("address"), info.billingContact().address());
+ var mergedContact = updateBillingContact(inspector.field("contact"), cloudTenant.name(), contact);
+ var mergedAddress = updateTenantInfoAddress(inspector.field("address"), billing.address());
+ var mergedTaxId = optional("taxId", inspector).map(TaxId::new).orElse(billing.getTaxId());
+ var mergedPurchaseOrder = optional("purchaseOrder", inspector).map(PurchaseOrder::new).orElse(billing.getPurchaseOrder());
+ var mergedInvoiceEmail = optional("invoiceEmail", inspector).map(mail -> new Email(mail, false)).orElse(billing.getInvoiceEmail());
var mergedBilling = info.billingContact()
.withContact(mergedContact)
- .withAddress(mergedAddress);
+ .withAddress(mergedAddress)
+ .withTaxId(mergedTaxId)
+ .withPurchaseOrder(mergedPurchaseOrder)
+ .withInvoiceEmail(mergedInvoiceEmail);
var mergedInfo = info.withBilling(mergedBilling);
@@ -763,6 +776,11 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
throw new IllegalArgumentException("'website' needs to be a valid address");
}
}
+ if (! mergedInfo.billingContact().getInvoiceEmail().isBlank()) {
+ // TODO: Validate invoice email is set if collection method is INVOICE
+ if (! mergedInfo.billingContact().getInvoiceEmail().getEmailAddress().contains("@"))
+ throw new IllegalArgumentException("'Invoice email' needs to be an email address");
+ }
}
private void toSlime(TenantAddress address, Cursor parentCursor) {
@@ -779,11 +797,15 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
private void toSlime(TenantBilling billingContact, Cursor parentCursor) {
if (billingContact.isEmpty()) return;
- Cursor addressCursor = parentCursor.setObject("billingContact");
- addressCursor.setString("name", billingContact.contact().name());
- addressCursor.setString("email", billingContact.contact().email().getEmailAddress());
- addressCursor.setString("phone", billingContact.contact().phone());
- toSlime(billingContact.address(), addressCursor);
+ Cursor billingCursor = parentCursor.setObject("billingContact");
+ billingCursor.setString("name", billingContact.contact().name());
+ billingCursor.setString("email", billingContact.contact().email().getEmailAddress());
+ billingCursor.setBool("emailVerified", billingContact.contact().email().isVerified());
+ billingCursor.setString("phone", billingContact.contact().phone());
+ billingCursor.setString("taxId", billingContact.getTaxId().value());
+ billingCursor.setString("purchaseOrder", billingContact.getPurchaseOrder().value());
+ billingCursor.setString("invoiceEmail", billingContact.getInvoiceEmail().getEmailAddress());
+ toSlime(billingContact.address(), billingCursor);
}
private void toSlime(TenantContacts contacts, Cursor parentCursor) {
@@ -892,15 +914,13 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
throw new IllegalArgumentException("All address fields must be set");
}
- private TenantContact updateTenantInfoContact(Inspector insp, TenantName tenantName, TenantContact oldContact, boolean isBillingContact) {
+ private TenantContact updateBillingContact(Inspector insp, TenantName tenantName, TenantContact oldContact) {
if (!insp.valid()) return oldContact;
var mergedEmail = optional("email", insp)
.filter(address -> !address.equals(oldContact.email().getEmailAddress()))
.map(address -> {
- if (isBillingContact)
- return new Email(address, true);
- controller.mailVerifier().sendMailVerification(tenantName, address, PendingMailVerification.MailType.TENANT_CONTACT);
+ controller.mailVerifier().sendMailVerification(tenantName, address, PendingMailVerification.MailType.BILLING);
return new Email(address, false);
})
.orElse(oldContact.email());
@@ -914,9 +934,15 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
private TenantBilling updateTenantInfoBillingContact(Inspector insp, TenantName tenantName, TenantBilling oldContact) {
if (!insp.valid()) return oldContact;
+ var taxId = optional("taxId", insp).map(TaxId::new).orElse(oldContact.getTaxId());
+ var purchaseOrder = optional("purchaseOrder", insp).map(PurchaseOrder::new).orElse(oldContact.getPurchaseOrder());
+ var invoiceEmail = optional("invoiceEmail", insp).map(mail -> new Email(mail, false)).orElse(oldContact.getInvoiceEmail());
return TenantBilling.empty()
- .withContact(updateTenantInfoContact(insp, tenantName, oldContact.contact(), true))
- .withAddress(updateTenantInfoAddress(insp.field("address"), oldContact.address()));
+ .withContact(updateBillingContact(insp, tenantName, oldContact.contact()))
+ .withAddress(updateTenantInfoAddress(insp.field("address"), oldContact.address()))
+ .withTaxId(taxId)
+ .withPurchaseOrder(purchaseOrder)
+ .withInvoiceEmail(invoiceEmail);
}
private TenantContacts updateTenantInfoContacts(Inspector insp, TenantName tenantName, TenantContacts oldContacts) {
@@ -964,27 +990,43 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
private HttpResponse listTokens(String tenant, HttpRequest request) {
- var tokens = controller.dataplaneTokenService().listTokens(TenantName.from(tenant))
- .stream().sorted(Comparator.comparing(DataplaneTokenVersions::tokenId)).toList();
Slime slime = new Slime();
Cursor tokensArray = slime.setObject().setArray("tokens");
- for (DataplaneTokenVersions token : tokens) {
+ controller.dataplaneTokenService().listTokensWithState(TenantName.from(tenant)).forEach((token, states) -> {
Cursor tokenObject = tokensArray.addObject();
tokenObject.setString("id", token.tokenId().value());
+ tokenObject.setLong("lastUpdatedMillis", token.lastUpdated().toEpochMilli());
Cursor fingerprintsArray = tokenObject.setArray("versions");
- var versions = token.tokenVersions().stream()
- .sorted(Comparator.comparing(DataplaneTokenVersions.Version::creationTime)).toList();
- for (var tokenVersion : versions) {
+ for (var tokenVersion : token.tokenVersions()) {
Cursor fingerprintObject = fingerprintsArray.addObject();
fingerprintObject.setString("fingerprint", tokenVersion.fingerPrint().value());
fingerprintObject.setString("created", tokenVersion.creationTime().toString());
fingerprintObject.setString("author", tokenVersion.author());
fingerprintObject.setString("expiration", tokenVersion.expiration().map(Instant::toString).orElse("none"));
+ String tokenState = tokenVersion.expiration().map(controller.clock().instant()::isAfter).orElse(false)
+ ? "expired"
+ : valueOf(states.get(tokenVersion.fingerPrint()));
+ fingerprintObject.setString("state", tokenState);
}
- }
+ states.forEach((print, state) -> {
+ if (state != State.REVOKING) return;
+ Cursor fingerprintObject = fingerprintsArray.addObject();
+ fingerprintObject.setString("fingerprint", print.value());
+ fingerprintObject.setString("state", valueOf(state));
+ });
+ });
return new SlimeJsonResponse(slime);
}
+ private static String valueOf(DataplaneTokenService.State state) {
+ return switch (state) {
+ case UNUSED: yield "unused";
+ case DEPLOYING: yield "deploying";
+ case ACTIVE: yield "active";
+ case REVOKING: yield "revoking";
+ };
+ }
+
private HttpResponse generateToken(String tenant, String tokenid, HttpRequest request) {
var expiration = resolveExpiration(request).orElse(null);
@@ -1032,6 +1074,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
cursor.setString("level", notificationLevelAsString(notification.level()));
cursor.setString("type", notificationTypeAsString(notification.type()));
if (!excludeMessages) {
+ cursor.setString("title", notification.title());
Cursor messagesArray = cursor.setArray("messages");
notification.messages().forEach(messagesArray::addString);
}
@@ -1055,6 +1098,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
case deployment: yield "deployment";
case feedBlock: yield "feedBlock";
case reindex: yield "reindex";
+ case account: yield "account";
};
}
@@ -1684,6 +1728,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
var mailType = switch (type) {
case "contact" -> PendingMailVerification.MailType.TENANT_CONTACT;
case "notifications" -> PendingMailVerification.MailType.NOTIFICATIONS;
+ case "billing" -> PendingMailVerification.MailType.BILLING;
default -> throw new IllegalArgumentException("Unknown mail type " + type);
};
@@ -1983,10 +2028,11 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
response.setString("region", deploymentId.zoneId().region().value());
addAvailabilityZone(response, deployment.zone());
var application = controller.applications().requireApplication(TenantAndApplicationId.from(deploymentId.applicationId()));
- boolean includeAllEndpoints = request.getBooleanProperty("includeAllEndpoints") ||
- request.getBooleanProperty("includeLegacyEndpoints");
+ boolean includeAllEndpoints = request.getBooleanProperty("includeAllEndpoints");
+ boolean includeWeightedEndpoints = includeAllEndpoints || request.getBooleanProperty("includeWeightedEndpoints");
+ boolean includeLegacyEndpoints = includeAllEndpoints || request.getBooleanProperty("includeLegacyEndpoints");
var endpointArray = response.setArray("endpoints");
- for (var endpoint : endpointsOf(deploymentId, application, includeAllEndpoints)) {
+ for (var endpoint : endpointsOf(deploymentId, application, includeLegacyEndpoints, includeWeightedEndpoints)) {
toSlime(endpoint, endpointArray.addObject());
}
response.setString("clusters", withPath(toPath(deploymentId) + "/clusters", request.getUri()).toString());
@@ -2061,19 +2107,15 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
metrics.instant().ifPresent(instant -> metricsObject.setLong("lastUpdated", instant.toEpochMilli()));
}
- private EndpointList endpointsOf(DeploymentId deploymentId, Application application, boolean includeHidden) {
+ private EndpointList endpointsOf(DeploymentId deploymentId, Application application, boolean includeLegacy, boolean includeWeighted) {
EndpointList zoneEndpoints = controller.routing().readEndpointsOf(deploymentId).direct();
EndpointList declaredEndpoints = controller.routing().readDeclaredEndpointsOf(application).targets(deploymentId);
EndpointList endpoints = zoneEndpoints.and(declaredEndpoints);
- EndpointList generatedEndpoints = endpoints.generated();
- if (!includeHidden) {
- // If we have generated endpoints, hide non-generated
- if (!generatedEndpoints.isEmpty()) {
- endpoints = endpoints.generated();
- }
- // Hide legacy and weighted endpoints
- endpoints = endpoints.not().legacy()
- .not().scope(Endpoint.Scope.weighted);
+ if (!includeLegacy) {
+ endpoints = endpoints.not().legacy();
+ }
+ if (!includeWeighted) {
+ endpoints = endpoints.not().scope(Endpoint.Scope.weighted);
}
return endpoints;
}
@@ -2223,7 +2265,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
Cursor array = slime.setObject().setArray("globalrotationoverride");
Optional<Endpoint> primaryEndpoint = controller.routing().readDeclaredEndpointsOf(deploymentId.applicationId())
.requiresRotation()
- .primary();
+ .first();
if (primaryEndpoint.isPresent()) {
DeploymentRoutingContext context = controller.routing().of(deploymentId);
RoutingStatus status = context.routingStatus();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/HtmlResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/HtmlResponse.java
index 1cded3227a5..3bf2f070f97 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/HtmlResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/HtmlResponse.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.container.jdisc.HttpResponse;
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 0edfdb51055..18221d82e44 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.config.application.api.DeploymentSpec;
@@ -519,6 +519,8 @@ class JobControllerApiHandlerHelper {
run.end().ifPresent(end -> runObject.setLong("end", end.toEpochMilli()));
runObject.setString("status", nameOf(run.status()));
toSlime(runObject, run.versions(), run.reason(), application);
+ run.cloudAccount().filter(account -> ! account.isUnspecified())
+ .ifPresent(cloudAccount -> runObject.setObject("enclave").setString("cloudAccount", cloudAccount.value()));
Cursor runStepsArray = runObject.setArray("steps");
run.steps().forEach((step, info) -> {
Cursor runStepObject = runStepsArray.addObject();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParser.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParser.java
index a28f0e9733d..35eb495a564 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParser.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParser.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.container.jdisc.HttpRequest;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ZipResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ZipResponse.java
index f45ef49402b..73f9db7165c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ZipResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ZipResponse.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.container.jdisc.HttpResponse;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java
index 56d82d286cd..2ff0c1ab05c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.athenz;
import com.yahoo.component.annotation.Inject;
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
deleted file mode 100644
index d29603c529c..00000000000
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java
+++ /dev/null
@@ -1,512 +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.restapi.billing;
-
-import com.yahoo.config.provision.TenantName;
-import com.yahoo.container.jdisc.HttpRequest;
-import com.yahoo.container.jdisc.HttpResponse;
-import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
-import com.yahoo.restapi.ErrorResponse;
-import com.yahoo.restapi.JacksonJsonResponse;
-import com.yahoo.restapi.MessageResponse;
-import com.yahoo.restapi.Path;
-import com.yahoo.restapi.SlimeJsonResponse;
-import com.yahoo.restapi.StringResponse;
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.Inspector;
-import com.yahoo.slime.Slime;
-import com.yahoo.slime.SlimeUtils;
-import com.yahoo.vespa.hosted.controller.ApplicationController;
-import com.yahoo.vespa.hosted.controller.Controller;
-import com.yahoo.vespa.hosted.controller.TenantController;
-import com.yahoo.vespa.hosted.controller.api.integration.billing.Bill;
-import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
-import com.yahoo.vespa.hosted.controller.api.integration.billing.CollectionMethod;
-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.restapi.ErrorResponses;
-import com.yahoo.vespa.hosted.controller.tenant.Tenant;
-import com.yahoo.yolean.Exceptions;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.security.Principal;
-import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-/**
- * @author andreer
- * @author olaa
- */
-public class BillingApiHandler extends ThreadedHttpRequestHandler {
-
- private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
-
- 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();
- }
-
- @Override
- public HttpResponse handle(HttpRequest request) {
- try {
- Optional<String> userId = Optional.ofNullable(request.getJDiscRequest().getUserPrincipal()).map(Principal::getName);
- if (userId.isEmpty())
- return ErrorResponse.unauthorized("Must be authenticated to use this API");
-
- Path path = new Path(request.getUri());
- return switch (request.getMethod()) {
- case GET -> handleGET(request, path, userId.get());
- case PATCH -> handlePATCH(request, path, userId.get());
- case DELETE -> handleDELETE(path, userId.get());
- case POST -> handlePOST(path, request, userId.get());
- default -> ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported");
- };
- }
- catch (IllegalArgumentException e) {
- return ErrorResponse.badRequest(Exceptions.toMessageString(e));
- } catch (Exception e) {
- return ErrorResponses.logThrowing(request, log, e);
- }
- }
-
- private HttpResponse handleGET(HttpRequest request, Path path, String userId) {
- if (path.matches("/billing/v1/tenant/{tenant}/token")) return getToken(path.get("tenant"), userId);
- if (path.matches("/billing/v1/tenant/{tenant}/instrument")) return getInstruments(path.get("tenant"), userId);
- if (path.matches("/billing/v1/tenant/{tenant}/billing")) return getBilling(path.get("tenant"), request.getProperty("until"));
- if (path.matches("/billing/v1/tenant/{tenant}/plan")) return getPlan(path.get("tenant"));
- 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);
- }
-
- private HttpResponse getAllBills() {
- var bills = billingController.getBills();
- var headers = new String[]{ "ID", "Tenant", "From", "To", "CpuHours", "MemoryHours", "DiskHours", "Cpu", "Memory", "Disk", "Additional" };
- var rows = bills.stream()
- .map(bill -> {
- return new Object[] {
- bill.id().value(), bill.tenant().value(),
- bill.getStartDate().format(DateTimeFormatter.ISO_LOCAL_DATE),
- bill.getEndDate().format(DateTimeFormatter.ISO_LOCAL_DATE),
- bill.sumCpuHours(), bill.sumMemoryHours(), bill.sumDiskHours(),
- bill.sumCpuCost(), bill.sumMemoryCost(), bill.sumDiskCost(),
- bill.sumAdditionalCost()
- };
- })
- .toList();
- return new CsvResponse(headers, rows);
- }
-
- private HttpResponse handlePATCH(HttpRequest request, Path path, String userId) {
- if (path.matches("/billing/v1/tenant/{tenant}/instrument")) return patchActiveInstrument(request, path.get("tenant"), userId);
- if (path.matches("/billing/v1/tenant/{tenant}/plan")) return patchPlan(request, path.get("tenant"));
- if (path.matches("/billing/v1/tenant/{tenant}/collection")) return patchCollectionMethod(request, path.get("tenant"));
- return ErrorResponse.notFoundError("Nothing at " + path);
-
- }
-
- private HttpResponse handleDELETE(Path path, String userId) {
- if (path.matches("/billing/v1/tenant/{tenant}/instrument/{instrument}")) return deleteInstrument(path.get("tenant"), userId, path.get("instrument"));
- if (path.matches("/billing/v1/invoice/line-item/{line-item-id}")) return deleteLineItem(path.get("line-item-id"));
- return ErrorResponse.notFoundError("Nothing at " + path);
-
- }
-
- private HttpResponse handlePOST(Path path, HttpRequest request, String userId) {
- if (path.matches("/billing/v1/invoice")) return createBill(request, userId);
- if (path.matches("/billing/v1/invoice/{invoice-id}/status")) return setBillStatus(request, path.get("invoice-id"), userId);
- if (path.matches("/billing/v1/invoice/tenant/{tenant}/line-item")) return addLineItem(request, path.get("tenant"), userId);
- return ErrorResponse.notFoundError("Nothing at " + path);
-
- }
-
- private HttpResponse getPlan(String tenant) {
- var plan = billingController.getPlan(TenantName.from(tenant));
- var slime = new Slime();
- var root = slime.setObject();
- root.setString("tenant", tenant);
- root.setString("plan", plan.value());
- return new SlimeJsonResponse(slime);
- }
-
- private HttpResponse patchPlan(HttpRequest request, String tenant) {
- var tenantName = TenantName.from(tenant);
- var slime = inspectorOrThrow(request);
- var planId = PlanId.from(slime.field("plan").asString());
- var roles = requestRoles(request);
- var isAccountant = roles.contains(Role.hostedAccountant());
-
- var hasDeployments = hasDeployments(tenantName);
- var result = billingController.setPlan(tenantName, planId, hasDeployments, isAccountant);
-
- if (result.isSuccess())
- return new StringResponse("Plan: " + planId.value());
-
- return ErrorResponse.forbidden(result.getErrorMessage().orElse("Invalid plan change"));
- }
-
- private HttpResponse patchCollectionMethod(HttpRequest request, String tenant) {
- var tenantName = TenantName.from(tenant);
- var slime = inspectorOrThrow(request);
- var newMethod = slime.field("collection").valid() ?
- slime.field("collection").asString().toUpperCase() :
- slime.field("collectionMethod").asString().toUpperCase();
- if (newMethod.isEmpty()) return ErrorResponse.badRequest("No collection method specified");
-
- try {
- var result = billingController.setCollectionMethod(tenantName, CollectionMethod.valueOf(newMethod));
- if (result.isSuccess())
- return new StringResponse("Collection method updated to " + newMethod);
-
- return ErrorResponse.forbidden(result.getErrorMessage().orElse("Invalid collection method change"));
- } catch (IllegalArgumentException iea){
- return ErrorResponse.badRequest("Invalid collection method: " + newMethod);
- }
- }
-
- private HttpResponse getBillingAllTenants(String until) {
- try {
- var untilDate = untilParameter(until);
- var uncommittedBills = billingController.createUncommittedBills(untilDate);
-
- var slime = new Slime();
- var root = slime.setObject();
- root.setString("until", untilDate.format(DateTimeFormatter.ISO_DATE));
- var tenants = root.setArray("tenants");
-
- tenantController.asList().stream().sorted(Comparator.comparing(Tenant::name)).forEach(tenant -> {
- var bill = uncommittedBills.get(tenant.name());
- var tc = tenants.addObject();
- tc.setString("tenant", tenant.name().value());
- getPlanForTenant(tc, tenant.name());
- getCollectionForTenant(tc, tenant.name());
- renderCurrentUsage(tc.setObject("current"), bill);
- renderAdditionalItems(tc.setObject("additional").setArray("items"), billingController.getUnusedLineItems(tenant.name()));
-
- billingController.getDefaultInstrument(tenant.name()).ifPresent(card ->
- renderInstrument(tc.setObject("payment"), card)
- );
- });
-
- return new SlimeJsonResponse(slime);
- } catch (DateTimeParseException e) {
- return ErrorResponse.badRequest("Could not parse date: " + until);
- }
- }
-
- private void getCollectionForTenant(Cursor tc, TenantName tenant) {
- var collection = billingController.getCollectionMethod(tenant);
- tc.setString("collection", collection.name());
- }
-
- private HttpResponse addLineItem(HttpRequest request, String tenant, String userId) {
- Inspector inspector = inspectorOrThrow(request);
-
- Optional<Bill.Id> billId = SlimeUtils.optionalString(inspector.field("billId")).map(Bill.Id::of);
-
- billingController.addLineItem(
- TenantName.from(tenant),
- getInspectorFieldOrThrow(inspector, "description"),
- new BigDecimal(getInspectorFieldOrThrow(inspector, "amount")),
- billId,
- userId);
-
- return new MessageResponse("Added line item for tenant " + tenant);
- }
-
- private HttpResponse setBillStatus(HttpRequest request, String billId, String userId) {
- Inspector inspector = inspectorOrThrow(request);
- String status = getInspectorFieldOrThrow(inspector, "status");
- billingController.updateBillStatus(Bill.Id.of(billId), userId, status);
- return new MessageResponse("Updated status of invoice " + billId);
- }
-
- private HttpResponse createBill(HttpRequest request, String userId) {
- Inspector inspector = inspectorOrThrow(request);
- TenantName tenantName = TenantName.from(getInspectorFieldOrThrow(inspector, "tenant"));
-
- LocalDate startDate = LocalDate.parse(getInspectorFieldOrThrow(inspector, "startTime"));
- LocalDate endDate = LocalDate.parse(getInspectorFieldOrThrow(inspector, "endTime"));
-
- var billId = billingController.createBillForPeriod(tenantName, startDate, endDate, userId);
-
- Slime slime = new Slime();
- Cursor root = slime.setObject();
- root.setString("message", "Created invoice with ID " + billId.value());
- root.setString("id", billId.value());
- return new SlimeJsonResponse(slime);
- }
-
- private HttpResponse getInstruments(String tenant, String userId) {
- var instrumentListResponse = billingController.listInstruments(TenantName.from(tenant), userId);
- return new JacksonJsonResponse<>(200, instrumentListResponse);
- }
-
- private HttpResponse getToken(String tenant, String userId) {
- return new StringResponse(billingController.createClientToken(tenant, userId));
- }
-
- private HttpResponse getBilling(String tenant, String until) {
- try {
- var untilDate = untilParameter(until);
- var tenantId = TenantName.from(tenant);
- var slimeResponse = new Slime();
- var root = slimeResponse.setObject();
-
- root.setString("until", untilDate.format(DateTimeFormatter.ISO_DATE));
-
- getPlanForTenant(root, tenantId);
- renderCurrentUsage(root.setObject("current"), getCurrentUsageForTenant(tenantId, untilDate));
- renderAdditionalItems(root.setObject("additional").setArray("items"), billingController.getUnusedLineItems(tenantId));
- renderBills(root.setArray("bills"), getBillsForTenant(tenantId));
-
- billingController.getDefaultInstrument(tenantId).ifPresent( card ->
- renderInstrument(root.setObject("payment"), card)
- );
-
- root.setString("collection", billingController.getCollectionMethod(tenantId).name());
- return new SlimeJsonResponse(slimeResponse);
- } catch (DateTimeParseException e) {
- return ErrorResponse.badRequest("Could not parse date: " + until);
- }
- }
-
- 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();
- var lineItems = root.setArray("lineItems");
-
- billingController.getUnusedLineItems(TenantName.from(tenant))
- .forEach(lineItem -> {
- var itemCursor = lineItems.addObject();
- renderLineItemToCursor(itemCursor, lineItem);
- });
-
- return new SlimeJsonResponse(slimeResponse);
- }
-
- private void getPlanForTenant(Cursor cursor, TenantName tenant) {
- PlanId plan = billingController.getPlan(tenant);
- cursor.setString("plan", plan.value());
- cursor.setString("planName", billingController.getPlanDisplayName(plan));
- }
-
- private void renderInstrument(Cursor cursor, PaymentInstrument instrument) {
- cursor.setString("pi-id", instrument.getId());
- cursor.setString("type", instrument.getType());
- cursor.setString("brand", instrument.getBrand());
- cursor.setString("endingWith", instrument.getEndingWith());
- cursor.setString("expiryDate", instrument.getExpiryDate());
- cursor.setString("displayText", instrument.getDisplayText());
- cursor.setString("nameOnCard", instrument.getNameOnCard());
- cursor.setString("addressLine1", instrument.getAddressLine1());
- cursor.setString("addressLine2", instrument.getAddressLine2());
- cursor.setString("zip", instrument.getZip());
- cursor.setString("city", instrument.getCity());
- cursor.setString("state", instrument.getState());
- cursor.setString("country", instrument.getCountry());
-
- }
-
- private void renderCurrentUsage(Cursor cursor, Bill currentUsage) {
- if (currentUsage == null) return;
- cursor.setString("amount", currentUsage.sum().toPlainString());
- cursor.setString("status", "accrued");
- cursor.setString("from", currentUsage.getStartDate().format(DATE_TIME_FORMATTER));
- var itemsCursor = cursor.setArray("items");
- currentUsage.lineItems().forEach(lineItem -> {
- var itemCursor = itemsCursor.addObject();
- renderLineItemToCursor(itemCursor, lineItem);
- });
- }
-
- private void renderAdditionalItems(Cursor cursor, List<Bill.LineItem> items) {
- items.forEach(item -> {
- renderLineItemToCursor(cursor.addObject(), item);
- });
- }
-
- private Bill getCurrentUsageForTenant(TenantName tenant, LocalDate until) {
- return billingController.createUncommittedBill(tenant, until);
- }
-
- private List<Bill> getBillsForTenant(TenantName tenant) {
- return billingController.getBillsForTenant(tenant);
- }
-
- private void renderBills(Cursor cursor, List<Bill> bills) {
- bills.forEach(bill -> {
- var billCursor = cursor.addObject();
- renderBillToCursor(billCursor, bill);
- });
- }
-
- private void renderBillToCursor(Cursor billCursor, Bill bill) {
- billCursor.setString("id", bill.id().value());
- billCursor.setString("from", bill.getStartDate().format(DATE_TIME_FORMATTER));
- billCursor.setString("to", bill.getEndDate().format(DATE_TIME_FORMATTER));
-
- billCursor.setString("amount", bill.sum().toString());
- billCursor.setString("status", bill.status());
- var statusCursor = billCursor.setArray("statusHistory");
- renderStatusHistory(statusCursor, bill.statusHistory());
-
-
- var lineItemsCursor = billCursor.setArray("items");
- bill.lineItems().forEach(lineItem -> {
- var itemCursor = lineItemsCursor.addObject();
- renderLineItemToCursor(itemCursor, lineItem);
- });
- }
-
- private void renderStatusHistory(Cursor cursor, Bill.StatusHistory statusHistory) {
- statusHistory.getHistory()
- .entrySet()
- .stream()
- .forEach(entry -> {
- var c = cursor.addObject();
- c.setString("at", entry.getKey().format(DATE_TIME_FORMATTER));
- c.setString("status", entry.getValue());
- });
- }
-
- private void renderLineItemToCursor(Cursor cursor, Bill.LineItem lineItem) {
- cursor.setString("id", lineItem.id());
- cursor.setString("description", lineItem.description());
- cursor.setString("amount", lineItem.amount().toString());
- cursor.setString("plan", lineItem.plan());
- cursor.setString("planName", billingController.getPlanDisplayName(PlanId.from(lineItem.plan())));
-
- lineItem.applicationId().ifPresent(appId -> {
- cursor.setString("application", appId.application().value());
- cursor.setString("instance", appId.instance().value());
- });
- lineItem.zoneId().ifPresent(zoneId ->
- cursor.setString("zone", zoneId.value())
- );
-
- lineItem.getArchitecture().ifPresent(architecture -> {
- cursor.setString("architecture", architecture.name());
- });
-
- cursor.setLong("majorVersion", lineItem.getMajorVersion());
-
- lineItem.getCpuHours().ifPresent(cpuHours ->
- cursor.setString("cpuHours", cpuHours.toString())
- );
- lineItem.getMemoryHours().ifPresent(memoryHours ->
- cursor.setString("memoryHours", memoryHours.toString())
- );
- lineItem.getDiskHours().ifPresent(diskHours ->
- cursor.setString("diskHours", diskHours.toString())
- );
- lineItem.getGpuHours().ifPresent(gpuHours ->
- cursor.setString("gpuHours", gpuHours.toString())
- );
- lineItem.getCpuCost().ifPresent(cpuCost ->
- cursor.setString("cpuCost", cpuCost.toString())
- );
- lineItem.getMemoryCost().ifPresent(memoryCost ->
- cursor.setString("memoryCost", memoryCost.toString())
- );
- lineItem.getDiskCost().ifPresent(diskCost ->
- cursor.setString("diskCost", diskCost.toString())
- );
- lineItem.getGpuCost().ifPresent(gpuCost ->
- cursor.setString("gpuCost", gpuCost.toString())
- );
- }
-
- private HttpResponse deleteInstrument(String tenant, String userId, String instrument) {
- if (billingController.deleteInstrument(TenantName.from(tenant), userId, instrument)) {
- return new StringResponse("OK");
- } else {
- return ErrorResponse.forbidden("Cannot delete payment instrument you don't own");
- }
- }
-
- private HttpResponse deleteLineItem(String lineItemId) {
- billingController.deleteLineItem(lineItemId);
- return new MessageResponse("Succesfully deleted line item " + lineItemId);
- }
-
- private HttpResponse patchActiveInstrument(HttpRequest request, String tenant, String userId) {
- var inspector = inspectorOrThrow(request);
- String instrumentId = getInspectorFieldOrThrow(inspector, "active");
- InstrumentOwner paymentInstrument = new InstrumentOwner(TenantName.from(tenant), userId, instrumentId, true);
- boolean success = billingController.setActivePaymentInstrument(paymentInstrument);
- return success ? new StringResponse("OK") : ErrorResponse.internalServerError("Failed to patch active instrument");
- }
-
- private Inspector inspectorOrThrow(HttpRequest request) {
- try {
- return SlimeUtils.jsonToSlime(request.getData().readAllBytes()).get();
- } catch (IOException e) {
- throw new IllegalArgumentException("Failed to parse request body");
- }
- }
-
- private static String getInspectorFieldOrThrow(Inspector inspector, String field) {
- if (!inspector.field(field).valid())
- throw new IllegalArgumentException("Field " + field + " cannot be null");
- return inspector.field(field).asString();
- }
-
- private LocalDate untilParameter(String until) {
- if (until == null || until.isEmpty() || until.isBlank())
- return LocalDate.now();
- return LocalDate.parse(until);
- }
-
- private boolean hasDeployments(TenantName tenantName) {
- return applicationController.asList(tenantName)
- .stream()
- .flatMap(app -> app.instances().values()
- .stream()
- .flatMap(instance -> instance.deployments().values().stream())
- )
- .count() > 0;
- }
-
- private static Set<Role> requestRoles(HttpRequest request) {
- return Optional.ofNullable(request.getJDiscRequest().context().get(SecurityContext.ATTRIBUTE_NAME))
- .filter(SecurityContext.class::isInstance)
- .map(SecurityContext.class::cast)
- .map(SecurityContext::roles)
- .orElseThrow(() -> new IllegalArgumentException("Attribute '" + SecurityContext.ATTRIBUTE_NAME + "' was not set on request"));
- }
-
-}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java
index c5fb1afbae8..85a77dcfa61 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.billing;
import com.yahoo.config.provision.TenantName;
@@ -12,26 +12,32 @@ import com.yahoo.restapi.SlimeJsonResponse;
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.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.TenantController;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Bill;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingReporter;
import com.yahoo.vespa.hosted.controller.api.integration.billing.CollectionMethod;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Plan;
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.integration.billing.Quota;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.StatusHistory;
import com.yahoo.vespa.hosted.controller.api.role.Role;
import com.yahoo.vespa.hosted.controller.api.role.SecurityContext;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponses;
+import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import java.math.BigDecimal;
import java.time.Clock;
+import java.time.Instant;
import java.time.LocalDate;
+import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
@@ -51,6 +57,7 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
private final ApplicationController applications;
private final TenantController tenants;
private final BillingController billing;
+ private final BillingReporter billingReporter;
private final PlanRegistry planRegistry;
private final Clock clock;
@@ -61,6 +68,7 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
this.billing = controller.serviceRegistry().billingController();
this.planRegistry = controller.serviceRegistry().planRegistry();
this.clock = controller.serviceRegistry().clock();
+ this.billingReporter = controller.serviceRegistry().billingReporter();
}
private static RestApi createRestApi(BillingApiHandlerV2 self) {
@@ -82,9 +90,24 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
*/
.addRoute(RestApi.route("/billing/v2/accountant")
.get(self::accountant))
- .addRoute(RestApi.route("/billing/v2/accountant/preview/tenant/{tenant}")
+ .addRoute(RestApi.route("/billing/v2/accountant/preview")
+ .get(self::accountantPreview))
+ .addRoute(RestApi.route("/billing/v2/accountant/tenant/{tenant}")
+ .get(self::accountantTenant))
+ .addRoute(RestApi.route("/billing/v2/accountant/tenant/{tenant}/preview")
.get(self::previewBill)
.post(Slime.class, self::createBill))
+ .addRoute(RestApi.route("/billing/v2/accountant/tenant/{tenant}/items")
+ .get(self::additionalItems)
+ .post(Slime.class, self::newAdditionalItem))
+ .addRoute(RestApi.route("/billing/v2/accountant/tenant/{tenant}/item/{item}")
+ .delete(self::deleteAdditionalItem))
+ .addRoute(RestApi.route("/billing/v2/accountant/tenant/{tenant}/plan")
+ .get(self::accountantTenantPlan)
+ .post(Slime.class, self::setAccountantTenantPlan))
+ .addRoute(RestApi.route("/billing/v2/accountant/tenant/{tenant}/collection")
+ .get(self::accountantTenantCollection)
+ .post(Slime.class, self::setAccountantTenantCollection))
.addRoute(RestApi.route("/billing/v2/accountant/bill/{invoice}/export")
.put(Slime.class, self::putAccountantInvoiceExport))
.addRoute(RestApi.route("/billing/v2/accountant/plans")
@@ -202,21 +225,39 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
// --------- ACCOUNTANT API ----------
private Slime accountant(RestApi.RequestContext requestContext) {
- var untilAt = untilParameter(requestContext);
- var usagePerTenant = billing.createUncommittedBills(untilAt);
-
var response = new Slime();
var tenantsResponse = response.setObject().setArray("tenants");
tenants.asList().stream().sorted(Comparator.comparing(Tenant::name)).forEach(tenant -> {
- var usage = Optional.ofNullable(usagePerTenant.get(tenant.name()));
var tenantResponse = tenantsResponse.addObject();
tenantResponse.setString("tenant", tenant.name().value());
toSlime(tenantResponse.setObject("plan"), planFor(tenant.name()));
toSlime(tenantResponse.setObject("quota"), billing.getQuota(tenant.name()));
tenantResponse.setString("collection", billing.getCollectionMethod(tenant.name()).name());
- tenantResponse.setString("lastBill", usage.map(Bill::getStartDate).map(DateTimeFormatter.ISO_DATE::format).orElse(null));
- tenantResponse.setString("unbilled", usage.map(Bill::sum).map(BigDecimal::toPlainString).orElse("0.00"));
+ tenantResponse.setString("lastBill", LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC).format(DateTimeFormatter.ISO_DATE));
+ tenantResponse.setString("unbilled", "0.00");
+ });
+
+ return response;
+ }
+
+ private Slime accountantPreview(RestApi.RequestContext requestContext) {
+ var untilAt = untilParameter(requestContext);
+ var usagePerTenant = billing.createUncommittedBills(untilAt);
+
+ var response = new Slime();
+ var tenantsResponse = response.setObject().setArray("tenants");
+
+ usagePerTenant.entrySet().stream().sorted(Comparator.comparing(x -> x.getValue().sum())).forEachOrdered(x -> {
+ var tenant = x.getKey();
+ var usage = x.getValue();
+ var tenantResponse = tenantsResponse.addObject();
+ tenantResponse.setString("tenant", tenant.value());
+ toSlime(tenantResponse.setObject("plan"), planFor(tenant));
+ toSlime(tenantResponse.setObject("quota"), billing.getQuota(tenant));
+ tenantResponse.setString("collection", billing.getCollectionMethod(tenant).name());
+ tenantResponse.setString("lastBill", usage.getStartDate().format(DateTimeFormatter.ISO_DATE));
+ tenantResponse.setString("unbilled", usage.sum().toPlainString());
});
return response;
@@ -265,17 +306,146 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
}
private HttpResponse putAccountantInvoiceExport(RestApi.RequestContext ctx, Slime slime) {
- var billId = ctx.attributes().get("invoice")
- .map(id -> Bill.Id.of((String) id))
- .orElseThrow(() -> new RestApiException.BadRequest("Missing bill ID"));
+ var billId = Bill.Id.of(ctx.pathParameters().getStringOrThrow("invoice"));
// TODO: try to find a way to retrieve the cloud tenant from BillingControllerImpl
var bill = billing.getBill(billId);
var cloudTenant = tenants.require(bill.tenant(), CloudTenant.class);
var exportMethod = slime.get().field("method").asString();
- var result = billing.exportBill(bill, exportMethod, cloudTenant);
- return new MessageResponse("Bill has been exported: " + result);
+ var result = billingReporter.exportBill(bill, exportMethod, cloudTenant);
+
+ var responseSlime = new Slime();
+ responseSlime.setObject().setString("invoiceId", result);
+ return new SlimeJsonResponse(responseSlime);
+ }
+
+ private MessageResponse deleteAdditionalItem(RestApi.RequestContext requestContext) {
+ var tenantName = TenantName.from(requestContext.pathParameters().getStringOrThrow("tenant"));
+ var tenant = tenants.get(tenantName).orElseThrow(() -> new RestApiException.NotFound("No such tenant: " + tenantName));
+
+ var itemId = requestContext.pathParameters().getStringOrThrow("item");
+
+ var items = billing.getUnusedLineItems(tenant.name());
+ var candidate = items.stream().filter(item -> item.id().equals(itemId)).findAny();
+
+ if (candidate.isEmpty()) {
+ throw new RestApiException.NotFound("Could not find item with ID " + itemId);
+ }
+
+ billing.deleteLineItem(itemId);;
+
+ return new MessageResponse("Successfully deleted line item " + itemId);
+ }
+
+ private MessageResponse newAdditionalItem(RestApi.RequestContext requestContext, Slime body) {
+ var tenantName = TenantName.from(requestContext.pathParameters().getStringOrThrow("tenant"));
+ var tenant = tenants.get(tenantName).orElseThrow(() -> new RestApiException.NotFound("No such tenant: " + tenantName));
+
+ var inspector = body.get();
+
+ var billId = SlimeUtils.optionalString(inspector.field("billId")).map(Bill.Id::of);
+
+ billing.addLineItem(
+ tenant.name(),
+ getInspectorFieldOrThrow(inspector, "description"),
+ new BigDecimal(getInspectorFieldOrThrow(inspector, "amount")),
+ billId,
+ requestContext.userPrincipalOrThrow().getName());
+
+ return new MessageResponse("Added line item for tenant " + tenantName);
+ }
+
+ private Slime additionalItems(RestApi.RequestContext requestContext) {
+ var tenantName = TenantName.from(requestContext.pathParameters().getStringOrThrow("tenant"));
+ var tenant = tenants.get(tenantName).orElseThrow(() -> new RestApiException.NotFound("No such tenant: " + tenantName));
+
+ var slime = new Slime();
+ var items = slime.setObject().setArray("items");
+
+ billing.getUnusedLineItems(tenant.name()).forEach(item -> {
+ var itemCursor = items.addObject();
+ toSlime(itemCursor, item);
+ });
+
+ return slime;
+ }
+
+ private MessageResponse setAccountantTenantPlan(RestApi.RequestContext requestContext, Slime body) {
+ var tenantName = TenantName.from(requestContext.pathParameters().getStringOrThrow("tenant"));
+ var tenant = tenants.require(tenantName, CloudTenant.class);
+
+ var planId = PlanId.from(getInspectorFieldOrThrow(body.get(), "id"));
+ var response = billing.setPlan(tenant.name(), planId, false, true);
+
+ if (response.isSuccess()) {
+ return new MessageResponse("Plan: " + planId.value());
+ } else {
+ throw new RestApiException.BadRequest("Could not change plan: " + response.getErrorMessage());
+ }
+ }
+
+ private Slime accountantTenantPlan(RestApi.RequestContext requestContext) {
+ var tenantName = TenantName.from(requestContext.pathParameters().getStringOrThrow("tenant"));
+ var tenant = tenants.require(tenantName, CloudTenant.class);
+
+ var planId = billing.getPlan(tenant.name());
+ var plan = planRegistry.plan(planId);
+
+ if (plan.isEmpty()) {
+ throw new RestApiException.BadRequest("Plan with ID '" + planId.value() + "' does not exist");
+ }
+
+ var slime = new Slime();
+ var root = slime.setObject();
+ root.setString("id", plan.get().id().value());
+ root.setString("name", plan.get().displayName());
+
+ return slime;
+ }
+
+ private MessageResponse setAccountantTenantCollection(RestApi.RequestContext requestContext, Slime body) {
+ var tenantName = TenantName.from(requestContext.pathParameters().getStringOrThrow("tenant"));
+ var tenant = tenants.require(tenantName, CloudTenant.class);
+
+ var collection = CollectionMethod.valueOf(getInspectorFieldOrThrow(body.get(), "collection"));
+ var result = billing.setCollectionMethod(tenant.name(), collection);
+
+ if (result.isSuccess()) {
+ return new MessageResponse("Collection: " + collection.name());
+ } else {
+ throw new RestApiException.BadRequest("Could not change collection method: " + result.getErrorMessage());
+ }
+ }
+
+ private Slime accountantTenantCollection(RestApi.RequestContext requestContext) {
+ var tenantName = TenantName.from(requestContext.pathParameters().getStringOrThrow("tenant"));
+ var tenant = tenants.require(tenantName, CloudTenant.class);
+
+ var collection = billing.getCollectionMethod(tenant.name());
+
+ var slime = new Slime();
+ var root = slime.setObject();
+ root.setString("collection", collection.name());
+
+ return slime;
+ }
+
+ private Slime accountantTenant(RestApi.RequestContext requestContext) {
+ var tenantName = TenantName.from(requestContext.pathParameters().getStringOrThrow("tenant"));
+ var tenant = tenants.require(tenantName, CloudTenant.class);
+
+ var slime = new Slime();
+ var root = slime.setObject();
+
+ var planId = billing.getPlan(tenant.name());
+ var plan = planRegistry.plan(planId);
+
+ var collection = billing.getCollectionMethod(tenant.name());
+
+ toSlime(root, tenant, planId, plan, collection);
+
+ return slime;
}
// --------- INVOICE RENDERING ----------
@@ -289,7 +459,7 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
slime.setString("from", bill.getStartDate().format(DateTimeFormatter.ISO_LOCAL_DATE));
slime.setString("to", bill.getEndDate().format(DateTimeFormatter.ISO_LOCAL_DATE));
slime.setString("total", bill.sum().toString());
- slime.setString("status", bill.status());
+ slime.setString("status", bill.status().value());
}
private void usageToSlime(Cursor slime, Bill bill) {
@@ -304,16 +474,16 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
slime.setString("from", bill.getStartDate().format(DateTimeFormatter.ISO_LOCAL_DATE));
slime.setString("to", bill.getEndDate().format(DateTimeFormatter.ISO_LOCAL_DATE));
slime.setString("total", bill.sum().toString());
- slime.setString("status", bill.status());
+ slime.setString("status", bill.status().value());
toSlime(slime.setArray("statusHistory"), bill.statusHistory());
toSlime(slime.setArray("items"), bill.lineItems());
}
- private void toSlime(Cursor slime, Bill.StatusHistory history) {
+ private void toSlime(Cursor slime, StatusHistory history) {
history.getHistory().forEach((key, value) -> {
var c = slime.addObject();
c.setString("at", key.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
- c.setString("status", value);
+ c.setString("status", value.value());
});
}
@@ -328,6 +498,8 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
toSlime(slime.setObject("plan"), planRegistry.plan(item.plan()).orElseThrow(() -> new RuntimeException("No such plan: '" + item.plan() + "'")));
item.getArchitecture().ifPresent(arch -> slime.setString("architecture", arch.name()));
slime.setLong("majorVersion", item.getMajorVersion());
+ if (! item.getCloudAccount().isUnspecified())
+ slime.setString("cloudAccount", item.getCloudAccount().value());
item.applicationId().ifPresent(appId -> {
slime.setString("application", appId.application().value());
@@ -339,6 +511,7 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
toSlime(slime.setObject("cpu"), item.getCpuHours(), item.getCpuCost());
toSlime(slime.setObject("memory"), item.getMemoryHours(), item.getMemoryCost());
toSlime(slime.setObject("disk"), item.getDiskHours(), item.getDiskCost());
+ toSlime(slime.setObject("gpu"), item.getGpuHours(), item.getGpuCost());
}
private void toSlime(Cursor slime, Optional<BigDecimal> hours, Optional<BigDecimal> cost) {
@@ -346,6 +519,33 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
cost.ifPresent(c -> slime.setString("cost", c.toString()));
}
+ private void toSlime(Cursor slime, CloudTenant tenant, PlanId planId, Optional<Plan> plan, CollectionMethod method) {
+ slime.setString("tenant", tenant.name().value());
+ toSlime(slime.setObject("plan"), planId, plan);
+ toSlime(slime.setObject("billing"), tenant.billingReference());
+ slime.setString("collection", method.name());
+ }
+
+ private void toSlime(Cursor slime, PlanId planId, Optional<Plan> plan) {
+ slime.setString("id", planId.value());
+ if (plan.isPresent()) {
+ slime.setString("name", plan.get().displayName());
+ slime.setBool("billed", plan.get().isBilled());
+ slime.setBool("supported", plan.get().isSupported());
+ } else {
+ slime.setString("name", "UNKNOWN");
+ slime.setBool("billed", false);
+ slime.setBool("supported", false);
+ }
+ }
+
+ private void toSlime(Cursor slime, Optional<BillingReference> billingReference) {
+ if (billingReference.isPresent()) {
+ slime.setString("id", billingReference.get().reference());
+ slime.setLong("lastUpdated", billingReference.get().updated().toEpochMilli());
+ }
+ }
+
private List<Object[]> toCsv(Bill bill) {
return List.<Object[]>of(new Object[]{
bill.id().value(), bill.tenant().value(),
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/CsvResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/CsvResponse.java
index e97a51e58a2..cf45bfb67f0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/CsvResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/CsvResponse.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.billing;
import com.yahoo.container.jdisc.HttpResponse;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/certificate/EndpointCertificatesHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/certificate/EndpointCertificatesHandler.java
index 912bd051a31..b38bb73a98a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/certificate/EndpointCertificatesHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/certificate/EndpointCertificatesHandler.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.certificate;
import com.yahoo.config.provision.ApplicationId;
@@ -19,6 +20,7 @@ import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.certificate.AssignedCertificate;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.persistence.EndpointCertificateSerializer;
+import com.yahoo.vespa.hosted.controller.routing.EndpointConfig;
import java.util.List;
import java.util.Optional;
@@ -73,11 +75,11 @@ public class EndpointCertificatesHandler extends ThreadedHttpRequestHandler {
public StringResponse reRequestEndpointCertificateFor(String instanceId, boolean ignoreExisting) {
ApplicationId applicationId = ApplicationId.fromFullString(instanceId);
- if (controller.routing().generatedEndpointsEnabled(applicationId)) {
+ if (controller.routing().endpointConfig(applicationId) == EndpointConfig.generated) {
throw new IllegalArgumentException("Cannot re-request certificate. " + instanceId + " is assigned certificate from a pool");
}
try (var lock = curator.lock(TenantAndApplicationId.from(applicationId))) {
- AssignedCertificate assignedCertificate = curator.readAssignedCertificate(applicationId)
+ AssignedCertificate assignedCertificate = curator.readAssignedCertificate(TenantAndApplicationId.from(applicationId), Optional.of(applicationId.instance()))
.orElseThrow(() -> new RestApiException.NotFound("No certificate found for application " + applicationId.serializedForm()));
String algo = this.endpointCertificateAlgo.with(FetchVector.Dimension.INSTANCE_ID, applicationId.serializedForm()).value();
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 f0c851f50ef..f3b28691262 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.changemanagement;
import com.yahoo.config.provision.Environment;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
index 14223b49abc..425c1fd894d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.configserver;
import ai.vespa.http.HttpURL;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/package-info.java
index a44d138ff11..91dde82e233 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/configserver/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* @author freva
*/
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/AccessRequestResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/AccessRequestResponse.java
index 30e103048cf..4863b91b3eb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/AccessRequestResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/AccessRequestResponse.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/AuditLogResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/AuditLogResponse.java
index f46806743e9..859281dbe18 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/AuditLogResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/AuditLogResponse.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.restapi.SlimeJsonResponse;
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 6da4e788de1..b9ba4f691fc 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/DecryptionTokenResealer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/DecryptionTokenResealer.java
index b3d966d20c9..f2e51b51752 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/DecryptionTokenResealer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/DecryptionTokenResealer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.container.jdisc.HttpRequest;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/JobsResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/JobsResponse.java
index 05768410891..0d15d9b2971 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/JobsResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/JobsResponse.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.concurrent.maintenance.JobControl;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java
index ea7bce00794..5a8c4847ce6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/MeteringResponse.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/RequestUtils.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/RequestUtils.java
index 884399f25d9..746f1d8ce2e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/RequestUtils.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/RequestUtils.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.io.IOUtils;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ResealedTokenResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ResealedTokenResponse.java
index 4714d0e5af1..2aab64a7c30 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ResealedTokenResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/ResealedTokenResponse.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.restapi.SlimeJsonResponse;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/StatsResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/StatsResponse.java
index 96a3c9f177d..ab12187c069 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/StatsResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/StatsResponse.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.config.provision.zone.ZoneId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/UpgraderResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/UpgraderResponse.java
index f9add356f19..e8ba1177c67 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/UpgraderResponse.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/UpgraderResponse.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.restapi.SlimeJsonResponse;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/WellKnownApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/WellKnownApiHandler.java
index c98a4cc72be..63f600aaa50 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/WellKnownApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/controller/WellKnownApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.container.jdisc.HttpRequest;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenService.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenService.java
index 385200a1624..834133e7eb5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenService.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenService.java
@@ -1,27 +1,53 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.dataplanetoken;
+import com.yahoo.concurrent.DaemonThreadFactory;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.security.token.Token;
import com.yahoo.security.token.TokenCheckHash;
import com.yahoo.security.token.TokenDomain;
import com.yahoo.security.token.TokenGenerator;
import com.yahoo.transaction.Mutex;
+import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
+import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.DataplaneToken;
import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.DataplaneTokenVersions;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.DataplaneTokenVersions.Version;
import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.FingerPrint;
import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+import com.yahoo.vespa.hosted.controller.application.Deployment;
+import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import java.security.Principal;
import java.time.Duration;
import java.time.Instant;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Phaser;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static java.util.Comparator.comparing;
+import static java.util.Comparator.naturalOrder;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toMap;
+
/**
* Service to list, generate and delete data plane tokens
*
@@ -34,7 +60,7 @@ public class DataplaneTokenService {
private static final int CHECK_HASH_BYTES = 32;
public static final Duration DEFAULT_TTL = Duration.ofDays(30);
-
+ private final ExecutorService executor = Executors.newCachedThreadPool(new DaemonThreadFactory("dataplane-token-service-"));
private final Controller controller;
public DataplaneTokenService(Controller controller) {
@@ -48,6 +74,110 @@ public class DataplaneTokenService {
return controller.curator().readDataplaneTokens(tenantName);
}
+ public enum State { UNUSED, DEPLOYING, ACTIVE, REVOKING }
+
+ /** List all known tokens for a tenant, with the state of each token version (both current and deactivating). */
+ public Map<DataplaneTokenVersions, Map<FingerPrint, State>> listTokensWithState(TenantName tenantName) {
+ List<DataplaneTokenVersions> currentTokens = listTokens(tenantName);
+ Set<TokenId> usedTokens = new HashSet<>();
+ Map<HostName, Map<TokenId, List<FingerPrint>>> activeTokens = listActiveTokens(tenantName, usedTokens);
+ Map<TokenId, Map<FingerPrint, Boolean>> activeFingerprints = computeStates(activeTokens);
+ Map<DataplaneTokenVersions, Map<FingerPrint, State>> tokens = new TreeMap<>(comparing(DataplaneTokenVersions::tokenId));
+ for (DataplaneTokenVersions token : currentTokens) {
+ Map<FingerPrint, State> states = new TreeMap<>();
+ // Current tokens are active iff. they are active everywhere.
+ for (Version version : token.tokenVersions()) {
+ // If the token was not seen anywhere, it is deploying or unused.
+ // Otherwise, it is active iff. it is active everywhere.
+ Boolean isActive = activeFingerprints.getOrDefault(token.tokenId(), Map.of()).get(version.fingerPrint());
+ states.put(version.fingerPrint(),
+ isActive == null ? usedTokens.contains(token.tokenId()) ? State.DEPLOYING : State.UNUSED
+ : isActive ? State.ACTIVE : State.DEPLOYING);
+ }
+ // Active, non-current token versions are deactivating.
+ for (FingerPrint print : activeFingerprints.getOrDefault(token.tokenId(), Map.of()).keySet()) {
+ states.putIfAbsent(print, State.REVOKING);
+ }
+ tokens.put(token, states);
+ }
+ // Active, non-current tokens are also deactivating.
+ activeFingerprints.forEach((id, prints) -> {
+ if (currentTokens.stream().noneMatch(token -> token.tokenId().equals(id))) {
+ Map<FingerPrint, State> states = new TreeMap<>();
+ for (FingerPrint print : prints.keySet()) states.put(print, State.REVOKING);
+ tokens.put(new DataplaneTokenVersions(id, List.of(), Instant.EPOCH), states);
+ }
+ });
+ return tokens;
+ }
+
+ private Map<HostName, Map<TokenId, List<FingerPrint>>> listActiveTokens(TenantName tenantName, Set<TokenId> usedTokens) {
+ Map<HostName, Map<TokenId, List<FingerPrint>>> tokens = new ConcurrentHashMap<>();
+ Phaser phaser = new Phaser(1);
+ for (Application application : controller.applications().asList(tenantName)) {
+ for (Instance instance : application.instances().values()) {
+ instance.deployments().forEach((zone, deployment) -> {
+ DeploymentId id = new DeploymentId(instance.id(), zone);
+ usedTokens.addAll(deployment.dataPlaneTokens().keySet());
+ phaser.register();
+ executor.execute(() -> {
+ try { tokens.putAll(controller.serviceRegistry().configServer().activeTokenFingerprints(id)); }
+ finally { phaser.arrive(); }
+ });
+ });
+ }
+ }
+ phaser.arriveAndAwaitAdvance();
+ return tokens;
+ }
+
+ /** Computes whether each print is active on all hosts where its token is present. */
+ private Map<TokenId, Map<FingerPrint, Boolean>> computeStates(Map<HostName, Map<TokenId, List<FingerPrint>>> activeTokens) {
+ Map<TokenId, Map<FingerPrint, Boolean>> states = new HashMap<>();
+ for (Map<TokenId, List<FingerPrint>> token : activeTokens.values()) {
+ token.forEach((id, prints) -> {
+ states.merge(id,
+ prints.stream().collect(toMap(print -> print, __ -> true)),
+ (a, b) -> new HashMap<>() {{ // true iff. present in both, false iff. present in one.
+ a.forEach((p, s) -> put(p, s && b.getOrDefault(p, false)));
+ b.forEach((p, s) -> putIfAbsent(p, false));
+ }});
+ });
+ }
+ return states;
+ }
+
+ /** Triggers redeployment of all applications which reference a token which has changed. */
+ public void triggerTokenChangeDeployments() {
+ controller.applications().asList().stream()
+ .collect(groupingBy(application -> application.id().tenant()))
+ .forEach((tenant, applications) -> {
+ List<DataplaneTokenVersions> currentTokens = listTokens(tenant);
+ for (Application application : applications) {
+ for (Instance instance : application.instances().values()) {
+ instance.deployments().forEach((zone, deployment) -> {
+ if (zone.environment().isTest()) return;
+ if (deployment.dataPlaneTokens().isEmpty()) return;
+ boolean needsRetrigger = false;
+ // If a token has a newer change than the deployed token data, we need to re-trigger.
+ for (DataplaneTokenVersions token : currentTokens)
+ needsRetrigger |= deployment.dataPlaneTokens().getOrDefault(token.tokenId(), Instant.MAX).isBefore(token.lastUpdated());
+
+ // If a token is no longer current, but was deployed with at least one version, we need to re-trigger.
+ for (var entry : deployment.dataPlaneTokens().entrySet())
+ needsRetrigger |= ! Instant.EPOCH.equals(entry.getValue())
+ && currentTokens.stream().noneMatch(token -> token.tokenId().equals(entry.getKey()));
+
+ if (needsRetrigger && controller.jobController().last(instance.id(), JobType.deploymentTo(zone)).map(Run::hasEnded).orElse(true))
+ controller.applications().deploymentTrigger().reTrigger(instance.id(),
+ JobType.deploymentTo(zone),
+ "Data plane tokens changed");
+ });
+ }
+ }
+ });
+ }
+
/**
* Generates a token using tenant name as the check access context.
* Persists the token fingerprint and check access hash, but not the token value
@@ -62,10 +192,11 @@ public class DataplaneTokenService {
TokenDomain tokenDomain = TokenDomain.of("Vespa Cloud tenant data plane:%s".formatted(tenantName.value()));
Token token = TokenGenerator.generateToken(tokenDomain, TOKEN_PREFIX, TOKEN_BYTES);
TokenCheckHash checkHash = TokenCheckHash.of(token, CHECK_HASH_BYTES);
+ Instant now = controller.clock().instant();
DataplaneTokenVersions.Version newTokenVersion = new DataplaneTokenVersions.Version(
FingerPrint.of(token.fingerprint().toDelimitedHexString()),
checkHash.toHexString(),
- controller.clock().instant(),
+ now,
Optional.ofNullable(expiration),
principal.getName());
@@ -81,18 +212,18 @@ public class DataplaneTokenService {
.toList();
dataplaneTokenVersions = Stream.concat(
dataplaneTokenVersions.stream().filter(t -> !Objects.equals(t.tokenId(), tokenId)),
- Stream.of(new DataplaneTokenVersions(tokenId, versions)))
+ Stream.of(new DataplaneTokenVersions(tokenId, versions, now)))
.toList();
} else {
- DataplaneTokenVersions newToken = new DataplaneTokenVersions(tokenId, List.of(newTokenVersion));
+ DataplaneTokenVersions newToken = new DataplaneTokenVersions(tokenId, List.of(newTokenVersion), now);
dataplaneTokenVersions = Stream.concat(dataplaneTokenVersions.stream(), Stream.of(newToken)).toList();
}
curator.writeDataplaneTokens(tenantName, dataplaneTokenVersions);
-
- // Return the data plane token including the secret token.
- return new DataplaneToken(tokenId, FingerPrint.of(token.fingerprint().toDelimitedHexString()),
- token.secretTokenString(), Optional.ofNullable(expiration));
}
+
+ // Return the data plane token including the secret token.
+ return new DataplaneToken(tokenId, FingerPrint.of(token.fingerprint().toDelimitedHexString()),
+ token.secretTokenString(), Optional.ofNullable(expiration));
}
/**
@@ -110,9 +241,13 @@ public class DataplaneTokenService {
if (versions.isEmpty()) {
dataplaneTokenVersions = dataplaneTokenVersions.stream().filter(t -> !Objects.equals(t.tokenId(), tokenId)).toList();
} else {
- boolean fingerPrintExists = existingToken.get().tokenVersions().stream().anyMatch(v -> v.fingerPrint().equals(tokenFingerprint));
- if (fingerPrintExists) {
- dataplaneTokenVersions = Stream.concat(dataplaneTokenVersions.stream().filter(t -> !Objects.equals(t.tokenId(), tokenId)), Stream.of(new DataplaneTokenVersions(tokenId, versions))).toList();
+ Optional<Version> existingVersion = existingToken.get().tokenVersions().stream().filter(v -> v.fingerPrint().equals(tokenFingerprint)).findAny();
+ if (existingVersion.isPresent()) {
+ Instant now = controller.clock().instant();
+ // If we removed an expired token, we keep the old lastUpdated timestamp.
+ Instant lastUpdated = existingVersion.get().expiration().map(now::isAfter).orElse(false) ? existingToken.get().lastUpdated() : now;
+ dataplaneTokenVersions = Stream.concat(dataplaneTokenVersions.stream().filter(t -> !Objects.equals(t.tokenId(), tokenId)),
+ Stream.of(new DataplaneTokenVersions(tokenId, versions, lastUpdated))).toList();
} else {
throw new IllegalArgumentException("Fingerprint does not exist: " + tokenFingerprint);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java
index c6eaf5abef7..839dbf76faa 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java
@@ -1,7 +1,8 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.deployment;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.container.jdisc.EmptyResponse;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.ThreadedHttpRequestHandler;
@@ -53,7 +54,12 @@ public class BadgeApiHandler extends ThreadedHttpRequestHandler {
Method method = request.getMethod();
try {
return switch (method) {
- case GET -> get(request);
+ case OPTIONS -> new SvgHttpResponse("") {{
+ headers().add("Allow", "GET, HEAD, OPTIONS");
+ headers().add("Access-Control-Allow-Origin", "*");
+ headers().add("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
+ }};
+ case HEAD, GET -> get(request);
default -> ErrorResponse.methodNotAllowed("Method '" + method + "' is unsupported");
};
} catch (IllegalArgumentException|IllegalStateException e) {
@@ -98,20 +104,20 @@ public class BadgeApiHandler extends ThreadedHttpRequestHandler {
}
private HttpResponse cachedResponse(Key key, Instant now, Supplier<String> badge) {
- return svgResponse(badgeCache.compute(key, (__, value) -> {
+ return new SvgHttpResponse(badgeCache.compute(key, (__, value) -> {
return value != null && value.expiry.isAfter(now) ? value : new Value(badge.get(), now);
}).badgeSvg);
}
- private static HttpResponse svgResponse(String svg) {
- return new HttpResponse(200) {
- @Override public void render(OutputStream outputStream) throws IOException {
- outputStream.write(svg.getBytes(UTF_8));
- }
- @Override public String getContentType() {
- return "image/svg+xml; charset=UTF-8";
- }
- };
+ private static class SvgHttpResponse extends HttpResponse {
+ private final String svg;
+ SvgHttpResponse(String svg) { super(200); this.svg = svg; }
+ @Override public void render(OutputStream outputStream) throws IOException {
+ outputStream.write(svg.getBytes(UTF_8));
+ }
+ @Override public String getContentType() {
+ return "image/svg+xml";
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java
index ae1949e2214..41b5c833ec8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.deployment;
import com.yahoo.config.provision.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/CliApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/CliApiHandler.java
index c67d0d04938..150acd297c2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/CliApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/CliApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.deployment;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
index 4e3a8b7caf0..edfa4d01d78 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.deployment;
import com.yahoo.component.Version;
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 c25502ab9bf..0a466b7ffe8 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.filter;
import com.auth0.jwt.JWT;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
index cef6840dfe1..115467ac805 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.filter;
import com.yahoo.component.annotation.Inject;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilter.java
index e840b70a95a..114dfc8420c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilter.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.filter;
import com.yahoo.component.annotation.Inject;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
index 5eaa6d7af1d..7173b086b79 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilter.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.filter;
import ai.vespa.hosted.api.Method;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java
index 7284bc70bfa..400576abfea 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.flags;
import com.yahoo.container.jdisc.HttpRequest;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiHandler.java
index 3c0ec666415..4f12f00eace 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.horizon;
import com.yahoo.component.annotation.Inject;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/TsdbQueryRewriter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/TsdbQueryRewriter.java
index 5953c51782a..2f3957af70d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/TsdbQueryRewriter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/horizon/TsdbQueryRewriter.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.horizon;
import com.fasterxml.jackson.databind.JsonNode;
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 1efccb8afe4..701761895c3 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.os;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java
index bc83eeb73c1..2a6778870b1 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.routing;
import com.yahoo.config.provision.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/FlagsClient.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/FlagsClient.java
index 6327a6262ba..2b53b1a32f5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/FlagsClient.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/FlagsClient.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.systemflags;
import ai.vespa.util.http.hc4.SslConnectionSocketFactory;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/FlagsClientException.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/FlagsClientException.java
index 1fe97fed2c7..e1b3da65e6e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/FlagsClientException.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/FlagsClientException.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.systemflags;
import java.util.OptionalInt;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResult.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResult.java
index 872202dc222..c006fa13223 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResult.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResult.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.systemflags;
import com.fasterxml.jackson.databind.JsonNode;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployer.java
index 2c38066eddd..0fa800e7367 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.systemflags;
import com.yahoo.concurrent.DaemonThreadFactory;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsHandler.java
index bb285b8b742..6318dc8c6fa 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.systemflags;
import com.yahoo.component.annotation.Inject;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
index 3811ec22555..11a5e178703 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.user;
import com.yahoo.component.annotation.Inject;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializer.java
index c3acf01a53e..46de4b7a348 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializer.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.user;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
index 7978e64482b..90792e9febe 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.zone.v1;
import com.yahoo.config.provision.Environment;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/package-info.java
index 6c27f12954a..c5b29dad8b9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* @author mpolden
*/
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
index 89a2067837b..722bdac2101 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiHandler.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.zone.v2;
import ai.vespa.http.HttpURL;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/package-info.java
index 9cb62748b63..7902c38982c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* @author mpolden
*/
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/EndpointConfig.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/EndpointConfig.java
new file mode 100644
index 00000000000..555fd024e47
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/EndpointConfig.java
@@ -0,0 +1,30 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.routing;
+
+/**
+ * Endpoint configurations supported for an application.
+ *
+ * @author mpolden
+ */
+public enum EndpointConfig {
+
+ /** Only legacy endpoints will be published in DNS. Certificate will contain both legacy and generated names, and is never assigned from a pool */
+ legacy,
+
+ /** Legacy and generated endpoints will be published in DNS. Certificate will contain both legacy and generated names, and is never assigned from a pool */
+ combined,
+
+ /** Only generated endpoints will be published in DNS. Certificate will contain generated names only. Certificate is assigned from a pool */
+ generated;
+
+ /** Returns whether this config supports legacy endpoints */
+ public boolean supportsLegacy() {
+ return this == legacy || this == combined;
+ }
+
+ /** Returns whether this config supports generated endpoints */
+ public boolean supportsGenerated() {
+ return this == combined || this == generated;
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpointList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpointList.java
index 62734091a57..af1abff142b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpointList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/GeneratedEndpointList.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import com.yahoo.collections.AbstractFilteringList;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/PreparedEndpoints.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/PreparedEndpoints.java
index 62dc8eab1c7..63b17a087f2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/PreparedEndpoints.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/PreparedEndpoints.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
@@ -27,13 +28,13 @@ import java.util.stream.Collectors;
public record PreparedEndpoints(DeploymentId deployment,
EndpointList endpoints,
List<AssignedRotation> rotations,
- Optional<EndpointCertificate> certificate) {
+ EndpointCertificate certificate) {
- public PreparedEndpoints(DeploymentId deployment, EndpointList endpoints, List<AssignedRotation> rotations, Optional<EndpointCertificate> certificate) {
+ public PreparedEndpoints(DeploymentId deployment, EndpointList endpoints, List<AssignedRotation> rotations, EndpointCertificate certificate) {
this.deployment = Objects.requireNonNull(deployment);
this.endpoints = Objects.requireNonNull(endpoints);
this.rotations = List.copyOf(Objects.requireNonNull(rotations));
- this.certificate = Objects.requireNonNull(certificate);
+ this.certificate = requireMatchingSans(certificate, endpoints);
}
/** Returns the endpoints contained in this as {@link com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint} */
@@ -100,4 +101,15 @@ public record PreparedEndpoints(DeploymentId deployment,
};
}
+ private static EndpointCertificate requireMatchingSans(EndpointCertificate certificate, EndpointList endpoints) {
+ Objects.requireNonNull(certificate);
+ for (var endpoint : endpoints.not().scope(Endpoint.Scope.weighted)) { // Weighted endpoints are not present in certificate
+ if (!certificate.sanMatches(endpoint.dnsName())) {
+ throw new IllegalArgumentException(endpoint + " has no matching SAN. Certificate contains " +
+ certificate.requestedDnsSans());
+ }
+ }
+ return certificate;
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingId.java
index 21c8b5aeb87..50e54423f9a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingId.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingId.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import com.yahoo.config.provision.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
index a21c6548a0b..e93bc637a6b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicies.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import ai.vespa.http.DomainName;
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 39b25f76cce..fc72f3ed663 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import ai.vespa.http.DomainName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyId.java
index 1c0b41155fd..ea8ae6820c9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyId.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyId.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import com.yahoo.config.provision.ApplicationId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyList.java
index 68ccd9143df..f96275a0d5a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingPolicyList.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import com.yahoo.collections.AbstractFilteringList;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingStatus.java
index de16089e735..bd46760cc3e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/RoutingStatus.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import java.time.Instant;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/ZoneRoutingPolicy.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/ZoneRoutingPolicy.java
index a404be76507..3ca72a7dd67 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/ZoneRoutingPolicy.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/ZoneRoutingPolicy.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import com.yahoo.config.provision.zone.ZoneId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
index 99f60735f6e..50e65187835 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/DeploymentRoutingContext.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.context;
import com.yahoo.config.application.api.DeploymentSpec;
@@ -45,7 +45,7 @@ public abstract class DeploymentRoutingContext implements RoutingContext {
*
* @return the container endpoints relevant for this deployment, as declared in deployment spec
*/
- public final PreparedEndpoints prepare(BasicServicesXml services, Optional<EndpointCertificate> certificate, LockedApplication application) {
+ public final PreparedEndpoints prepare(BasicServicesXml services, EndpointCertificate certificate, LockedApplication application) {
return routing.prepare(deployment, services, certificate, application);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveZoneRoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveZoneRoutingContext.java
index 75009e0b37a..201baa78437 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveZoneRoutingContext.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/ExclusiveZoneRoutingContext.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.context;
import com.yahoo.config.provision.zone.RoutingMethod;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/RoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/RoutingContext.java
index 6f43416b9b5..84315e319ec 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/RoutingContext.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/RoutingContext.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.context;
import com.yahoo.config.provision.zone.RoutingMethod;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedZoneRoutingContext.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedZoneRoutingContext.java
index bbd2e6bbb41..00ab41fc61c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedZoneRoutingContext.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/context/SharedZoneRoutingContext.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.context;
import com.yahoo.config.provision.zone.RoutingMethod;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/Rotation.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/Rotation.java
index ea97b1da4de..d94124709f7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/Rotation.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/Rotation.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.rotation;
import com.yahoo.text.Text;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationId.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationId.java
index 95cebf7ea78..a99c9ada0f9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationId.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationId.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.rotation;
/**
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationLock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationLock.java
index 39fc70aac64..3043ec146a6 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationLock.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationLock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.rotation;
import com.yahoo.transaction.Mutex;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepository.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepository.java
index 79eb115c977..c70826161da 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepository.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepository.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.rotation;
import com.yahoo.config.application.api.DeploymentSpec;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationState.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationState.java
index 19e816a0b51..53ebbd1e95e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationState.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationState.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.rotation;
/**
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationStatus.java
index 89247ca2a31..7ad841c96f9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationStatus.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.rotation;
import com.yahoo.config.provision.zone.ZoneId;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java
index d24d66ea2a0..4c2f2627026 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControl.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControlRequests.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControlRequests.java
index 682927cc475..081c72f7e25 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControlRequests.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AccessControlRequests.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzAccessControlRequests.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzAccessControlRequests.java
index 4ece2b9a691..ccf3db5d204 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzAccessControlRequests.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzAccessControlRequests.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.component.annotation.Inject;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzCredentials.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzCredentials.java
index fd74626a6cf..aa8ab8375b0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzCredentials.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzCredentials.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.vespa.athenz.api.AthenzDomain;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzTenantSpec.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzTenantSpec.java
index db5b5930166..70799250773 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzTenantSpec.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/AthenzTenantSpec.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/Auth0Credentials.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/Auth0Credentials.java
index d8f4370ebcf..aaf2b5a9367 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/Auth0Credentials.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/Auth0Credentials.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.vespa.hosted.controller.api.role.Role;
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 0e7fefba15d..051298d4f8b 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.component.annotation.Inject;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControlRequests.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControlRequests.java
index 809d22fb196..697b324dc3e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControlRequests.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControlRequests.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudTenantSpec.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudTenantSpec.java
index 5ca005b6582..f746df2b71e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudTenantSpec.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudTenantSpec.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudUserSessionManager.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudUserSessionManager.java
index e2b5083abae..c2a505fc185 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudUserSessionManager.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudUserSessionManager.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/Credentials.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/Credentials.java
index e8f5e6336ef..d2ad1433413 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/Credentials.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/Credentials.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import java.security.Principal;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/TenantSpec.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/TenantSpec.java
index 9ad2254bf93..8f74c59941d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/TenantSpec.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/TenantSpec.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.config.provision.TenantName;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccess.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccess.java
index e5304a05d02..ae1231fa450 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccess.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccess.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.support.access;
import java.time.Instant;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessChange.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessChange.java
index 93659742538..6b6c869d400 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessChange.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessChange.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.support.access;
import java.time.Instant;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessControl.java
index 1561a486fda..7e3dc77822f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessControl.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessControl.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.support.access;
import com.yahoo.transaction.Mutex;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessGrant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessGrant.java
index 76ceb6400bb..ee57f14c71b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessGrant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/support/access/SupportAccessGrant.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.support.access;
import java.security.cert.X509Certificate;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java
index 286d8c997fd..a91b5ad72ed 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/ControllerSslContextFactoryProvider.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.tls;
import com.google.common.collect.Sets;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/package-info.java
index e0edf2c2100..be84cfdfca0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tls/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
/**
* @author mpolden
*/
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/CertifiedOsVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/CertifiedOsVersion.java
index 9402165f112..0a790be1ab8 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/CertifiedOsVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/CertifiedOsVersion.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
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 760ae894e37..4e4f00e6d4b 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClient.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClient.java
index 363a3e81c3f..e0d6dcfe36e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClient.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClient.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.vespa.hosted.controller.api.integration.maven.ArtifactId;
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 707fa85b6b6..c3b8a825cb8 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
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 6ed716bb4bf..68b3b01f75a 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
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 f90cee65058..f031b906dc0 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.google.common.collect.ImmutableMap;
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 e9785216376..ea9322b5fab 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
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 9fb87735b42..28938577876 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionTarget.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionTarget.java
index 9b53d04c80f..6d3aac9475e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionTarget.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VersionTarget.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
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 b03098bf18f..9921102d460 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
@@ -53,9 +53,15 @@ public record VespaVersion(Version version,
if (productionOnThis.with(UpgradePolicy.canary).unpinned().size() < all.withProductionDeployment().with(UpgradePolicy.canary).unpinned().size())
return Confidence.low;
- // 'high' if 90% of all unpinned default upgrade applications upgraded
- if (productionOnThis.with(UpgradePolicy.defaultPolicy).unpinned().groupingBy(TenantAndApplicationId::from).size() >=
- all.withProductionDeployment().with(UpgradePolicy.defaultPolicy).unpinned().groupingBy(TenantAndApplicationId::from).size() * 0.9)
+ // 'low' unless at least half of all canary applications are upgraded
+ if (productionOnThis.with(UpgradePolicy.canary).size() < all.withProductionDeployment().with(UpgradePolicy.canary).size() * 0.5)
+ return Confidence.low;
+
+ // 'high' if 90% of all unpinned default upgrade applications, and 50% of all of them, have upgraded
+ if ( productionOnThis.with(UpgradePolicy.defaultPolicy).unpinned().groupingBy(TenantAndApplicationId::from).size() >=
+ all.withProductionDeployment().with(UpgradePolicy.defaultPolicy).unpinned().groupingBy(TenantAndApplicationId::from).size() * 0.9
+ && productionOnThis.with(UpgradePolicy.defaultPolicy).groupingBy(TenantAndApplicationId::from).size() >=
+ all.withProductionDeployment().with(UpgradePolicy.defaultPolicy).groupingBy(TenantAndApplicationId::from).size() * 0.5)
return Confidence.high;
return Confidence.normal;
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 26890cfd8f8..bf66425fe81 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/package-info.java
index 0d6f268b626..73ca7d2b42f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/package-info.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/package-info.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
package com.yahoo.vespa.hosted.controller.versions;
diff --git a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.athenz.config.athenz.def b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.athenz.config.athenz.def
index 672e773591e..c9c93705c95 100644
--- a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.athenz.config.athenz.def
+++ b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.athenz.config.athenz.def
@@ -1,4 +1,4 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
namespace=vespa.hosted.controller.athenz.config
# URL to ZMS API endpoint
diff --git a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.controller.def b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.controller.def
index c63e429abcd..fb17d33ae16 100644
--- a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.controller.def
+++ b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.controller.def
@@ -1,7 +1,7 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
# Generic config for controller
namespace=vespa.hosted.controller.config
steprunner.testerapp.tenantCdBundle string default="cloud-tenant-cd"
-steprunner.testerapp.runtimeProviderClass string default="ai.vespa.hosted.cd.cloud.impl.VespaTestRuntimeProvider" \ No newline at end of file
+steprunner.testerapp.runtimeProviderClass string default="ai.vespa.hosted.cd.cloud.impl.VespaTestRuntimeProvider"
diff --git a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.core-dump-token-resealing.def b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.core-dump-token-resealing.def
index eec6e482cf9..619b0ee5bdf 100644
--- a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.core-dump-token-resealing.def
+++ b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.core-dump-token-resealing.def
@@ -1,4 +1,4 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
namespace=vespa.hosted.controller.config
# Key name for private key used for re-sealing decryption tokens.
diff --git a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.well-known-folder.def b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.well-known-folder.def
index 655d570bc58..2717a2753a0 100644
--- a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.well-known-folder.def
+++ b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.config.well-known-folder.def
@@ -1,5 +1,5 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
# Config for serving content from .well-known directory
namespace=vespa.hosted.controller.config
-securityTxt string \ No newline at end of file
+securityTxt string
diff --git a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.maven.repository.config.maven-repository.def b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.maven.repository.config.maven-repository.def
index 53b1e79900f..c83d13770df 100644
--- a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.maven.repository.config.maven-repository.def
+++ b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.maven.repository.config.maven-repository.def
@@ -1,4 +1,4 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
namespace=vespa.hosted.controller.maven.repository.config
diff --git a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.tls.config.tls.def b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.tls.config.tls.def
index 7130a5c5dc7..2c3a7f9eb8b 100644
--- a/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.tls.config.tls.def
+++ b/controller-server/src/main/resources/configdefinitions/vespa.hosted.controller.tls.config.tls.def
@@ -1,4 +1,4 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
namespace=vespa.hosted.controller.tls.config
# Path to the CA trust store
diff --git a/controller-server/src/main/resources/configdefinitions/vespa.hosted.rotation.config.rotations.def b/controller-server/src/main/resources/configdefinitions/vespa.hosted.rotation.config.rotations.def
index 77863e65a55..324426e1860 100644
--- a/controller-server/src/main/resources/configdefinitions/vespa.hosted.rotation.config.rotations.def
+++ b/controller-server/src/main/resources/configdefinitions/vespa.hosted.rotation.config.rotations.def
@@ -1,4 +1,4 @@
-# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
namespace=vespa.hosted.rotation.config
rotations{} string
diff --git a/controller-server/src/main/resources/mail/cloud-trial-notification.vm b/controller-server/src/main/resources/mail/cloud-trial-notification.vm
new file mode 100644
index 00000000000..c1ba394bf8e
--- /dev/null
+++ b/controller-server/src/main/resources/mail/cloud-trial-notification.vm
@@ -0,0 +1,3 @@
+<p>
+ $esc.html($cloudTrialMessage)
+</p> \ No newline at end of file
diff --git a/controller-server/src/main/resources/mail/default-mail-content.vm b/controller-server/src/main/resources/mail/default-mail-content.vm
new file mode 100644
index 00000000000..02de98b900d
--- /dev/null
+++ b/controller-server/src/main/resources/mail/default-mail-content.vm
@@ -0,0 +1,131 @@
+<tbody>
+<tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 25px 0px 25px;
+ padding-top: 0px;
+ padding-right: 50px;
+ padding-bottom: 0px;
+ padding-left: 50px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+ <h1
+ style="
+ text-align: center;
+ color: #000000;
+ line-height: 32px;
+ "
+ >
+ $esc.html($mailTitle)
+ </h1>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 25px 0px 25px;
+ padding-top: 0px;
+ padding-right: 50px;
+ padding-bottom: 0px;
+ padding-left: 50px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+
+ #parse($mailMessageTemplate)
+
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="center"
+ vertical-align="middle"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ word-break: break-word;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="border-collapse: separate; line-height: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ bgcolor="#005A8E"
+ role="presentation"
+ style="
+ border: none;
+ border-radius: 100px;
+ cursor: auto;
+ mso-padding-alt: 15px 25px 15px 25px;
+ background: #005a8e;
+ "
+ valign="middle"
+ >
+ <a
+ href="$consoleLink"
+ style="
+ display: inline-block;
+ background: #005a8e;
+ color: #ffffff;
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 120%;
+ margin: 0;
+ text-decoration: none;
+ text-transform: none;
+ padding: 15px 25px 15px 25px;
+ mso-padding-alt: 0px;
+ border-radius: 100px;
+ "
+ target="_blank"
+ ><b style="font-weight: 700"
+ ><b style="font-weight: 700"
+ >Go to Console</b
+ ></b
+ ></a
+ >
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+</tr>
+</tbody> \ No newline at end of file
diff --git a/controller-server/src/main/resources/mail/mail-verification.tmpl b/controller-server/src/main/resources/mail/mail-verification.vm
index 8a473e74755..340895812ca 100644
--- a/controller-server/src/main/resources/mail/mail-verification.tmpl
+++ b/controller-server/src/main/resources/mail/mail-verification.vm
@@ -366,7 +366,7 @@
"
>
<p style="margin: 10px 0; text-align: center">
- You have entered the email address <b>%{email}</b> in
+ You have entered the email address <b>$esc.html($email)</b> in
Vespa Cloud.&nbsp;
</p>
<p style="margin: 10px 0; text-align: center">
@@ -411,7 +411,7 @@
valign="middle"
>
<a
- href="https://%{consoleUrl}/verify?code=%{code}"
+ href="$verifyLink"
style="
display: inline-block;
background: #3b9fde;
@@ -471,9 +471,9 @@
<a
target="_blank"
rel="noopener noreferrer"
- href="https://%{consoleUrl}/verify?code=%{code}"
+ href="$verifyLink"
style="color: #3b9fde"
- >https://%{consoleUrl}/verify?code=%{code}</a
+ >$verifyLink</a
>
</p>
</div>
diff --git a/controller-server/src/main/resources/mail/mail.vm b/controller-server/src/main/resources/mail/mail.vm
new file mode 100644
index 00000000000..1dbec781b3a
--- /dev/null
+++ b/controller-server/src/main/resources/mail/mail.vm
@@ -0,0 +1,516 @@
+<!DOCTYPE html>
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:v="urn:schemas-microsoft-com:vml"
+ xmlns:o="urn:schemas-microsoft-com:office:office"
+>
+ <head>
+ <title></title>
+ <!--[if !mso]><!-->
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <!--<![endif]-->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <style type="text/css">
+ #outlook a {
+ padding: 0;
+ }
+
+ body {
+ margin: 0;
+ padding: 0;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ }
+
+ table,
+ td {
+ border-collapse: collapse;
+ mso-table-lspace: 0pt;
+ mso-table-rspace: 0pt;
+ }
+
+ img {
+ border: 0;
+ height: auto;
+ line-height: 100%;
+ outline: none;
+ text-decoration: none;
+ -ms-interpolation-mode: bicubic;
+ }
+
+ p {
+ display: block;
+ margin: 13px 0;
+ }
+ </style>
+ <!--[if mso]>
+ <noscript>
+ <xml>
+ <o:OfficeDocumentSettings>
+ <o:AllowPNG />
+ <o:PixelsPerInch>96</o:PixelsPerInch>
+ </o:OfficeDocumentSettings>
+ </xml>
+ </noscript>
+ <![endif]-->
+ <!--[if lte mso 11]>
+ <style type="text/css">
+ .mj-outlook-group-fix {
+ width: 100% !important;
+ }
+ </style>
+ <![endif]-->
+ <!--[if !mso]><!-->
+ <link
+ href="https://fonts.googleapis.com/css?family=Open Sans"
+ rel="stylesheet"
+ type="text/css"
+ />
+ <style type="text/css">
+ @import url(https://fonts.googleapis.com/css?family=Open Sans);
+ </style>
+ <!--<![endif]-->
+ <style type="text/css">
+ @media only screen and (min-width: 480px) {
+ .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ }
+ </style>
+ <style media="screen and (min-width:480px)">
+ .moz-text-html .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ </style>
+ <style type="text/css">
+ [owa] .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ </style>
+ <style type="text/css">
+ @media only screen and (max-width: 480px) {
+ table.mj-full-width-mobile {
+ width: 100% !important;
+ }
+
+ td.mj-full-width-mobile {
+ width: auto !important;
+ }
+ }
+ </style>
+ </head>
+
+ <body style="word-spacing: normal; background-color: #f2f7fa">
+ <div style="background-color: #f2f7fa">
+ <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div style="margin: 0px auto; max-width: 600px">
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 0px;
+ padding-top: 0px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 0px 0px 25px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 11px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+ <br />
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div
+ style="
+ background: #ffffff;
+ background-color: #ffffff;
+ margin: 0px auto;
+ max-width: 600px;
+ "
+ >
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="background: #ffffff; background-color: #ffffff; width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0;
+ padding-bottom: 0px;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 0px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 0px;
+ padding-right: 0px;
+ padding-bottom: 40px;
+ padding-left: 0px;
+ word-break: break-word;
+ "
+ >
+ <p
+ style="
+ border-top: solid 8px #005a8e;
+ font-size: 1px;
+ margin: 0px auto;
+ width: 100%;
+ "
+ ></p>
+ <!--[if mso | IE
+ ]><table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ style="
+ border-top: solid 8px #005a8e;
+ font-size: 1px;
+ margin: 0px auto;
+ width: 600px;
+ "
+ role="presentation"
+ width="600px"
+ >
+ <tr>
+ <td style="height: 0; line-height: 0">
+ &nbsp;
+ </td>
+ </tr>
+ </table><!
+ [endif]-->
+ </td>
+ </tr>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="
+ border-collapse: collapse;
+ border-spacing: 0px;
+ "
+ >
+ <tbody>
+ <tr>
+ <td style="width: 121px">
+ <img
+ alt=""
+ height="auto"
+ src="https://data.vespa.oath.cloud/assets/vespa-cloud-logo.png"
+ style="
+ border: none;
+ display: block;
+ outline: none;
+ text-decoration: none;
+ height: auto;
+ width: 100%;
+ font-size: 13px;
+ "
+ width="121"
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div
+ style="
+ background: #ffffff;
+ background-color: #ffffff;
+ margin: 0px auto;
+ max-width: 600px;
+ "
+ >
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="background: #ffffff; background-color: #ffffff; width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 70px;
+ padding-top: 30px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+
+ #parse($mailBodyTemplate)
+
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div style="margin: 0px auto; max-width: 600px">
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 0px;
+ padding-top: 20px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 0px 20px 0px 20px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 11px;
+ line-height: 22px;
+ text-align: center;
+ color: #797e82;
+ "
+ >
+ <p style="margin: 10px 0">
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="$privacyPolicyLink"
+ ><span style="color: #005a8e"
+ >Yahoo Privacy Policy</span
+ ></a
+ ><span style="color: #797e82"
+ >&nbsp; &nbsp;|&nbsp; &nbsp;</span
+ ><a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="$termsOfServiceLink"
+ ><span style="color: #005a8e"
+ >Terms of Service</span
+ ></a
+ ><span style="color: #797e82"
+ >&nbsp; &nbsp;|&nbsp; &nbsp;</span
+ ><a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="$supportLink"
+ ><span style="color: #005a8e">Support</span></a
+ >
+ </p>
+ <p style="margin: 10px 0">
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: inherit; text-decoration: none"
+ href="$accountNotificationLink"
+ >Click
+ <span style="color: #005a8e"><u>here</u></span>
+ to manage your notifications setting.</a
+ ><br />
+ </p>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </div>
+ </body>
+</html>
diff --git a/controller-server/src/main/resources/mail/notification-message.vm b/controller-server/src/main/resources/mail/notification-message.vm
new file mode 100644
index 00000000000..29673d38420
--- /dev/null
+++ b/controller-server/src/main/resources/mail/notification-message.vm
@@ -0,0 +1,6 @@
+<p>
+ $esc.html($notificationHeader):
+</p>
+#foreach( $i in $notificationItems )
+<p>$i</p>
+#end
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 6901b6f93c9..345c880eaea 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.google.common.collect.Sets;
@@ -28,6 +28,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistryMoc
import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import com.yahoo.vespa.hosted.controller.api.integration.dns.LatencyAliasTarget;
@@ -72,7 +73,6 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import static com.yahoo.config.provision.SystemName.main;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.devAwsUsEast2a;
@@ -951,8 +951,6 @@ public class ControllerTest {
// Create app1
var context1 = tester.newDeploymentContext("tenant1", "app1", "default");
var prodZone = ZoneId.from("prod", "us-west-1");
- var stagingZone = ZoneId.from("staging", "us-east-3");
- var testZone = ZoneId.from("test", "us-east-1");
tester.controllerTester().zoneRegistry().exclusiveRoutingIn(ZoneApiMock.from(prodZone));
var applicationPackage = new ApplicationPackageBuilder().athenzIdentity(AthenzDomain.from("domain"), AthenzService.from("service"))
.region(prodZone.region())
@@ -961,16 +959,23 @@ public class ControllerTest {
context1.submit(applicationPackage).deploy();
var cert = certificate.apply(context1.instance());
assertTrue(cert.isPresent(), "Provisions certificate in " + Environment.prod);
- assertEquals(Stream.concat(Stream.of("vznqtz7a5ygwjkbhhj7ymxvlrekgt4l6g.vespa.oath.cloud",
- "app1.tenant1.global.vespa.oath.cloud",
- "*.app1.tenant1.global.vespa.oath.cloud"),
- Stream.of(prodZone, testZone, stagingZone)
- .flatMap(zone -> Stream.of("", "*.")
- .map(prefix -> prefix + "app1.tenant1." + zone.region().value() +
- (zone.environment() == Environment.prod ? "" : "." + zone.environment().value()) +
- ".vespa.oath.cloud")))
- .collect(Collectors.toUnmodifiableSet()),
- Set.copyOf(tester.controllerTester().serviceRegistry().endpointCertificateMock().dnsNamesOf(cert.get().rootRequestId())));
+ assertEquals(List.of("*.app1.tenant1.global.vespa.oath.cloud",
+ "*.app1.tenant1.us-east-1.test.vespa.oath.cloud",
+ "*.app1.tenant1.us-east-3.staging.vespa.oath.cloud",
+ "*.app1.tenant1.us-west-1.vespa.oath.cloud",
+ "*.f5549014.a.vespa.oath.cloud",
+ "*.f5549014.g.vespa.oath.cloud",
+ "*.f5549014.z.vespa.oath.cloud",
+ "app1.tenant1.global.vespa.oath.cloud",
+ "app1.tenant1.us-east-1.test.vespa.oath.cloud",
+ "app1.tenant1.us-east-3.staging.vespa.oath.cloud",
+ "app1.tenant1.us-west-1.vespa.oath.cloud",
+ "vznqtz7a5ygwjkbhhj7ymxvlrekgt4l6g.vespa.oath.cloud"),
+ tester.controllerTester().serviceRegistry().endpointCertificateMock()
+ .dnsNamesOf(cert.get().rootRequestId())
+ .stream()
+ .sorted()
+ .toList());
// Next deployment reuses certificate
context1.submit(applicationPackage).deploy();
@@ -1086,12 +1091,15 @@ public class ControllerTest {
var zone3 = ZoneId.from("prod", "eu-west-1");
tester.controllerTester().zoneRegistry()
.exclusiveRoutingIn(ZoneApiMock.from(zone1), ZoneApiMock.from(zone2), ZoneApiMock.from(zone3));
+ tester.controller().dataplaneTokenService().generateToken(context.application().id().tenant(), TokenId.of("token-1"), null, () -> "foo");
+ tester.clock().advance(Duration.ofSeconds(1));
+ tester.controller().dataplaneTokenService().generateToken(context.application().id().tenant(), TokenId.of("token-2"), null, () -> "foo");
var applicationPackageBuilder = new ApplicationPackageBuilder()
.region(zone1.region())
.region(zone2.region())
.region(zone3.region())
- .container("qrs", AuthMethod.mtls)
+ .container("qrs", AuthMethod.mtls, AuthMethod.token)
.container("default", AuthMethod.mtls)
.endpoint("default", "default")
.endpoint("foo", "qrs")
@@ -1108,6 +1116,8 @@ public class ControllerTest {
"application.tenant." + zone.region().value() + ".vespa.oath.cloud"),
tester.configServer().containerEndpointNames(context.deploymentIdIn(zone)),
"Expected container endpoints in " + zone);
+ assertEquals(Map.of(TokenId.of("token-1"), tester.clock().instant().minusSeconds(1)),
+ context.deployment(zone).dataPlaneTokens());
}
assertEquals(Set.of("application.tenant.global.vespa.oath.cloud",
"foo.application.tenant.global.vespa.oath.cloud",
@@ -1335,6 +1345,7 @@ public class ControllerTest {
Type.testPackage,
Level.warning,
NotificationSource.from(app.application().id()),
+ "There are problems with tests for [application](https://console.tld/tenant/tenant/application/application/prod/instance)",
List.of("test package has staging tests, so it should also include staging setup",
"see https://docs.vespa.ai/en/testing.html for details on how to write system tests for Vespa"))),
tester.controller().notificationsDb().listNotifications(NotificationSource.from(app.application().id()), true));
@@ -1548,7 +1559,7 @@ public class ControllerTest {
DeploymentId deployment = context.deploymentIdIn(ZoneId.from("prod", "us-west-1"));
DeploymentData deploymentData = new DeploymentData(deployment.applicationId(), deployment.zoneId(), InputStream::nullInputStream, Version.fromString("6.1"),
() -> DeploymentEndpoints.none, Optional.empty(), Optional.empty(),
- Quota::unlimited, List.of(), List.of(), Optional::empty, List.of(), false);
+ Quota::unlimited, List.of(), List.of(), Optional::empty, () -> List.of(), false);
tester.configServer().deploy(deploymentData);
assertTrue(tester.configServer().application(deployment.applicationId(), deployment.zoneId()).isPresent());
tester.controller().applications().deactivate(deployment.applicationId(), deployment.zoneId());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
index d9b95a53a0e..7bdecca11c0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTester.java
@@ -1,11 +1,9 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
-import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.RoutingMethod;
@@ -20,6 +18,7 @@ import com.yahoo.vespa.athenz.api.OAuthCredentials;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
+import com.yahoo.vespa.hosted.controller.api.identifiers.ControllerVersion;
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.athenz.AthenzClientFactoryMock;
@@ -54,7 +53,6 @@ import com.yahoo.vespa.hosted.controller.security.Credentials;
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 com.yahoo.vespa.hosted.controller.api.identifiers.ControllerVersion;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import com.yahoo.yolean.concurrent.Sleeper;
@@ -289,14 +287,6 @@ public final class ControllerTester {
return controller().clock().instant().atOffset(ZoneOffset.UTC).getHour();
}
- public ZoneId toZone(Environment environment) {
- return switch (environment) {
- case dev, test -> ZoneId.from(environment, RegionName.from("us-east-1"));
- case staging -> ZoneId.from(environment, RegionName.from("us-east-3"));
- default -> ZoneId.from(environment, RegionName.from("us-west-1"));
- };
- }
-
public AthenzDomain createDomainWithAdmin(String domainName, AthenzUser user) {
AthenzDomain domain = new AthenzDomain(domainName);
athenzDb.getOrCreateDomain(domain).admin(user);
@@ -405,7 +395,7 @@ public final class ControllerTester {
RotationsConfig.Builder builder = new RotationsConfig.Builder();
for (int i = 1; i <= availableRotations; i++) {
String id = Text.format("%02d", i);
- builder = builder.rotations("rotation-id-" + id, "rotation-fqdn-" + id);
+ builder.rotations("rotation-id-" + id, "rotation-fqdn-" + id);
}
return new RotationsConfig(builder);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java
index 77145be4197..4fbf39f8d8b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller;
import com.yahoo.config.provision.SystemName;
@@ -9,15 +9,15 @@ import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.Email;
import com.yahoo.vespa.hosted.controller.tenant.PendingMailVerification;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
+import com.yahoo.vespa.hosted.controller.tenant.TenantBilling;
+import com.yahoo.vespa.hosted.controller.tenant.TenantContact;
import com.yahoo.vespa.hosted.controller.tenant.TenantContacts;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.net.URI;
import java.time.Duration;
import java.util.List;
-import static com.yahoo.yolean.Exceptions.uncheck;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -30,7 +30,7 @@ class MailVerifierTest {
private final ControllerTester tester = new ControllerTester(SystemName.Public);
private final MockMailer mailer = tester.serviceRegistry().mailer();
- private final MailVerifier mailVerifier = new MailVerifier(URI.create("https://dashboard.uri.example.com"), tester.controller().tenants(), mailer, tester.curator(), tester.clock());
+ private final MailVerifier mailVerifier = new MailVerifier(tester.serviceRegistry().consoleUrls(), tester.controller().tenants(), mailer, tester.curator(), tester.clock());
private static final TenantName tenantName = TenantName.from("scoober");
private static final String mail = "unverified@bar.com";
@@ -100,4 +100,29 @@ class MailVerifierTest {
assertTrue(tester.curator().getPendingMailVerification(resentVerification.get().getVerificationCode()).isPresent());
}
+ @Test
+ public void test_billing_mail_verification() {
+ var billingMail = "billing@foo.bar";
+ tester.controller().tenants().lockOrThrow(tenantName, LockedTenant.Cloud.class, lockedTenant -> {
+ var tenantBilling = TenantBilling.empty().withContact(TenantContact.empty().withEmail(new Email(billingMail, false)));
+ lockedTenant = lockedTenant.withInfo(lockedTenant.get().info().withBilling(tenantBilling));
+ tester.controller().tenants().store(lockedTenant);
+ });
+ mailVerifier.sendMailVerification(tenantName, billingMail, PendingMailVerification.MailType.BILLING);
+
+ // Assert written verification data
+ var writtenMailVerification = tester.curator().listPendingMailVerifications().get(0);
+ assertEquals(PendingMailVerification.MailType.BILLING, writtenMailVerification.getMailType());
+ assertEquals(tenantName, writtenMailVerification.getTenantName());
+ assertEquals(tester.clock().instant().plus(Duration.ofDays(7)), writtenMailVerification.getVerificationDeadline());
+ assertEquals(billingMail, writtenMailVerification.getMailAddress());
+
+ // Assert mail is verified
+ mailVerifier.verifyMail(writtenMailVerification.getVerificationCode());
+ assertTrue(tester.curator().listPendingMailVerifications().isEmpty());
+ var tenant = tester.controller().tenants().require(tenantName, CloudTenant.class);
+ var expectedBillingContact = TenantContact.empty().withEmail(new Email(billingMail, true));
+ assertEquals(expectedBillingContact, tenant.info().billingContact().contact());
+ }
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
index f2897c14ffe..b20da8ae4d9 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/DeploymentQuotaCalculatorTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -65,7 +65,7 @@ public class DeploymentQuotaCalculatorTest {
var existing_dev_deployment = new Application(TenantAndApplicationId.from(ApplicationId.defaultId()), Instant.EPOCH, DeploymentSpec.empty, ValidationOverrides.empty, Optional.empty(),
Optional.empty(), Optional.empty(), Optional.empty(), OptionalInt.empty(), new ApplicationMetrics(1, 1), Set.of(), OptionalLong.empty(), RevisionHistory.empty(),
List.of(new Instance(ApplicationId.defaultId()).withNewDeployment(ZoneId.from(Environment.dev, RegionName.defaultName()),
- RevisionId.forProduction(1), Version.emptyVersion, Instant.EPOCH, Map.of(), QuotaUsage.create(0.53d), CloudAccount.empty)));
+ RevisionId.forProduction(1), Version.emptyVersion, Instant.EPOCH, Map.of(), QuotaUsage.create(0.53d), CloudAccount.empty, List.of())));
Quota calculated = DeploymentQuotaCalculator.calculate(Quota.unlimited().withBudget(2), List.of(existing_dev_deployment), ApplicationId.defaultId(), ZoneId.defaultId(),
DeploymentSpec.fromXml(
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 fbc5567101f..cec48dd1598 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.config.provision.ApplicationId;
@@ -370,6 +370,24 @@ public class EndpointTest {
"dead2bad.deadbeef.a.vespa-app.cloud",
Endpoint.of(TenantAndApplicationId.from(instance1)).targetApplication(EndpointId.of("foo"), deployment)
.generatedFrom(ge2)
+ .routingMethod(RoutingMethod.exclusive).on(Port.tls()).in(SystemName.Public),
+ // Wildcard endpoint for zone
+ "*.deadbeef.z.vespa-app.cloud",
+ Endpoint.of(instance1)
+ .wildcardGenerated(ge1.applicationPart(), Endpoint.Scope.zone)
+ .certificateName()
+ .routingMethod(RoutingMethod.exclusive).on(Port.tls()).in(SystemName.Public),
+ // Wildcard endpoint for global
+ "*.deadbeef.g.vespa-app.cloud",
+ Endpoint.of(instance1)
+ .wildcardGenerated(ge1.applicationPart(), Endpoint.Scope.global)
+ .certificateName()
+ .routingMethod(RoutingMethod.exclusive).on(Port.tls()).in(SystemName.Public),
+ // Wildcard endpoint for application
+ "*.deadbeef.a.vespa-app.cloud",
+ Endpoint.of(instance1)
+ .wildcardGenerated(ge1.applicationPart(), Endpoint.Scope.application)
+ .certificateName()
.routingMethod(RoutingMethod.exclusive).on(Port.tls()).in(SystemName.Public)
);
tests.forEach((expected, endpoint) -> assertEquals(expected, endpoint.dnsName()));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiffTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiffTest.java
index a92c035b502..fbbd199aa05 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiffTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageDiffTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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 org.junit.jupiter.api.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 910d526a2ed..988a20b44ad 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.config.application.api.DeploymentSpec;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java
index 7d377ef6361..ff103ddddfa 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/BasicServicesXmlTest.java
@@ -1,6 +1,8 @@
+// Copyright Vespa.ai. 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.text.XML;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
import com.yahoo.vespa.hosted.controller.application.pkg.BasicServicesXml.Container;
import org.junit.jupiter.api.Test;
@@ -16,19 +18,23 @@ class BasicServicesXmlTest {
@Test
public void parse() {
assertServices(new BasicServicesXml(List.of()), "<services/>");
- assertServices(new BasicServicesXml(List.of(new Container("foo", List.of(Container.AuthMethod.mtls)),
- new Container("bar", List.of(Container.AuthMethod.mtls)))),
+ assertServices(new BasicServicesXml(List.of(new Container("foo", List.of(Container.AuthMethod.mtls), List.of()),
+ new Container("bar", List.of(Container.AuthMethod.mtls), List.of()),
+ new Container("container", List.of(Container.AuthMethod.mtls), List.of()))),
"""
<services>
<container id="foo"/>
<container id="bar"/>
+ <container/>
</services>
""");
assertServices(new BasicServicesXml(List.of(
new Container("foo",
List.of(Container.AuthMethod.mtls,
- Container.AuthMethod.token)),
- new Container("bar", List.of(Container.AuthMethod.mtls)))),
+ Container.AuthMethod.token),
+ List.of(TokenId.of("my-token"),
+ TokenId.of("other-token"))),
+ new Container("bar", List.of(Container.AuthMethod.mtls), List.of()))),
"""
<services>
<container id="foo">
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/LinesComparatorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/LinesComparatorTest.java
index 9fd15a2f55d..92506459728 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/LinesComparatorTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/LinesComparatorTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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 org.junit.jupiter.api.Test;
@@ -109,4 +109,4 @@ public class LinesComparatorTest {
assertEquals(Optional.ofNullable(expected),
LinesComparator.diff(left.lines().toList(), right.lines().toList()));
}
-} \ No newline at end of file
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/TestPackageTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/TestPackageTest.java
index 450b7f5e6cd..3cc05df0953 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/TestPackageTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/TestPackageTest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.config.application.api.DeploymentSpec;
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 e5571c0e0ca..7f8aa592cdb 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.archive;
import com.yahoo.config.provision.CloudAccount;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggerTest.java
index 5c5abea0276..1920524823a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLoggerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.auditlog;
import com.yahoo.container.jdisc.HttpRequest;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
index a6d3b435dcb..378b92d37ce 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/certificate/EndpointCertificatesTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.certificate;
import com.yahoo.config.application.api.DeploymentSpec;
@@ -17,19 +17,21 @@ import com.yahoo.security.SignatureAlgorithm;
import com.yahoo.security.X509CertificateBuilder;
import com.yahoo.security.X509CertificateUtils;
import com.yahoo.test.ManualClock;
+import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.hosted.controller.ControllerTester;
-import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateProviderMock;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidatorImpl;
+import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateValidatorMock;
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.integration.SecretStoreMock;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
-import com.yahoo.vespa.hosted.controller.maintenance.EndpointCertificateMaintainer;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
+import com.yahoo.vespa.hosted.controller.routing.EndpointConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -59,12 +61,13 @@ public class EndpointCertificatesTest {
private final ControllerTester tester = new ControllerTester();
private final SecretStoreMock secretStore = new SecretStoreMock();
- private final CuratorDb mockCuratorDb = tester.curator();
+ private final CuratorDb curator = tester.curator();
private final ManualClock clock = tester.clock();
private final EndpointCertificateProviderMock endpointCertificateProviderMock = new EndpointCertificateProviderMock();
private final EndpointCertificateValidatorImpl endpointCertificateValidator = new EndpointCertificateValidatorImpl(secretStore, clock);
private final EndpointCertificates endpointCertificates = new EndpointCertificates(tester.controller(), endpointCertificateProviderMock, endpointCertificateValidator);
private final KeyPair testKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC, 192);
+ private final Mutex lock = () -> {};
private X509Certificate testCertificate;
private X509Certificate testCertificate2;
@@ -75,6 +78,9 @@ public class EndpointCertificatesTest {
"*.default.default.global.vespa.oath.cloud",
"default.default.aws-us-east-1a.vespa.oath.cloud",
"*.default.default.aws-us-east-1a.vespa.oath.cloud",
+ "*.f5549014.z.vespa.oath.cloud",
+ "*.f5549014.g.vespa.oath.cloud",
+ "*.f5549014.a.vespa.oath.cloud",
"default.default.us-east-1.test.vespa.oath.cloud",
"*.default.default.us-east-1.test.vespa.oath.cloud",
"default.default.us-east-3.staging.vespa.oath.cloud",
@@ -94,7 +100,10 @@ public class EndpointCertificatesTest {
private static final List<String> expectedDevSans = List.of(
"vt2ktgkqme5zlnp4tj4ttyor7fj3v7q5o.vespa.oath.cloud",
"default.default.us-east-1.dev.vespa.oath.cloud",
- "*.default.default.us-east-1.dev.vespa.oath.cloud"
+ "*.default.default.us-east-1.dev.vespa.oath.cloud",
+ "*.f5549014.z.vespa.oath.cloud",
+ "*.f5549014.g.vespa.oath.cloud",
+ "*.f5549014.a.vespa.oath.cloud"
);
private X509Certificate makeTestCert(List<String> sans) {
@@ -109,7 +118,7 @@ public class EndpointCertificatesTest {
return x509CertificateBuilder.build();
}
- private final Instance instance = new Instance(ApplicationId.defaultId());
+ private final ApplicationId instance = ApplicationId.defaultId();
private final String testKeyName = "testKeyName";
private final String testCertName = "testCertName";
private ZoneId prodZone;
@@ -126,22 +135,20 @@ public class EndpointCertificatesTest {
@Test
void provisions_new_certificate_in_dev() {
ZoneId testZone = tester.zoneRegistry().zones().all().routingMethod(RoutingMethod.exclusive).in(Environment.dev).zones().stream().findFirst().orElseThrow().getId();
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, testZone, DeploymentSpec.empty);
- assertTrue(cert.isPresent());
- assertTrue(cert.get().keyName().matches("vespa.tls.default.default.*-key"));
- assertTrue(cert.get().certName().matches("vespa.tls.default.default.*-cert"));
- assertEquals(0, cert.get().version());
- assertEquals(expectedDevSans, cert.get().requestedDnsSans());
+ EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, testZone), DeploymentSpec.empty, lock);
+ assertTrue(cert.keyName().matches("vespa.tls.default.default.*-key"));
+ assertTrue(cert.certName().matches("vespa.tls.default.default.*-cert"));
+ assertEquals(0, cert.version());
+ assertEquals(expectedDevSans, cert.requestedDnsSans());
}
@Test
void provisions_new_certificate_in_prod() {
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty);
- assertTrue(cert.isPresent());
- assertTrue(cert.get().keyName().matches("vespa.tls.default.default.*-key"));
- assertTrue(cert.get().certName().matches("vespa.tls.default.default.*-cert"));
- assertEquals(0, cert.get().version());
- assertEquals(expectedSans, cert.get().requestedDnsSans());
+ EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock);
+ assertTrue(cert.keyName().matches("vespa.tls.default.default.*-key"));
+ assertTrue(cert.certName().matches("vespa.tls.default.default.*-cert"));
+ assertEquals(0, cert.version());
+ assertEquals(expectedSans, cert.requestedDnsSans());
}
private ControllerTester publicTester() {
@@ -161,66 +168,68 @@ public class EndpointCertificatesTest {
"*.default.default.g.vespa-app.cloud",
"default.default.aws-us-east-1a.z.vespa-app.cloud",
"*.default.default.aws-us-east-1a.z.vespa-app.cloud",
+ "*.f5549014.z.vespa-app.cloud",
+ "*.f5549014.g.vespa-app.cloud",
+ "*.f5549014.a.vespa-app.cloud",
"default.default.us-east-1.test.z.vespa-app.cloud",
"*.default.default.us-east-1.test.z.vespa-app.cloud",
"default.default.us-east-3.staging.z.vespa-app.cloud",
"*.default.default.us-east-3.staging.z.vespa-app.cloud"
);
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty);
- assertTrue(cert.isPresent());
- assertTrue(cert.get().keyName().matches("vespa.tls.default.default.*-key"));
- assertTrue(cert.get().certName().matches("vespa.tls.default.default.*-cert"));
- assertEquals(0, cert.get().version());
- assertEquals(expectedSans, cert.get().requestedDnsSans());
+ EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock);
+ assertTrue(cert.keyName().matches("vespa.tls.default.default.*-key"));
+ assertTrue(cert.certName().matches("vespa.tls.default.default.*-cert"));
+ assertEquals(0, cert.version());
+ assertEquals(expectedSans, cert.requestedDnsSans());
}
@Test
void reuses_stored_certificate() {
- mockCuratorDb.writeAssignedCertificate(assignedCertificate(instance.id(), new EndpointCertificate(testKeyName, testCertName, 7, 0, "request_id", Optional.of("leaf-request-uuid"),
- List.of("vt2ktgkqme5zlnp4tj4ttyor7fj3v7q5o.vespa.oath.cloud",
+ curator.writeAssignedCertificate(assignedCertificate(instance, new EndpointCertificate(testKeyName, testCertName, 7, 0, "request_id", Optional.of("leaf-request-uuid"),
+ List.of("vt2ktgkqme5zlnp4tj4ttyor7fj3v7q5o.vespa.oath.cloud",
"default.default.global.vespa.oath.cloud",
"*.default.default.global.vespa.oath.cloud",
"default.default.aws-us-east-1a.vespa.oath.cloud",
- "*.default.default.aws-us-east-1a.vespa.oath.cloud"),
- "", Optional.empty(), Optional.empty(), Optional.empty())));
+ "*.default.default.aws-us-east-1a.vespa.oath.cloud",
+ "*.f5549014.z.vespa.oath.cloud",
+ "*.f5549014.g.vespa.oath.cloud",
+ "*.f5549014.a.vespa.oath.cloud"),
+ "", Optional.empty(), Optional.empty(), Optional.empty())));
secretStore.setSecret(testKeyName, KeyUtils.toPem(testKeyPair.getPrivate()), 7);
secretStore.setSecret(testCertName, X509CertificateUtils.toPem(testCertificate) + X509CertificateUtils.toPem(testCertificate), 7);
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty);
- assertTrue(cert.isPresent());
- assertEquals(testKeyName, cert.get().keyName());
- assertEquals(testCertName, cert.get().certName());
- assertEquals(7, cert.get().version());
+ EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock);
+ assertEquals(testKeyName, cert.keyName());
+ assertEquals(testCertName, cert.certName());
+ assertEquals(7, cert.version());
}
@Test
void reprovisions_certificate_when_necessary() {
- mockCuratorDb.writeAssignedCertificate(assignedCertificate(instance.id(), new EndpointCertificate(testKeyName, testCertName, -1, 0, "root-request-uuid", Optional.of("leaf-request-uuid"), List.of(), "issuer", Optional.empty(), Optional.empty(), Optional.empty())));
+ curator.writeAssignedCertificate(assignedCertificate(instance, new EndpointCertificate(testKeyName, testCertName, -1, 0, "root-request-uuid", Optional.of("leaf-request-uuid"), List.of(), "issuer", Optional.empty(), Optional.empty(), Optional.empty())));
secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), 0);
secretStore.setSecret("vespa.tls.default.default.default-cert", X509CertificateUtils.toPem(testCertificate) + X509CertificateUtils.toPem(testCertificate), 0);
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty);
- assertTrue(cert.isPresent());
- assertEquals(0, cert.get().version());
- assertEquals(cert, mockCuratorDb.readAssignedCertificate(instance.id()).map(AssignedCertificate::certificate));
+ EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock);
+ assertEquals(0, cert.version());
+ assertEquals(cert, curator.readAssignedCertificate(instance).map(AssignedCertificate::certificate).get());
}
@Test
void reprovisions_certificate_with_added_sans_when_deploying_to_new_zone() {
ZoneId testZone = ZoneId.from("prod.ap-northeast-1");
- mockCuratorDb.writeAssignedCertificate(assignedCertificate(instance.id(), new EndpointCertificate(testKeyName, testCertName, -1, 0, "original-request-uuid", Optional.of("leaf-request-uuid"), expectedSans, "mockCa", Optional.empty(), Optional.empty(), Optional.empty())));
+ curator.writeAssignedCertificate(assignedCertificate(instance, new EndpointCertificate(testKeyName, testCertName, -1, 0, "original-request-uuid", Optional.of("leaf-request-uuid"), expectedSans, "mockCa", Optional.empty(), Optional.empty(), Optional.empty())));
secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), -1);
secretStore.setSecret("vespa.tls.default.default.default-cert", X509CertificateUtils.toPem(testCertificate) + X509CertificateUtils.toPem(testCertificate), -1);
secretStore.setSecret("vespa.tls.default.default.default-key", KeyUtils.toPem(testKeyPair.getPrivate()), 0);
secretStore.setSecret("vespa.tls.default.default.default-cert", X509CertificateUtils.toPem(testCertificate2) + X509CertificateUtils.toPem(testCertificate2), 0);
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, testZone, DeploymentSpec.empty);
- assertTrue(cert.isPresent());
- assertEquals(0, cert.get().version());
- assertEquals(cert, mockCuratorDb.readAssignedCertificate(instance.id()).map(AssignedCertificate::certificate));
- assertEquals("original-request-uuid", cert.get().rootRequestId());
- assertNotEquals(Optional.of("leaf-request-uuid"), cert.get().leafRequestId());
- assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(cert.get().requestedDnsSans()));
+ EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, testZone), DeploymentSpec.empty, lock);
+ assertEquals(0, cert.version());
+ assertEquals(cert, curator.readAssignedCertificate(instance).map(AssignedCertificate::certificate).get());
+ assertEquals("original-request-uuid", cert.rootRequestId());
+ assertNotEquals(Optional.of("leaf-request-uuid"), cert.leafRequestId());
+ assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(cert.requestedDnsSans()));
}
@Test
@@ -239,17 +248,16 @@ public class EndpointCertificatesTest {
);
ZoneId testZone = tester.zoneRegistry().zones().all().in(Environment.staging).zones().stream().findFirst().orElseThrow().getId();
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, testZone, deploymentSpec);
- assertTrue(cert.isPresent());
- assertTrue(cert.get().keyName().matches("vespa.tls.default.default.*-key"));
- assertTrue(cert.get().certName().matches("vespa.tls.default.default.*-cert"));
- assertEquals(0, cert.get().version());
- assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(cert.get().requestedDnsSans()));
+ EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, testZone), deploymentSpec, lock);
+ assertTrue(cert.keyName().matches("vespa.tls.default.default.*-key"));
+ assertTrue(cert.certName().matches("vespa.tls.default.default.*-cert"));
+ assertEquals(0, cert.version());
+ assertEquals(Set.copyOf(expectedCombinedSans), Set.copyOf(cert.requestedDnsSans()));
}
@Test
void includes_application_endpoint_when_declared() {
- Instance instance = new Instance(ApplicationId.from("t1", "a1", "default"));
+ ApplicationId instance = ApplicationId.from("t1", "a1", "default");
ZoneId zone1 = ZoneId.from(Environment.prod, RegionName.from("aws-us-east-1c"));
ZoneId zone2 = ZoneId.from(Environment.prod, RegionName.from("aws-us-west-2a"));
ControllerTester tester = publicTester();
@@ -281,28 +289,24 @@ public class EndpointCertificatesTest {
"a1.t1.us-east-1.test.z.vespa-app.cloud",
"*.a1.t1.us-east-1.test.z.vespa-app.cloud",
"a1.t1.us-east-3.staging.z.vespa-app.cloud",
- "*.a1.t1.us-east-3.staging.z.vespa-app.cloud"
+ "*.a1.t1.us-east-3.staging.z.vespa-app.cloud",
+ "*.f5549014.z.vespa-app.cloud",
+ "*.f5549014.g.vespa-app.cloud",
+ "*.f5549014.a.vespa-app.cloud"
).sorted().toList();
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, zone1, applicationPackage.deploymentSpec());
- assertTrue(cert.isPresent());
- assertTrue(cert.get().keyName().matches("vespa.tls.t1.a1.*-key"));
- assertTrue(cert.get().certName().matches("vespa.tls.t1.a1.*-cert"));
- assertEquals(0, cert.get().version());
- assertEquals(expectedSans, cert.get().requestedDnsSans().stream().sorted().toList());
+ EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, zone1), applicationPackage.deploymentSpec(), lock);
+ assertTrue(cert.keyName().matches("vespa.tls.t1.a1.*-key"));
+ assertTrue(cert.certName().matches("vespa.tls.t1.a1.*-cert"));
+ assertEquals(0, cert.version());
+ assertEquals(expectedSans, cert.requestedDnsSans().stream().sorted().toList());
}
@Test
public void assign_certificate_from_pool() {
- // Initial certificate is requested directly from provider
- Optional<EndpointCertificate> certFromProvider = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty);
- assertTrue(certFromProvider.isPresent());
- assertFalse(certFromProvider.get().randomizedId().isPresent());
-
- // Pooled certificates become available
- tester.flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true);
+ setEndpointConfig(tester, EndpointConfig.generated);
try {
- addCertificateToPool("pool-cert-1", UnassignedCertificate.State.requested);
- endpointCertificates.get(instance, prodZone, DeploymentSpec.empty);
+ addCertificateToPool("bad0f00d", UnassignedCertificate.State.requested, tester);
+ endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock);
fail("Expected exception as certificate is not ready");
} catch (IllegalArgumentException ignored) {}
@@ -312,76 +316,171 @@ public class EndpointCertificatesTest {
// Certificate is assigned from pool instead. The previously assigned certificate will eventually be cleaned up
// by EndpointCertificateMaintainer
{ // prod
- String certId = "pool-cert-1";
- addCertificateToPool(certId, UnassignedCertificate.State.ready);
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty);
- assertEquals(certId, cert.get().randomizedId().get());
- assertEquals(certId, tester.curator().readAssignedCertificate(TenantAndApplicationId.from(instance.id()), Optional.empty()).get().certificate().randomizedId().get(), "Certificate is assigned at application-level");
+ String certId = "bad0f00d";
+ addCertificateToPool(certId, UnassignedCertificate.State.ready, tester);
+ EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, prodZone), DeploymentSpec.empty, lock);
+ assertEquals(certId, cert.generatedId().get());
+ assertEquals(certId, tester.curator().readAssignedCertificate(TenantAndApplicationId.from(instance), Optional.empty()).get().certificate().generatedId().get(), "Certificate is assigned at application-level");
assertTrue(tester.controller().curator().readUnassignedCertificate(certId).isEmpty(), "Certificate is removed from pool");
- assertEquals(clock.instant().getEpochSecond(), cert.get().lastRequested());
+ assertEquals(clock.instant().getEpochSecond(), cert.lastRequested());
}
{ // dev
- String certId = "pool-cert-2";
- addCertificateToPool(certId, UnassignedCertificate.State.ready);
+ String certId = "f00d0bad";
+ addCertificateToPool(certId, UnassignedCertificate.State.ready, tester);
ZoneId devZone = tester.zoneRegistry().zones().all().routingMethod(RoutingMethod.exclusive).in(Environment.dev).zones().stream().findFirst().orElseThrow().getId();
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, devZone, DeploymentSpec.empty);
- assertEquals(certId, cert.get().randomizedId().get());
- assertEquals(certId, tester.curator().readAssignedCertificate(instance.id()).get().certificate().randomizedId().get(), "Certificate is assigned at instance-level");
+ EndpointCertificate cert = endpointCertificates.get(new DeploymentId(instance, devZone), DeploymentSpec.empty, lock);
+ assertEquals(certId, cert.generatedId().get());
+ assertEquals(certId, tester.curator().readAssignedCertificate(instance).get().certificate().generatedId().get(), "Certificate is assigned at instance-level");
assertTrue(tester.controller().curator().readUnassignedCertificate(certId).isEmpty(), "Certificate is removed from pool");
- assertEquals(clock.instant().getEpochSecond(), cert.get().lastRequested());
+ assertEquals(clock.instant().getEpochSecond(), cert.lastRequested());
}
}
@Test
- void reuse_per_instance_certificate_if_assigned_random_id() {
- // Initial certificate is requested directly from provider
- Optional<EndpointCertificate> certFromProvider = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty);
- assertTrue(certFromProvider.isPresent());
- assertFalse(certFromProvider.get().randomizedId().isPresent());
-
- // Simulate endpoint certificate maintainer to assign random id
- TenantAndApplicationId tenantAndApplicationId = TenantAndApplicationId.from(instance.id());
- Optional<InstanceName> instanceName = Optional.of(instance.name());
- Optional<AssignedCertificate> assignedCertificate = tester.controller().curator().readAssignedCertificate(tenantAndApplicationId, instanceName);
- assertTrue(assignedCertificate.isPresent());
- String assignedRandomId = "randomid";
- AssignedCertificate updated = assignedCertificate.get().with(assignedCertificate.get().certificate().withRandomizedId(assignedRandomId));
- tester.controller().curator().writeAssignedCertificate(updated);
-
- // Pooled certificates become available
- tester.flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true);
-
- // Create 1 cert in pool
- String certId = "pool-cert-1";
- addCertificateToPool(certId, UnassignedCertificate.State.ready);
-
- // Request cert for app
- Optional<EndpointCertificate> cert = endpointCertificates.get(instance, prodZone, DeploymentSpec.empty);
- assertEquals(assignedRandomId, cert.get().randomizedId().get());
-
- // Pooled cert remains unassigned
- List<String> unassignedCertificateIds = tester.curator().readUnassignedCertificates().stream()
- .map(UnassignedCertificate::certificate)
- .map(EndpointCertificate::randomizedId)
- .map(Optional::get)
- .toList();
- assertEquals(List.of(certId), unassignedCertificateIds);
+ public void certificate_migration() {
+ // An application is initially deployed with legacy config (the default)
+ ZoneId zone1 = ZoneId.from(Environment.prod, RegionName.from("aws-us-east-1c"));
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder().region(zone1.region())
+ .build();
+ ControllerTester tester = publicTester();
+ EndpointCertificates endpointCertificates = new EndpointCertificates(tester.controller(), endpointCertificateProviderMock, new EndpointCertificateValidatorMock());
+ ApplicationId instance = ApplicationId.from("t1", "a1", "default");
+ DeploymentId deployment0 = new DeploymentId(instance, zone1);
+ final EndpointCertificate certificate = endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock);
+ final String generatedId = certificate.generatedId().get();
+ assertEquals(List.of("vlfms2wpoa4nyrka2s5lktucypjtxkqhv.internal.vespa-app.cloud",
+ "a1.t1.g.vespa-app.cloud",
+ "*.a1.t1.g.vespa-app.cloud",
+ "a1.t1.aws-us-east-1c.z.vespa-app.cloud",
+ "*.a1.t1.aws-us-east-1c.z.vespa-app.cloud",
+ "*.f5549014.z.vespa-app.cloud",
+ "*.f5549014.g.vespa-app.cloud",
+ "*.f5549014.a.vespa-app.cloud",
+ "a1.t1.us-east-1.test.z.vespa-app.cloud",
+ "*.a1.t1.us-east-1.test.z.vespa-app.cloud",
+ "a1.t1.us-east-3.staging.z.vespa-app.cloud",
+ "*.a1.t1.us-east-3.staging.z.vespa-app.cloud"),
+ certificate.requestedDnsSans());
+ Optional<AssignedCertificate> assignedCertificate = tester.curator().readAssignedCertificate(deployment0.applicationId());
+ assertTrue(assignedCertificate.isPresent(), "Certificate is assigned at instance level");
+ assertTrue(assignedCertificate.get().certificate().generatedId().isPresent(), "Certificate contains generated ID");
+
+ // Re-requesting certificate does not make any changes, except last requested time
+ tester.clock().advance(Duration.ofHours(1));
+ assertEquals(certificate.withLastRequested(tester.clock().instant().getEpochSecond()),
+ endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock),
+ "Next request returns same certificate and updates last requested time");
+
+ // An additional instance is added to deployment spec
+ applicationPackage = new ApplicationPackageBuilder().instances("default,beta")
+ .region(zone1.region())
+ .build();
+ DeploymentId deployment1 = new DeploymentId(ApplicationId.from("t1", "a1", "beta"), zone1);
+ EndpointCertificate betaCert = endpointCertificates.get(deployment1, applicationPackage.deploymentSpec(), lock);
+ assertEquals(List.of("v43ctkgqim52zsbwefrg6ixkuwidvsumy.internal.vespa-app.cloud",
+ "beta.a1.t1.g.vespa-app.cloud",
+ "*.beta.a1.t1.g.vespa-app.cloud",
+ "beta.a1.t1.aws-us-east-1c.z.vespa-app.cloud",
+ "*.beta.a1.t1.aws-us-east-1c.z.vespa-app.cloud",
+ "*.f5549014.z.vespa-app.cloud",
+ "*.f5549014.g.vespa-app.cloud",
+ "*.f5549014.a.vespa-app.cloud",
+ "beta.a1.t1.us-east-1.test.z.vespa-app.cloud",
+ "*.beta.a1.t1.us-east-1.test.z.vespa-app.cloud",
+ "beta.a1.t1.us-east-3.staging.z.vespa-app.cloud",
+ "*.beta.a1.t1.us-east-3.staging.z.vespa-app.cloud"),
+ betaCert.requestedDnsSans());
+ assertEquals(generatedId, betaCert.generatedId().get(), "Certificate inherits generated ID of existing instance");
+
+ // A dev instance is deployed
+ DeploymentId devDeployment0 = new DeploymentId(ApplicationId.from("t1", "a1", "dev"),
+ ZoneId.from("dev", "us-east-1"));
+ EndpointCertificate devCert0 = endpointCertificates.get(devDeployment0, applicationPackage.deploymentSpec(), lock);
+ assertNotEquals(generatedId, devCert0.generatedId().get(), "Dev deployments gets a new generated ID");
+ assertEquals(List.of("vld3y4mggzpd5wmm5jmldzcbyetjoqtzq.internal.vespa-app.cloud",
+ "dev.a1.t1.us-east-1.dev.z.vespa-app.cloud",
+ "*.dev.a1.t1.us-east-1.dev.z.vespa-app.cloud",
+ "*.a89ff7c6.z.vespa-app.cloud",
+ "*.a89ff7c6.g.vespa-app.cloud",
+ "*.a89ff7c6.a.vespa-app.cloud"),
+ devCert0.requestedDnsSans());
+
+ // Application switches to combined config
+ setEndpointConfig(tester, EndpointConfig.combined);
+ tester.clock().advance(Duration.ofHours(1));
+ assertEquals(certificate.withLastRequested(tester.clock().instant().getEpochSecond()),
+ endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock),
+ "No change to certificate: Existing certificate is compatible with " +
+ EndpointConfig.combined + " config");
+ assertTrue(tester.curator().readAssignedCertificate(deployment0.applicationId()).isPresent(), "Certificate is assigned at instance level");
+ assertFalse(tester.curator().readAssignedCertificate(TenantAndApplicationId.from(deployment0.applicationId()), Optional.empty()).isPresent(),
+ "Certificate is not assigned at application level");
+
+ // Application switches to generated config
+ setEndpointConfig(tester, EndpointConfig.generated);
+ tester.clock().advance(Duration.ofHours(1));
+ assertEquals(certificate.withLastRequested(tester.clock().instant().getEpochSecond()),
+ endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock),
+ "No change to certificate: Existing certificate is compatible with " +
+ EndpointConfig.generated + " config");
+ assertFalse(tester.curator().readAssignedCertificate(deployment0.applicationId()).isPresent(), "Certificate is no longer assigned at instance level");
+ assertTrue(tester.curator().readAssignedCertificate(TenantAndApplicationId.from(deployment0.applicationId()), Optional.empty()).isPresent(),
+ "Certificate is assigned at application level");
+
+ // Both instances still use the same certificate
+ assertEquals(endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock),
+ endpointCertificates.get(deployment1, applicationPackage.deploymentSpec(), lock));
+
+ // Another dev instance is deployed, and is assigned certificate from pool
+ String poolCertId0 = "badf00d0";
+ addCertificateToPool(poolCertId0, UnassignedCertificate.State.ready, tester);
+ EndpointCertificate devCert1 = endpointCertificates.get(new DeploymentId(ApplicationId.from("t1", "a1", "dev2"),
+ ZoneId.from("dev", "us-east-1")),
+ applicationPackage.deploymentSpec(), lock);
+ assertEquals(poolCertId0, devCert1.generatedId().get());
+
+ // Another application is deployed, and is assigned certificate from pool
+ String poolCertId1 = "badf00d1";
+ addCertificateToPool(poolCertId1, UnassignedCertificate.State.ready, tester);
+ EndpointCertificate prodCertificate = endpointCertificates.get(new DeploymentId(ApplicationId.from("t1", "a2", "default"),
+ ZoneId.from("prod", "us-east-1")),
+ applicationPackage.deploymentSpec(), lock);
+ assertEquals(poolCertId1, prodCertificate.generatedId().get());
+
+ // Application switches back to legacy config
+ setEndpointConfig(tester, EndpointConfig.legacy);
+ EndpointCertificate reissuedCertificate = endpointCertificates.get(deployment0, applicationPackage.deploymentSpec(), lock);
+ assertEquals(certificate.requestedDnsSans(), reissuedCertificate.requestedDnsSans());
+ assertTrue(tester.curator().readAssignedCertificate(deployment0.applicationId()).isPresent(), "Certificate is assigned at instance level again");
+ assertTrue(tester.curator().readAssignedCertificate(TenantAndApplicationId.from(deployment0.applicationId()), Optional.empty()).isPresent(),
+ "Certificate is still assigned at application level"); // Not removed because the assumption is that the application will eventually migrate back
+ }
+
+ private void setEndpointConfig(ControllerTester tester, EndpointConfig config) {
+ tester.flagSource().withStringFlag(Flags.ENDPOINT_CONFIG.id(), config.name());
}
- private void addCertificateToPool(String id, UnassignedCertificate.State state) {
- EndpointCertificate cert = new EndpointCertificate(testKeyName, testCertName, 1, 0,
+ private void addCertificateToPool(String id, UnassignedCertificate.State state, ControllerTester tester) {
+ EndpointCertificate cert = new EndpointCertificate(testKeyName,
+ testCertName,
+ 1,
+ 0,
"request-id",
Optional.of("leaf-request-uuid"),
- List.of("name1", "name2"),
- "", Optional.empty(),
- Optional.empty(), Optional.of(id));
+ List.of("*." + id + ".z.vespa.oath.cloud",
+ "*." + id + ".g.vespa.oath.cloud",
+ "*." + id + ".a.vespa.oath.cloud"),
+ "",
+ Optional.empty(),
+ Optional.empty(),
+ Optional.of(id));
UnassignedCertificate pooledCert = new UnassignedCertificate(cert, state);
tester.controller().curator().writeUnassignedCertificate(pooledCert);
}
private static AssignedCertificate assignedCertificate(ApplicationId instance, EndpointCertificate certificate) {
- return new AssignedCertificate(TenantAndApplicationId.from(instance), Optional.of(instance.instance()), certificate);
+ return new AssignedCertificate(TenantAndApplicationId.from(instance), Optional.of(instance.instance()), certificate, false);
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/concurrent/OnceTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/concurrent/OnceTest.java
index c1a8f77f2e9..8c2815c8646 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/concurrent/OnceTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/concurrent/OnceTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.concurrent;
import org.junit.jupiter.api.Test;
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 3623ddc4e56..de915229cd4 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
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 9f6cacb557b..841c54feb05 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.google.common.base.Suppliers;
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 ee6a21d071d..bc03d46a30a 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.config.provision.ApplicationId;
@@ -75,8 +75,7 @@ public class DeploymentTester {
tester = controllerTester;
jobs = tester.controller().jobController();
cloud = (MockTesterCloud) tester.controller().jobController().cloud();
- runner = new JobRunner(tester.controller(), maintenanceInterval, JobRunnerTest.inThreadExecutor(),
- new InternalStepRunner(tester.controller()));
+ runner = new JobRunner(tester.controller(), maintenanceInterval, JobRunnerTest.inThreadInOrderExecutor(), new InternalStepRunner(tester.controller()));
upgrader = new Upgrader(tester.controller(), maintenanceInterval);
upgrader.setUpgradesPerMinute(1); // Anything that makes it at least one for any maintenance period is fine.
readyJobsTrigger = new ReadyJobsTrigger(tester.controller(), maintenanceInterval);
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 62bdf95515d..a6ea8fa074e 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
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 7783f9af5a4..94cf016b46b 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/QuotaUsageTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/QuotaUsageTest.java
index 44ca248614b..1e918c27231 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/QuotaUsageTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/QuotaUsageTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.config.provision.zone.ZoneId;
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 831a79f24b8..0864ebb1154 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,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ZipBuilderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ZipBuilderTest.java
index a6f7f45caa4..bb389bb91c2 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ZipBuilderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/ZipBuilderTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.deployment;
import org.junit.jupiter.api.Test;
@@ -60,4 +60,4 @@ public class ZipBuilderTest {
return contents;
}
-} \ No newline at end of file
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueueTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueueTest.java
index 939e3252750..36619d4ca93 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueueTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/dns/NameServiceQueueTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.dns;
import com.yahoo.config.provision.HostName;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationStoreMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationStoreMock.java
index e025a3bea4f..a064cbb82d2 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationStoreMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ApplicationStoreMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.config.provision.ApplicationId;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ArtifactRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ArtifactRegistryMock.java
index a36bddc618e..3c9e9679210 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ArtifactRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ArtifactRegistryMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.api.integration.artifact.Artifact;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ArtifactRepositoryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ArtifactRepositoryMock.java
index dc7010312a2..e6915b0126e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ArtifactRepositoryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ArtifactRepositoryMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.AbstractComponent;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/AthenzFilterMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/AthenzFilterMock.java
index ea7521e8250..b781d8bb342 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/AthenzFilterMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/AthenzFilterMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.fasterxml.jackson.core.JsonProcessingException;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
index 0e5308fcef5..5995b3eaac6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.integration;
import ai.vespa.http.DomainName;
@@ -386,7 +386,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
@Override
public Availability verifyEndpoints(DeploymentId deploymentId, List<Endpoint> zoneEndpoints) {
- return mockTesterCloud.verifyEndpoints(deploymentId, zoneEndpoints); // Wraps the same name service mock, which is updated by test harness.
+ return mockTesterCloud.verifyEndpoints(deploymentId, zoneEndpoints, false); // Wraps the same name service mock, which is updated by test harness.
}
/** Add any of given loadBalancers that do not already exist to the load balancers in zone */
@@ -417,12 +417,11 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
applications.put(id, new Application(id.applicationId(), lastPrepareVersion, appPackage));
ClusterSpec.Id cluster = ClusterSpec.Id.from("default");
- deployment.endpoints(); // Supplier with side effects >_<
if (nodeRepository().list(id.zoneId(), NodeFilter.all().applications(id.applicationId())).isEmpty())
provision(id.zoneId(), id.applicationId(), cluster);
- this.containerEndpoints.put(id, deployment.endpoints().get().endpoints());
+ this.containerEndpoints.put(id, deployment.endpoints().endpoints());
deployment.cloudAccount().ifPresent(account -> this.cloudAccounts.put(id, account));
if (!deferLoadBalancerProvisioning.contains(id.zoneId().environment())) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerProxyMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerProxyMock.java
index 586056ce9dc..29aeb3f755d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerProxyMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerProxyMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.AbstractComponent;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MetricsMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MetricsMock.java
index 36de515ab58..8172de8680d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MetricsMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/MetricsMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.integration;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
index 5b70942bfd1..a4b14755d7d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.collections.Pair;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/SecretStoreMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/SecretStoreMock.java
index 37c050079e0..10f8d27772e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/SecretStoreMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/SecretStoreMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.AbstractComponent;
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 c6386509585..39d867d813d 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,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.cloud.config.ConfigserverConfig;
@@ -10,6 +10,7 @@ import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.controller.api.identifiers.ControllerVersion;
+import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls;
import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry;
import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService;
import com.yahoo.vespa.hosted.controller.api.integration.archive.MockArchiveService;
@@ -53,12 +54,10 @@ import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud;
import com.yahoo.vespa.hosted.controller.api.integration.user.RoleMaintainer;
import com.yahoo.vespa.hosted.controller.api.integration.user.RoleMaintainerMock;
import com.yahoo.vespa.hosted.controller.api.integration.vcmr.MockChangeRequestClient;
-import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
-import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import java.net.URI;
import java.time.Instant;
import java.util.Optional;
-import java.util.UUID;
/**
* A mock implementation of a {@link ServiceRegistry} for testing purposes.
@@ -71,6 +70,7 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
private final ControllerVersion controllerVersion;
private final ZoneRegistryMock zoneRegistryMock;
private final ConfigServerMock configServerMock;
+ private final ConsoleUrls consoleUrls = new ConsoleUrls(URI.create("https://console.tld"));
private final MemoryNameService memoryNameService = new MemoryNameService();
private final MockVpcEndpointService vpcEndpointService = new MockVpcEndpointService(clock, memoryNameService);
private final MockMailer mockMailer = new MockMailer();
@@ -90,7 +90,6 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
private final MockEnclaveAccessService mockAMIService = new MockEnclaveAccessService();
private final MockResourceTagger mockResourceTagger = new MockResourceTagger();
private final MockRoleService roleService = new MockRoleService();
- private final MockBillingController billingController = new MockBillingController(clock);
private final ArtifactRegistryMock containerRegistry = new ArtifactRegistryMock();
private final NoopTenantSecretService tenantSecretService = new NoopTenantSecretService();
private final NoopEndpointSecretManager secretManager = new NoopEndpointSecretManager();
@@ -101,6 +100,7 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
private final PlanRegistry planRegistry = new PlanRegistryMock();
private final ResourceDatabaseClient resourceDb = new ResourceDatabaseClientMock(planRegistry);
private final BillingDatabaseClient billingDb = new BillingDatabaseClientMock(clock, planRegistry);
+ private final MockBillingController billingController = new MockBillingController(clock, billingDb);
private final RoleMaintainerMock roleMaintainer = new RoleMaintainerMock();
public ServiceRegistryMock(SystemName system) {
@@ -221,6 +221,11 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
}
@Override
+ public ConsoleUrls consoleUrls() {
+ return consoleUrls;
+ }
+
+ @Override
public MockResourceTagger resourceTagger() {
return mockResourceTagger;
}
@@ -320,6 +325,7 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg
@Override
public BillingReporter billingReporter() {
- return new BillingReporterMock(clock());
+ return new BillingReporterMock(clock(), billingDb);
}
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java
index 21fe1f66bc5..e49440976de 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.config.provision.CloudName;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java
index 7aac44805b3..e2b5768bf33 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneFilterMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.config.provision.CloudName;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
index 2546812906a..ab1195a91be 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java
@@ -1,9 +1,7 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.AbstractComponent;
-import com.yahoo.config.provision.ApplicationId;
-import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.CloudName;
@@ -21,7 +19,6 @@ import com.yahoo.text.Text;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry;
import java.net.URI;
@@ -220,36 +217,6 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry
}
@Override
- public URI dashboardUrl() {
- return URI.create("https://dashboard.tld");
- }
-
- @Override
- public URI dashboardUrl(ApplicationId id) {
- return URI.create("https://dashboard.tld/" + id);
- }
-
- @Override
- public URI dashboardUrl(TenantName tenantName, ApplicationName applicationName) {
- return URI.create("https://dashboard.tld/" + tenantName + "/" + applicationName);
- }
-
- @Override
- public URI dashboardUrl(TenantName tenantName) {
- return URI.create("https://dashboard.tld/" + tenantName);
- }
-
- @Override
- public URI dashboardUrl(RunId id) {
- return URI.create("https://dashboard.tld/" + id.application() + "/" + id.type().jobName() + "/" + id.number());
- }
-
- @Override
- public URI supportUrl() {
- return URI.create("https://help.tld");
- }
-
- @Override
public URI apiUrl() {
return URI.create("https://api.tld:4443/");
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java
index 142210843ff..8aaf1e2a928 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.InstanceName;
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 fe14d696011..0b826e8f375 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
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 14540971faf..0a388806146 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.CloudAccount;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirerTest.java
index 58ac302d567..17233496e31 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdaterTest.java
index 8537fdaa9f5..bac89b1988c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdaterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdaterTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java
index b1e00ba0746..5cb46664a75 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java
@@ -1,15 +1,19 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.hosted.controller.ControllerTester;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.BillStatus;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.InvoiceUpdate;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistryMock;
import com.yahoo.vespa.hosted.controller.tenant.BillingReference;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import org.junit.jupiter.api.Test;
import java.time.Duration;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -39,8 +43,47 @@ public class BillingReportMaintainerTest {
assertNotNull(b1.orElseThrow().reference());
}
+ @Test
+ void only_open_bills_with_exported_id_are_maintained() {
+ var t1 = tester.createTenant("t1");
+ var billingController = tester.controller().serviceRegistry().billingController();
+ var billingDb = tester.controller().serviceRegistry().billingDatabase();
+
+ var start = LocalDate.of(2020, 5, 23).atStartOfDay(ZoneOffset.UTC);
+ var end = start.toLocalDate().plusDays(6).atStartOfDay(ZoneOffset.UTC);
+
+ var bill1 = billingDb.createBill(t1, start, end, "non-exported");
+ var bill2 = billingDb.createBill(t1, start, end, "exported");
+ var bill3 = billingDb.createBill(t1, start, end, "exported-and-frozen");
+ billingDb.setStatus(bill3, "foo", BillStatus.FROZEN);
+
+ billingController.setPlan(t1, PlanRegistryMock.paidPlan.id(), false, true);
+
+ tester.controller().serviceRegistry().billingReporter().exportBill(billingDb.readBill(bill2).get(), "FOO", cloudTenant(t1));
+ tester.controller().serviceRegistry().billingReporter().exportBill(billingDb.readBill(bill3).get(), "FOO", cloudTenant(t1));
+ var updates = maintainer.maintainInvoices();
+ assertEquals(new InvoiceUpdate(1, 0, 0), updates);
+
+ assertTrue(billingDb.readBill(bill1).get().getExportedId().isEmpty());
+
+ var exportedBill = billingDb.readBill(bill2).get();
+ assertEquals("EXT-ID-123", exportedBill.getExportedId().get());
+ var lineItems = exportedBill.lineItems();
+ assertEquals(1, lineItems.size());
+ assertEquals("maintained", lineItems.get(0).id());
+
+ var frozenBill = billingDb.readBill(bill3).get();
+ assertEquals("EXT-ID-123", frozenBill.getExportedId().get());
+ assertEquals(0, frozenBill.lineItems().size());
+
+ }
+
+ private CloudTenant cloudTenant(TenantName tenantName) {
+ return tester.controller().tenants().require(tenantName, CloudTenant.class);
+ }
+
private Optional<BillingReference> billingReference(TenantName tenantName) {
- var t = tester.controller().tenants().require(tenantName, CloudTenant.class);
- return t.billingReference();
+ return cloudTenant(tenantName).billingReference();
}
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainerTest.java
index 4257261b09b..1765d1ff86d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CertificatePoolMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.jdisc.test.MockMetric;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeManagementAssessorTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeManagementAssessorTest.java
index 084fd243769..0f8aa2885e2 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeManagementAssessorTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeManagementAssessorTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeRequestMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeRequestMaintainerTest.java
index a25304ee297..620a0505db8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeRequestMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ChangeRequestMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.zone.ZoneId;
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 c5c70998624..4056459c532 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
@@ -1,22 +1,32 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.test.ManualClock;
+import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
+import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
+import com.yahoo.vespa.hosted.controller.notification.Notification;
+import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import org.junit.jupiter.api.Test;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -24,6 +34,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
*/
public class CloudTrialExpirerTest {
+ private static final boolean OVERWRITE_TEST_FILES = false;
+
private final ControllerTester tester = new ControllerTester(SystemName.PublicCd);
private final DeploymentTester deploymentTester = new DeploymentTester(tester);
private final CloudTrialExpirer expirer = new CloudTrialExpirer(tester.controller(), Duration.ofMinutes(5));
@@ -31,28 +43,28 @@ public class CloudTrialExpirerTest {
@Test
void expire_inactive_tenant() {
registerTenant("trial-tenant", "trial", Duration.ofDays(14).plusMillis(1));
- assertEquals(1.0, expirer.maintain());
+ assertEquals(0.0, expirer.maintain());
assertPlan("trial-tenant", "none");
}
@Test
void tombstone_inactive_none() {
- registerTenant("none-tenant", "none", Duration.ofDays(183).plusMillis(1));
- assertEquals(1.0, expirer.maintain());
+ registerTenant("none-tenant", "none", Duration.ofDays(91).plusMillis(1));
+ assertEquals(0.0, expirer.maintain());
assertEquals(Tenant.Type.deleted, tester.controller().tenants().get(TenantName.from("none-tenant"), true).get().type());
}
@Test
void keep_inactive_nontrial_tenants() {
registerTenant("not-a-trial-tenant", "pay-as-you-go", Duration.ofDays(30));
- assertEquals(1.0, expirer.maintain());
+ assertEquals(0.0, expirer.maintain());
assertPlan("not-a-trial-tenant", "pay-as-you-go");
}
@Test
void keep_active_trial_tenants() {
registerTenant("active-trial-tenant", "trial", Duration.ofHours(14).minusMillis(1));
- assertEquals(1.0, expirer.maintain());
+ assertEquals(0.0, expirer.maintain());
assertPlan("active-trial-tenant", "trial");
}
@@ -60,7 +72,7 @@ public class CloudTrialExpirerTest {
void keep_inactive_exempt_tenants() {
registerTenant("exempt-trial-tenant", "trial", Duration.ofDays(40));
((InMemoryFlagSource) tester.controller().flagSource()).withListFlag(PermanentFlags.EXTENDED_TRIAL_TENANTS.id(), List.of("exempt-trial-tenant"), String.class);
- assertEquals(1.0, expirer.maintain());
+ assertEquals(0.0, expirer.maintain());
assertPlan("exempt-trial-tenant", "trial");
}
@@ -68,7 +80,7 @@ public class CloudTrialExpirerTest {
void keep_inactive_trial_tenants_with_deployments() {
registerTenant("with-deployments", "trial", Duration.ofDays(30));
registerDeployment("with-deployments", "my-app", "default");
- assertEquals(1.0, expirer.maintain());
+ assertEquals(0.0, expirer.maintain());
assertPlan("with-deployments", "trial");
}
@@ -76,19 +88,71 @@ public class CloudTrialExpirerTest {
void delete_tenants_with_applications_with_no_deployments() {
registerTenant("with-apps", "trial", Duration.ofDays(184));
tester.createApplication("with-apps", "app1", "instance1");
- assertEquals(1.0, expirer.maintain());
+ assertEquals(0.0, expirer.maintain());
assertPlan("with-apps", "none");
- assertEquals(1.0, expirer.maintain());
+ assertEquals(0.0, expirer.maintain());
assertTrue(tester.controller().tenants().get("with-apps").isEmpty());
}
@Test
void keep_tenants_without_applications_that_are_idle() {
registerTenant("active", "none", Duration.ofDays(182));
- assertEquals(1.0, expirer.maintain());
+ assertEquals(0.0, expirer.maintain());
assertPlan("active", "none");
}
+ @Test
+ void queues_trial_notification_based_on_account_age() throws IOException {
+ var clock = (ManualClock)tester.controller().clock();
+ var mailer = (MockMailer) tester.serviceRegistry().mailer();
+ var tenant = TenantName.from("trial-tenant");
+ ((InMemoryFlagSource) tester.controller().flagSource())
+ .withBooleanFlag(Flags.CLOUD_TRIAL_NOTIFICATIONS.id(), true);
+ registerTenant(tenant.value(), "trial", Duration.ZERO);
+ assertEquals(0.0, expirer.maintain());
+ var expected = "Welcome to Vespa Cloud trial! [Manage plan](https://console.tld/tenant/trial-tenant/account/billing)";
+ assertEquals(expected, lastAccountLevelNotificationTitle(tenant));
+ assertLastEmailEquals(mailer, "welcome.html");
+
+ expected = "You're halfway through the **14 day** trial period. [Manage plan](https://console.tld/tenant/trial-tenant/account/billing)";
+ clock.advance(Duration.ofDays(7));
+ assertEquals(0.0, expirer.maintain());
+ assertEquals(expected, lastAccountLevelNotificationTitle(tenant));
+ assertLastEmailEquals(mailer, "trial-reminder.html");
+
+ expected = "Your Vespa Cloud trial expires in **2** days. [Manage plan](https://console.tld/tenant/trial-tenant/account/billing)";
+ clock.advance(Duration.ofDays(5));
+ assertEquals(0.0, expirer.maintain());
+ assertEquals(expected, lastAccountLevelNotificationTitle(tenant));
+ assertLastEmailEquals(mailer, "trial-expiring-soon.html");
+
+ expected = "Your Vespa Cloud trial expires **tomorrow**. [Manage plan](https://console.tld/tenant/trial-tenant/account/billing)";
+ clock.advance(Duration.ofDays(1));
+ assertEquals(0.0, expirer.maintain());
+ assertEquals(expected, lastAccountLevelNotificationTitle(tenant));
+ assertLastEmailEquals(mailer, "trial-expiring-immediately.html");
+
+ expected = "Your Vespa Cloud trial has expired. [Upgrade plan](https://console.tld/tenant/trial-tenant/account/billing)";
+ clock.advance(Duration.ofDays(2));
+ assertEquals(0.0, expirer.maintain());
+ assertEquals(expected, lastAccountLevelNotificationTitle(tenant));
+ assertLastEmailEquals(mailer, "trial-expired.html");
+ }
+
+ private void assertLastEmailEquals(MockMailer mailer, String expectedContentFile) throws IOException {
+ var mails = mailer.inbox("dev-trial-tenant");
+ assertFalse(mails.isEmpty());
+ var content = mails.get(mails.size() - 1).htmlMessage().orElseThrow();
+ var path = Paths.get("src/test/resources/mail/" + expectedContentFile);
+ if (OVERWRITE_TEST_FILES) {
+ Files.write(path, content.getBytes(),
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
+ } else {
+ var expectedContent = Files.readString(path);
+ assertEquals(expectedContent, content);
+ }
+ }
+
private void registerTenant(String tenantName, String plan, Duration timeSinceLastLogin) {
var name = TenantName.from(tenantName);
tester.createTenant(tenantName, Tenant.Type.cloud);
@@ -111,4 +175,11 @@ public class CloudTrialExpirerTest {
assertEquals(planId, tester.serviceRegistry().billingController().getPlan(TenantName.from(tenant)).value());
}
+ private String lastAccountLevelNotificationTitle(TenantName tenant) {
+ return tester.controller().notificationsDb()
+ .listNotifications(NotificationSource.from(tenant), false).stream()
+ .filter(n -> n.type() == Notification.Type.account).map(Notification::title)
+ .findFirst().orElseThrow();
+ }
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java
index 2c54c0c9fb6..eb7458af0f7 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.TenantName;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java
index 6452edc9e61..a0312d2b52d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java
index 7cfe5dee3de..8d2cc5d9b55 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.NodeResources;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
index f39374a2a89..4805e1c3853 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Environment;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainerTest.java
index 4c3463895a1..60dc1ea7fd5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainerTest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.ApplicationId;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
index 2f84b58dbae..11b1140094b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
index 9a2870f53f9..8a441547da6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgraderTest.java
index df0afda838e..bc3a5808989 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgraderTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainerTest.java
index 1e1079a3314..25a7044d6ea 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainerTest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.CloudAccount;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java
index cbc69e52119..fe9e9b28655 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
@@ -26,6 +26,7 @@ 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.SecretStoreMock;
+import com.yahoo.vespa.hosted.controller.routing.EndpointConfig;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -33,6 +34,7 @@ import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.stream.Stream;
@@ -40,12 +42,10 @@ import java.util.stream.Stream;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.devUsEast1;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.perfUsEast3;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.productionUsCentral1;
-import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.productionUsEast3;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.productionUsWest1;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.stagingTest;
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentContext.systemTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -120,6 +120,7 @@ public class EndpointCertificateMaintainerTest {
var applicationPackage = new ApplicationPackageBuilder()
.region("us-west-1")
+ .container("default")
.build();
DeploymentContext deploymentContext = deploymentTester.newDeploymentContext("tenant", "application", "default");
@@ -137,7 +138,7 @@ public class EndpointCertificateMaintainerTest {
tester.clock().advance(Duration.ofDays(3));
secretStore.setSecret(assignedCertificate.certificate().keyName(), "foo", 1);
secretStore.setSecret(assignedCertificate.certificate().certName(), "bar", 1);
- tester.controller().serviceRegistry().endpointCertificateProvider().requestCaSignedCertificate("preprovisioned." + assignedCertificate.certificate().randomizedId().get(), assignedCertificate.certificate().requestedDnsSans(), Optional.of(assignedCertificate.certificate()), "rsa_2048", false);
+ tester.controller().serviceRegistry().endpointCertificateProvider().requestCaSignedCertificate("preprovisioned." + assignedCertificate.certificate().generatedId().get(), assignedCertificate.certificate().requestedDnsSans(), Optional.of(assignedCertificate.certificate()), "rsa_2048", false);
// We should now pick up the new key and cert version + uuid, but not force trigger deployment yet
assertEquals(0.0, maintainer.maintain(), 0.0000001);
@@ -163,7 +164,7 @@ public class EndpointCertificateMaintainerTest {
private EndpointCertificateMaintainer.EligibleJob makeDeploymentAtAge(int ageInDays) {
var deployment = new Deployment(ZoneId.defaultId(), CloudAccount.empty, RevisionId.forProduction(1), Version.emptyVersion,
- Instant.now().minus(ageInDays, ChronoUnit.DAYS), DeploymentMetrics.none, DeploymentActivity.none, QuotaUsage.none, OptionalDouble.empty());
+ Instant.now().minus(ageInDays, ChronoUnit.DAYS), DeploymentMetrics.none, DeploymentActivity.none, QuotaUsage.none, OptionalDouble.empty(), Map.of());
return new EndpointCertificateMaintainer.EligibleJob(deployment, ApplicationId.defaultId(), JobType.prod("somewhere"));
}
@@ -191,68 +192,6 @@ public class EndpointCertificateMaintainerTest {
}
@Test
- void production_deployment_certificates_are_assigned_random_id() {
- var app = ApplicationId.from("tenant", "app", "default");
- DeploymentTester deploymentTester = new DeploymentTester(tester);
- deployToAssignCert(deploymentTester, app, List.of(systemTest, stagingTest, productionUsWest1), Optional.empty());
- assertEquals(1, tester.curator().readAssignedCertificates().size());
-
- maintainer.maintain();
- assertEquals(2, tester.curator().readAssignedCertificates().size());
-
- // Verify random id is same for application and instance certificates
- Optional<AssignedCertificate> applicationCertificate = tester.curator().readAssignedCertificate(TenantAndApplicationId.from(app), Optional.empty());
- assertTrue(applicationCertificate.isPresent());
- Optional<AssignedCertificate> instanceCertificate = tester.curator().readAssignedCertificate(TenantAndApplicationId.from(app), Optional.of(app.instance()));
- assertTrue(instanceCertificate.isPresent());
- assertEquals(instanceCertificate.get().certificate().randomizedId(), applicationCertificate.get().certificate().randomizedId());
-
- // Verify the 3 wildcard random names are same in all certs
- List<String> appWildcardSans = applicationCertificate.get().certificate().requestedDnsSans();
- assertEquals(3, appWildcardSans.size());
- List<String> instanceSans = instanceCertificate.get().certificate().requestedDnsSans();
- List<String> wildcards = instanceSans.stream().filter(appWildcardSans::contains).toList();
- assertEquals(appWildcardSans, wildcards);
- }
-
- @Test
- void existing_application_randomid_is_copied_to_new_instance_deployments() {
- var instance1 = ApplicationId.from("tenant", "prod", "instance1");
- var instance2 = ApplicationId.from("tenant", "prod", "instance2");
-
- DeploymentTester deploymentTester = new DeploymentTester(tester);
- deployToAssignCert(deploymentTester, instance1, List.of(systemTest, stagingTest,productionUsWest1),Optional.of("instance1"));
- assertEquals(1, tester.curator().readAssignedCertificates().size());
- maintainer.maintain();
-
- String randomId = tester.curator().readAssignedCertificate(instance1).get().certificate().randomizedId().get();
-
- deployToAssignCert(deploymentTester, instance2, List.of(productionUsWest1), Optional.of("instance1,instance2"));
- maintainer.maintain();
- assertEquals(3, tester.curator().readAssignedCertificates().size());
-
- assertEquals(randomId, tester.curator().readAssignedCertificate(instance1).get().certificate().randomizedId().get());
- }
-
- @Test
- void dev_certificates_are_not_assigned_application_level_certificate() {
- var devApp = ApplicationId.from("tenant", "devonly", "foo");
- DeploymentTester deploymentTester = new DeploymentTester(tester);
- deployToAssignCert(deploymentTester, devApp, List.of(devUsEast1), Optional.empty());
- assertEquals(1, tester.curator().readAssignedCertificates().size());
- List<String> originalRequestedSans = tester.curator().readAssignedCertificate(devApp).get().certificate().requestedDnsSans();
- maintainer.maintain();
- assertEquals(1, tester.curator().readAssignedCertificates().size());
-
- // Verify certificate is assigned random id and 3 new names
- Optional<AssignedCertificate> assignedCertificate = tester.curator().readAssignedCertificate(devApp);
- assertTrue(assignedCertificate.get().certificate().randomizedId().isPresent());
- List<String> newRequestedSans = assignedCertificate.get().certificate().requestedDnsSans();
- List<String> randomizedNames = newRequestedSans.stream().filter(san -> !originalRequestedSans.contains(san)).toList();
- assertEquals(3, randomizedNames.size());
- }
-
- @Test
void deploy_to_other_manual_zone_refreshes_cert() {
String devSan = "*.foo.manual.tenant.us-east-1.dev.vespa.oath.cloud";
String perfSan = "*.foo.manual.tenant.us-east-3.perf.vespa.oath.cloud";
@@ -301,10 +240,6 @@ public class EndpointCertificateMaintainerTest {
Assertions.assertThat(usCentralWestSans).contains(centralSan);
}
- private void deploy() {
-
- }
-
private void deployToAssignCert(DeploymentTester tester, ApplicationId applicationId, List<JobType> jobTypes, Optional<String> instances) {
var applicationPackageBuilder = new ApplicationPackageBuilder();
@@ -322,19 +257,14 @@ public class EndpointCertificateMaintainerTest {
jobs.forEach(deploymentContext::runJob);
}
- EndpointCertificate certificate(List<String> sans) {
- return new EndpointCertificate("keyName", "certName", 0, 0, "root-request-uuid", Optional.of("leaf-request-uuid"), List.of(), "issuer", Optional.empty(), Optional.empty(), Optional.empty());
- }
-
-
private static AssignedCertificate assignedCertificate(ApplicationId instance, EndpointCertificate certificate) {
- return new AssignedCertificate(TenantAndApplicationId.from(instance), Optional.of(instance.instance()), certificate);
+ return new AssignedCertificate(TenantAndApplicationId.from(instance), Optional.of(instance.instance()), certificate, false);
}
private void prepareCertificatePool(int numCertificates) {
- ((InMemoryFlagSource)tester.controller().flagSource()).withIntFlag(PermanentFlags.CERT_POOL_SIZE.id(), numCertificates);
- ((InMemoryFlagSource)tester.controller().flagSource()).withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true);
+ ((InMemoryFlagSource) tester.controller().flagSource()).withIntFlag(PermanentFlags.CERT_POOL_SIZE.id(), numCertificates);
+ ((InMemoryFlagSource) tester.controller().flagSource()).withStringFlag(Flags.ENDPOINT_CONFIG.id(), EndpointConfig.generated.name());
// Provision certificates
for (int i = 0; i < numCertificates; i++) {
@@ -351,4 +281,5 @@ public class EndpointCertificateMaintainerTest {
});
certificatePoolMaintainer.maintain();
}
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdaterTest.java
index 1ef32b8e347..583800caefa 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdaterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdaterTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
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 3ee6c7aadc3..d96de8df6fd 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
@@ -1,8 +1,10 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
+import ai.vespa.metrics.ControllerMetrics;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.jdisc.test.MockMetric;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
@@ -22,6 +24,7 @@ import com.yahoo.vespa.hosted.controller.deployment.StepRunner;
import com.yahoo.vespa.hosted.controller.deployment.Submission;
import com.yahoo.vespa.hosted.controller.deployment.Versions;
import com.yahoo.vespa.hosted.controller.integration.MetricsMock;
+import com.yahoo.vespa.hosted.controller.maintenance.JobRunner.Metrics;
import org.junit.jupiter.api.Test;
import java.time.Duration;
@@ -31,16 +34,24 @@ import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Queue;
import java.util.Set;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Phaser;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -65,10 +76,12 @@ 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 com.yahoo.vespa.hosted.controller.deployment.Step.startTests;
+import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -121,11 +134,56 @@ public class JobRunnerTest {
}
@Test
+ void metrics() {
+ Phaser phaser = new Phaser(4);
+ StepRunner runner = (step, id) -> {
+ phaser.arriveAndAwaitAdvance();
+ phaser.arriveAndAwaitAdvance();
+ return Optional.of(running);
+ };
+ ExecutorService executor = new ThreadPoolExecutor(3, 3, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), (task, pool) -> task.run());
+ DeploymentTester tester = new DeploymentTester();
+ MockMetric metric = new MockMetric();
+ Metrics metrics = new Metrics(metric, Duration.ofDays(1));
+ JobRunner jobs = new JobRunner(tester.controller(), Duration.ofDays(1), executor, runner, metrics);
+ tester.newDeploymentContext().submit();
+
+ assertEquals(Map.of(), metric.metrics());
+ metrics.report();
+ assertEquals(Map.of(ControllerMetrics.DEPLOYMENT_JOBS_QUEUED.baseName(),
+ Map.of(Map.of(), 0.0),
+ ControllerMetrics.DEPLOYMENT_JOBS_ACTIVE.baseName(),
+ Map.of(Map.of(), 0.0)),
+ metric.metrics());
+ tester.triggerJobs();
+
+ assertEquals(2, tester.jobs().active().size());
+ jobs.maintain();
+ phaser.arriveAndAwaitAdvance();
+ metrics.report();
+ assertEquals(Map.of(ControllerMetrics.DEPLOYMENT_JOBS_QUEUED.baseName(),
+ Map.of(Map.of(), 1.0),
+ ControllerMetrics.DEPLOYMENT_JOBS_ACTIVE.baseName(),
+ Map.of(Map.of(), 3.0)),
+ metric.metrics());
+
+ jobs.shutdown();
+ phaser.forceTermination();
+ jobs.awaitShutdown();
+ metrics.report();
+ assertEquals(Map.of(ControllerMetrics.DEPLOYMENT_JOBS_QUEUED.baseName(),
+ Map.of(Map.of(), 0.0),
+ ControllerMetrics.DEPLOYMENT_JOBS_ACTIVE.baseName(),
+ Map.of(Map.of(), 0.0)),
+ metric.metrics());
+ }
+
+ @Test
void stepLogic() {
DeploymentTester tester = new DeploymentTester();
JobController jobs = tester.controller().jobController();
Map<Step, RunStatus> outcomes = new EnumMap<>(Step.class);
- JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), inThreadExecutor(), mappedRunner(outcomes));
+ JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), inThreadInOrderExecutor(), mappedRunner(outcomes));
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
ApplicationId id = appId.defaultInstance();
@@ -272,7 +330,7 @@ public class JobRunnerTest {
void historyPruning() {
DeploymentTester tester = new DeploymentTester();
JobController jobs = tester.controller().jobController();
- JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), inThreadExecutor(), (id, step) -> Optional.of(running));
+ JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), inThreadInOrderExecutor(), (id, step) -> Optional.of(running));
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
ApplicationId instanceId = appId.defaultInstance();
@@ -297,7 +355,7 @@ public class JobRunnerTest {
assertFalse(jobs.details(new RunId(instanceId, systemTest, 1)).isPresent());
assertTrue(jobs.details(new RunId(instanceId, systemTest, 65)).isPresent());
- JobRunner failureRunner = new JobRunner(tester.controller(), Duration.ofDays(1), inThreadExecutor(), (id, step) -> Optional.of(error));
+ JobRunner failureRunner = new JobRunner(tester.controller(), Duration.ofDays(1), inThreadInOrderExecutor(), (id, step) -> Optional.of(error));
// Make all but the oldest of the 54 jobs a failure.
for (int i = 0; i < jobs.historyLength() - 1; i++) {
@@ -369,7 +427,7 @@ public class JobRunnerTest {
DeploymentTester tester = new DeploymentTester();
JobController jobs = tester.controller().jobController();
Map<Step, RunStatus> outcomes = new EnumMap<>(Step.class);
- JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), inThreadExecutor(), mappedRunner(outcomes));
+ JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), inThreadInOrderExecutor(), mappedRunner(outcomes));
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
ApplicationId id = appId.defaultInstance();
@@ -387,7 +445,7 @@ public class JobRunnerTest {
DeploymentTester tester = new DeploymentTester();
JobController jobs = tester.controller().jobController();
Map<Step, RunStatus> outcomes = new EnumMap<>(Step.class);
- JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), inThreadExecutor(), mappedRunner(outcomes));
+ JobRunner runner = new JobRunner(tester.controller(), Duration.ofDays(1), inThreadInOrderExecutor(), mappedRunner(outcomes));
TenantAndApplicationId appId = tester.createApplication("tenant", "real", "default").id();
ApplicationId id = appId.defaultInstance();
@@ -423,19 +481,58 @@ public class JobRunnerTest {
assertEquals(1, metric.getMetric(context::equals, JobMetrics.noTests).get().intValue());
}
+ @Test
+ void testInThreadExecutor() throws InterruptedException {
+ ExecutorService executor = inThreadInOrderExecutor();
+ AtomicInteger c = new AtomicInteger(0), d = new AtomicInteger(0);
+ Consumer<AtomicInteger> task = i -> executor.execute(() -> {
+ executor.execute(() -> {
+ i.set(2);
+ executor.execute(() -> i.set(4));
+ });
+ executor.execute(() -> i.set(3));
+ i.set(1);
+ });
+ Thread s = new Thread(() -> task.accept(d));
+ s.start();
+ task.accept(c);
+ s.join();
+ assertEquals(4, c.get());
+ assertEquals(4, d.get());
+ assertEquals("executor is shut down",
+ assertThrows(RejectedExecutionException.class,
+ () -> executor.execute(() -> {
+ executor.execute(() -> executor.execute(() -> { c.set(6); }));
+ executor.shutdown();
+ c.set(5);
+ })).getMessage());
+ assertEquals(5, c.get());
+ }
+
private void start(JobController jobs, ApplicationId id, JobType type) {
jobs.start(id, type, versions, false, Reason.empty());
}
- public static ExecutorService inThreadExecutor() {
+ /** Dummy test executor for unit tests. Runs tasks BFS rather than DFS, like a simple {@code Runnable::run} would do. No real shutdown logic. */
+ public static ExecutorService inThreadInOrderExecutor() {
return new AbstractExecutorService() {
- final AtomicBoolean shutDown = new AtomicBoolean(false);
+ private final ThreadLocal<Boolean> inExecute = ThreadLocal.withInitial(() -> false);
+ private final ThreadLocal<Queue<Runnable>> tasks = ThreadLocal.withInitial(ConcurrentLinkedQueue::new);
+ private final AtomicBoolean shutDown = new AtomicBoolean(false);
+ @Override
+ public void execute(Runnable command) {
+ if (isShutdown()) throw new RejectedExecutionException("executor is shut down");
+ tasks.get().add(requireNonNull(command));
+ if (inExecute.get()) return;
+ inExecute.set(true);
+ try { Runnable task; while (null != (task = tasks.get().poll())) task.run(); }
+ finally { inExecute.set(false); }
+ }
@Override public void shutdown() { shutDown.set(true); }
@Override public List<Runnable> shutdownNow() { shutDown.set(true); return Collections.emptyList(); }
@Override public boolean isShutdown() { return shutDown.get(); }
@Override public boolean isTerminated() { return shutDown.get(); }
@Override public boolean awaitTermination(long timeout, TimeUnit unit) { return true; }
- @Override public void execute(Runnable command) { command.run(); }
};
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MeteringMonitorMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MeteringMonitorMaintainerTest.java
index 65dab67663e..a15deb17c0b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MeteringMonitorMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MeteringMonitorMaintainerTest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.ApplicationId;
@@ -49,4 +50,4 @@ public class MeteringMonitorMaintainerTest {
assertEquals(now - lastSnapshot, metrics.getMetric(MeteringMonitorMaintainer.METERING_AGE_METRIC_NAME));
}
-} \ No newline at end of file
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
index 593d788fd7d..aa5d6d23890 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporterTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcherTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcherTest.java
index cf5567f5f2f..c0975049f63 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcherTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/NameServiceDispatcherTest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.vespa.hosted.controller.ControllerTester;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeSchedulerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeSchedulerTest.java
index dd3174fce56..0adb71c1b68 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeSchedulerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeSchedulerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java
index fad78edc58f..7e2b99b83f4 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgraderTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java
index 8f4db5cad47..bfe140aa4c1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdaterTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
index 15855770c0b..09f3d7453db 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/OutstandingChangeDeployerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggererTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggererTest.java
index ecb5bf167d0..478c0a10585 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggererTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggererTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.ApplicationId;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
index 8f9ba75f95c..d93dcf71317 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainerTest.java
@@ -1,8 +1,10 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Cloud;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeResources;
@@ -62,9 +64,9 @@ public class ResourceMeterMaintainerTest {
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().cost().getAsDouble())));
List<ResourceSnapshot> resourceSnapshots = List.of(
- new ResourceSnapshot(app1, resources(12, 34, 56), Instant.EPOCH, z1, 0),
- new ResourceSnapshot(app1, resources(23, 45, 67), Instant.EPOCH, z2, 0),
- new ResourceSnapshot(app2, resources(34, 56, 78), Instant.EPOCH, z1, 0));
+ new ResourceSnapshot(app1, resources(12, 34, 56), Instant.EPOCH, z1, 0, CloudAccount.empty),
+ new ResourceSnapshot(app1, resources(23, 45, 67), Instant.EPOCH, z2, 0, CloudAccount.empty),
+ new ResourceSnapshot(app2, resources(34, 56, 78), Instant.EPOCH, z1, 0, CloudAccount.empty));
maintainer.updateDeploymentCost(resourceSnapshots);
assertCost.accept(app1, Map.of(z1, 1.72, z2, 3.05));
@@ -72,9 +74,9 @@ public class ResourceMeterMaintainerTest {
// Remove a region from app1 and add region to app2
resourceSnapshots = List.of(
- new ResourceSnapshot(app1, resources(23, 45, 67), Instant.EPOCH, z2, 0),
- new ResourceSnapshot(app2, resources(34, 56, 78), Instant.EPOCH, z1, 0),
- new ResourceSnapshot(app2, resources(45, 67, 89), Instant.EPOCH, z2, 0));
+ new ResourceSnapshot(app1, resources(23, 45, 67), Instant.EPOCH, z2, 0, CloudAccount.empty),
+ new ResourceSnapshot(app2, resources(34, 56, 78), Instant.EPOCH, z1, 0, CloudAccount.empty),
+ new ResourceSnapshot(app2, resources(45, 67, 89), Instant.EPOCH, z2, 0, CloudAccount.empty));
maintainer.updateDeploymentCost(resourceSnapshots);
assertCost.accept(app1, Map.of(z2, 3.05));
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainerTest.java
index 86c6e740a17..f3ca6ba7b41 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.ApplicationId;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainerTest.java
index 174cf93286c..923fa34abf1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java
index d3d66715202..d38c2006bf5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/SystemUpgraderTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainerTest.java
index 2f36287df45..94c2448e6cc 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.maintenance;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
index f1e8697cf41..c8853c008f7 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
@@ -1,10 +1,9 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
-import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.application.Change;
@@ -26,6 +25,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
@@ -40,7 +40,6 @@ import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger.Cha
import static com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger.ChangesToCancel.PLATFORM;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -1100,8 +1099,9 @@ public class UpgraderTest {
default2.instanceId(), default2);
// Throttle upgrades per run
- ((ManualClock) tester.controller().clock()).setInstant(Instant.ofEpochMilli(1589787107000L)); // Fixed random seed
- Upgrader upgrader = new Upgrader(tester.controller(), Duration.ofMinutes(10));
+ Upgrader upgrader = new Upgrader(tester.controller(),
+ Duration.ofMinutes(10),
+ new Random(1589787107000L)); // Fixed random seed
upgrader.setUpgradesPerMinute(0.1);
// Trigger some upgrades
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainerTest.java
index 4fe04b25577..4a49638bfc2 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.SystemName;
@@ -55,4 +55,4 @@ public class UserManagementMaintainerTest {
return tester;
}
-} \ No newline at end of file
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainerTest.java
index cba916df52a..6ffaf5fe0b6 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdaterTest.java
index 962288f9073..6e6013b070e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdaterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdaterTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.vespa.hosted.controller.ControllerTester;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatterTest.java
index 164df0a27f5..875487144d9 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatterTest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.yahoo.config.provision.ApplicationId;
@@ -5,16 +6,16 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.RegionName;
-import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
-import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock;
import org.junit.jupiter.api.Test;
+import java.net.URI;
import java.time.Instant;
import java.util.List;
@@ -30,9 +31,8 @@ public class NotificationFormatterTest {
private final ApplicationId applicationId = ApplicationId.from(tenant, application, instance);
private final DeploymentId deploymentId = new DeploymentId(applicationId, ZoneId.defaultId());
private final ClusterSpec.Id cluster = new ClusterSpec.Id("content");
- private final ZoneRegistryMock zoneRegistry = new ZoneRegistryMock(SystemName.Public);
- private final NotificationFormatter formatter = new NotificationFormatter(zoneRegistry);
+ private final NotificationFormatter formatter = new NotificationFormatter(new ConsoleUrls(URI.create("https://console.tld")));
@Test
void applicationPackage() {
@@ -40,7 +40,7 @@ public class NotificationFormatterTest {
var content = formatter.format(notification);
assertEquals("Application package", content.prettyType());
assertEquals("Application package for myapp.beta has 2 warnings", content.messagePrefix());
- assertEquals("https://dashboard.tld/scoober.myapp.beta", content.uri().toString());
+ assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance/beta", content.uri());
}
@Test
@@ -50,7 +50,7 @@ public class NotificationFormatterTest {
var content = formatter.format(notification);
assertEquals("Deployment", content.prettyType());
assertEquals("production-default #1001 for myapp.beta has a warning", content.messagePrefix());
- assertEquals("https://dashboard.tld/scoober.myapp.beta/production-default/1001", content.uri().toString());
+ assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance/beta/job/production-default/run/1001", content.uri());
}
@Test
@@ -60,7 +60,7 @@ public class NotificationFormatterTest {
var content = formatter.format(notification);
assertEquals("Deployment", content.prettyType());
assertEquals("production-default #1001 for myapp.beta has failed", content.messagePrefix());
- assertEquals("https://dashboard.tld/scoober.myapp.beta/production-default/1001", content.uri().toString());
+ assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance/beta/job/production-default/run/1001", content.uri());
}
@Test
@@ -69,7 +69,7 @@ public class NotificationFormatterTest {
var content = formatter.format(notification);
assertEquals("Test package", content.prettyType());
assertEquals("There is a problem with tests for myapp", content.messagePrefix());
- assertEquals("https://dashboard.tld/scoober/myapp", content.uri().toString());
+ assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance", content.uri());
}
@Test
@@ -78,7 +78,7 @@ public class NotificationFormatterTest {
var content = formatter.format(notification);
assertEquals("Reindex", content.prettyType());
assertEquals("Cluster content in prod.default for myapp.beta is reindexing", content.messagePrefix());
- assertEquals("https://dashboard.tld/scoober.myapp.beta?beta.prod.default=clusters%2Ccontent%3Dstatus", content.uri().toString());
+ assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance/beta?beta.prod.default=clusters%2Ccontent%3Dreindexing", content.uri());
}
@Test
@@ -87,6 +87,6 @@ public class NotificationFormatterTest {
var content = formatter.format(notification);
assertEquals("Nearly feed blocked", content.prettyType());
assertEquals("Cluster content in prod.default for myapp.beta is nearly feed blocked", content.messagePrefix());
- assertEquals("https://dashboard.tld/scoober.myapp.beta?beta.prod.default=clusters%2Ccontent", content.uri().toString());
+ assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance/beta?beta.prod.default=clusters%2Ccontent", content.uri());
}
-} \ No newline at end of file
+}
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 228a61cebc6..e41be11c846 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
@@ -1,8 +1,9 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. 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.collect.ImmutableBiMap;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.CloudName;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
@@ -14,7 +15,10 @@ import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ApplicationReindexing;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
@@ -30,6 +34,7 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import java.net.URI;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
@@ -69,7 +74,8 @@ public class NotificationsDbTest {
Optional.empty(),
Instant.EPOCH,
List.of(),
- Optional.empty());
+ Optional.empty(),
+ PlanId.from("none"));
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"),
@@ -82,7 +88,8 @@ public class NotificationsDbTest {
private final MockCuratorDb curatorDb = new MockCuratorDb(SystemName.Public);
private final MockMailer mailer = new MockMailer();
private final FlagSource flagSource = new InMemoryFlagSource().withBooleanFlag(PermanentFlags.NOTIFICATION_DISPATCH_FLAG.id(), true);
- private final NotificationsDb notificationsDb = new NotificationsDb(clock, curatorDb, new Notifier(curatorDb, new ZoneRegistryMock(SystemName.cd), mailer, flagSource));
+ private final ConsoleUrls consoleUrls = new ConsoleUrls(URI.create("https://console.tld"));
+ private final NotificationsDb notificationsDb = new NotificationsDb(clock, curatorDb, new Notifier(curatorDb, consoleUrls, mailer, flagSource), consoleUrls);
@Test
void list_test() {
@@ -100,10 +107,10 @@ public class NotificationsDbTest {
Notification notification2 = notification(12345, Type.deployment, Level.error, NotificationSource.from(ApplicationId.from(tenant.value(), "app3", "instance2")), "instance msg #3");
// Replace the 3rd notification
- notificationsDb.setNotification(notification1.source(), notification1.type(), notification1.level(), notification1.messages());
+ setNotification(notification1);
// Notification for a new app, add without replacement
- notificationsDb.setNotification(notification2.source(), notification2.type(), notification2.level(), notification2.messages());
+ setNotification(notification2);
List<Notification> expected = notificationIndices(0, 1, 3, 4, 5);
expected.addAll(List.of(notification1, notification2));
@@ -117,19 +124,19 @@ public class NotificationsDbTest {
Notification notification3 = notification(12345, Type.reindex, Level.warning, NotificationSource.from(new DeploymentId(ApplicationId.from(tenant.value(), "app2", "instance2"), ZoneId.defaultId()), new ClusterSpec.Id("content")), "instance msg #2");
;
var a = notifications.get(0);
- notificationsDb.setNotification(a.source(), a.type(), a.level(), a.messages());
+ setNotification(a);
assertEquals(0, mailer.inbox(email.getEmailAddress()).size());
// Replace the 3rd notification. but don't change source or type
- notificationsDb.setNotification(notification1.source(), notification1.type(), notification1.level(), notification1.messages());
+ setNotification(notification1);
assertEquals(0, mailer.inbox(email.getEmailAddress()).size());
// Notification for a new app, add without replacement
- notificationsDb.setNotification(notification2.source(), notification2.type(), notification2.level(), notification2.messages());
+ setNotification(notification2);
assertEquals(1, mailer.inbox(email.getEmailAddress()).size());
// Notification for new type on existing app
- notificationsDb.setNotification(notification3.source(), notification3.type(), notification3.level(), notification3.messages());
+ setNotification(notification3);
assertEquals(2, mailer.inbox(email.getEmailAddress()).size());
}
@@ -197,17 +204,19 @@ public class NotificationsDbTest {
// One resource is at warning
notificationsDb.setDeploymentMetricsNotifications(deploymentId, List.of(clusterMetrics("cluster1", 0.88, 0.9, 0.3, 0.5)), emptyReindexing);
- expected.add(notification(12345, Type.feedBlock, Level.warning, sourceCluster1, "disk (usage: 88.0%, feed block limit: 90.0%)"));
+ expected.add(notification(12345, Type.feedBlock, Level.warning, "Cluster [cluster1](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1?instance1.prod.us-south-3=clusters%2Ccluster1) in **prod.us-south-3** for **app1.instance1** is nearly feed blocked",
+ sourceCluster1, "disk (usage: 88.0%, feed block limit: 90.0%)"));
assertEquals(expected, curatorDb.readNotifications(tenant));
// Both resources over the limit
notificationsDb.setDeploymentMetricsNotifications(deploymentId, List.of(clusterMetrics("cluster1", 0.95, 0.9, 0.3, 0.5)), emptyReindexing);
- expected.set(6, notification(12345, Type.feedBlock, Level.error, sourceCluster1, "disk (usage: 95.0%, feed block limit: 90.0%)"));
+ expected.set(6, notification(12345, Type.feedBlock, Level.error, "Cluster [cluster1](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1?instance1.prod.us-south-3=clusters%2Ccluster1) in **prod.us-south-3** for **app1.instance1** is feed blocked",
+ sourceCluster1, "disk (usage: 95.0%, feed block limit: 90.0%)"));
assertEquals(expected, curatorDb.readNotifications(tenant));
// One resource at warning, one at error: Only show error message
notificationsDb.setDeploymentMetricsNotifications(deploymentId, List.of(clusterMetrics("cluster1", 0.95, 0.9, 0.7, 0.5)), emptyReindexing);
- expected.set(6, notification(12345, Type.feedBlock, Level.error, sourceCluster1,
+ expected.set(6, notification(12345, Type.feedBlock, Level.error, "Cluster [cluster1](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1?instance1.prod.us-south-3=clusters%2Ccluster1) in **prod.us-south-3** for **app1.instance1** is feed blocked", sourceCluster1,
"memory (usage: 70.0%, feed block limit: 50.0%)", "disk (usage: 95.0%, feed block limit: 90.0%)"));
assertEquals(expected, curatorDb.readNotifications(tenant));
}
@@ -227,9 +236,9 @@ public class NotificationsDbTest {
"build", reindexingStatus(null, 0.50)))));
notificationsDb.setDeploymentMetricsNotifications(deploymentId, List.of(
clusterMetrics("cluster1", 0.88, 0.9, 0.3, 0.5), clusterMetrics("cluster2", 0.6, 0.8, 0.9, 0.75), clusterMetrics("cluster3", 0.1, 0.8, 0.2, 0.9)), applicationReindexing1);
- expected.add(notification(12345, Type.feedBlock, Level.warning, sourceCluster1, "disk (usage: 88.0%, feed block limit: 90.0%)"));
- expected.add(notification(12345, Type.feedBlock, Level.error, sourceCluster2, "memory (usage: 90.0%, feed block limit: 75.0%)"));
- expected.add(notification(12345, Type.reindex, Level.info, sourceCluster3, "document type 'announcements' reindexing due to a schema change (75.0% done)", "document type 'build' (50.0% done)"));
+ expected.add(notification(12345, Type.feedBlock, Level.warning, "Cluster [cluster1](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1?instance1.prod.us-south-3=clusters%2Ccluster1) in **prod.us-south-3** for **app1.instance1** is nearly feed blocked", sourceCluster1, "disk (usage: 88.0%, feed block limit: 90.0%)"));
+ expected.add(notification(12345, Type.feedBlock, Level.error, "Cluster [cluster2](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1?instance1.prod.us-south-3=clusters%2Ccluster2) in **prod.us-south-3** for **app1.instance1** is feed blocked", sourceCluster2, "memory (usage: 90.0%, feed block limit: 75.0%)"));
+ expected.add(notification(12345, Type.reindex, Level.info, "Cluster [cluster3](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1?instance1.prod.us-south-3=clusters%2Ccluster3%3Dreindexing) in **prod.us-south-3** for **app1.instance1** is [reindexing](https://docs.vespa.ai/en/operations/reindexing.html)", sourceCluster3, "document type 'announcements' reindexing due to a schema change (75.0% done)", "document type 'build' (50.0% done)"));
assertEquals(expected, curatorDb.readNotifications(tenant));
// Cluster1 improves, while cluster3 starts having feed block issues and finishes reindexing 'build' documents
@@ -239,12 +248,41 @@ public class NotificationsDbTest {
"build", reindexingStatus(null, null)))));
notificationsDb.setDeploymentMetricsNotifications(deploymentId, List.of(
clusterMetrics("cluster1", 0.15, 0.9, 0.3, 0.5), clusterMetrics("cluster2", 0.6, 0.8, 0.9, 0.75), clusterMetrics("cluster3", 0.78, 0.8, 0.2, 0.9)), applicationReindexing2);
- expected.set(6, notification(12345, Type.feedBlock, Level.error, sourceCluster2, "memory (usage: 90.0%, feed block limit: 75.0%)"));
- expected.set(7, notification(12345, Type.feedBlock, Level.warning, sourceCluster3, "disk (usage: 78.0%, feed block limit: 80.0%)"));
- expected.set(8, notification(12345, Type.reindex, Level.info, sourceCluster3, "document type 'announcements' reindexing due to a schema change (90.0% done)"));
+ expected.set(6, notification(12345, Type.feedBlock, Level.error, "Cluster [cluster2](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1?instance1.prod.us-south-3=clusters%2Ccluster2) in **prod.us-south-3** for **app1.instance1** is feed blocked", sourceCluster2, "memory (usage: 90.0%, feed block limit: 75.0%)"));
+ expected.set(7, notification(12345, Type.feedBlock, Level.warning, "Cluster [cluster3](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1?instance1.prod.us-south-3=clusters%2Ccluster3) in **prod.us-south-3** for **app1.instance1** is nearly feed blocked", sourceCluster3, "disk (usage: 78.0%, feed block limit: 80.0%)"));
+ expected.set(8, notification(12345, Type.reindex, Level.info, "Cluster [cluster3](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1?instance1.prod.us-south-3=clusters%2Ccluster3%3Dreindexing) in **prod.us-south-3** for **app1.instance1** is [reindexing](https://docs.vespa.ai/en/operations/reindexing.html)", sourceCluster3, "document type 'announcements' reindexing due to a schema change (90.0% done)"));
assertEquals(expected, curatorDb.readNotifications(tenant));
}
+ @Test
+ void title_test() {
+ curatorDb.deleteNotifications(tenant);
+ TenantAndApplicationId tenantApp = TenantAndApplicationId.from(tenant.value(), "app1");
+ ApplicationId app = tenantApp.instance("instance1");
+ ZoneRegistryMock zoneRegistry = new ZoneRegistryMock(SystemName.Public);
+
+ notificationsDb.setApplicationPackageNotification(NotificationSource.from(tenantApp), List.of());
+ notificationsDb.setApplicationPackageNotification(NotificationSource.from(new DeploymentId(app, ZoneId.from("dev.us-east-3"))), List.of());
+ notificationsDb.setSubmissionNotification(tenantApp, "msg");
+ notificationsDb.setTestPackageNotification(tenantApp, List.of());
+ notificationsDb.setDeploymentNotification(new RunId(app, JobType.prod("us-east-3"), 123), "msg");
+ notificationsDb.setDeploymentNotification(new RunId(app, JobType.productionTestOf(ZoneId.from("prod.us-east-3")), 123), "msg");
+ notificationsDb.setDeploymentNotification(new RunId(app, JobType.systemTest(zoneRegistry, CloudName.AWS), 123), "msg");
+ notificationsDb.setDeploymentNotification(new RunId(app, JobType.stagingTest(zoneRegistry, CloudName.AWS), 123), "msg");
+ notificationsDb.setDeploymentNotification(new RunId(app, JobType.dev("us-east-3"), 123), "msg");
+ assertEquals(List.of(
+ "Application package for [app1](https://console.tld/tenant/tenant1/application/app1/prod/instance) has warnings",
+ "Application package for [app1.instance1](https://console.tld/tenant/tenant1/application/app1/dev/instance/instance1) has warnings",
+ "Application package for [app1](https://console.tld/tenant/tenant1/application/app1/prod/instance) has a warning",
+ "There are problems with tests for [app1](https://console.tld/tenant/tenant1/application/app1/prod/instance)",
+ "Deployment job [#123 to us-east-3](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1/job/production-us-east-3/run/123) for application **app1.instance1** has failed",
+ "Test job [#123 to us-east-3](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1/job/test-us-east-3/run/123) for application **app1.instance1** has failed",
+ "[System test #123](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1/job/system-test/run/123) for application **app1.instance1** has failed",
+ "[Staging test #123](https://console.tld/tenant/tenant1/application/app1/prod/instance/instance1/job/staging-test/run/123) for application **app1.instance1** has failed",
+ "Deployment job [#123 to dev.us-east-3](https://console.tld/tenant/tenant1/application/app1/dev/instance/instance1/job/dev-us-east-3/run/123) for application **app1.instance1** has failed"
+ ), notificationsDb.listNotifications(NotificationSource.from(tenant), false).stream().map(Notification::title).toList());
+ }
+
@BeforeEach
public void init() {
curatorDb.writeNotifications(tenant, notifications);
@@ -252,12 +290,20 @@ public class NotificationsDbTest {
mailer.reset();
}
+ private void setNotification(Notification notification) {
+ notificationsDb.setNotification(notification.source(), notification.type(), notification.level(), "", notification.messages(), Optional.empty());
+ }
+
private static List<Notification> notificationIndices(int... indices) {
return Arrays.stream(indices).mapToObj(notifications::get).collect(Collectors.toCollection(ArrayList::new));
}
private static Notification notification(long secondsSinceEpoch, Type type, Level level, NotificationSource source, String... messages) {
- return new Notification(Instant.ofEpochSecond(secondsSinceEpoch), type, level, source, List.of(messages));
+ return notification(secondsSinceEpoch, type, level, "", source, messages);
+ }
+
+ private static Notification notification(long secondsSinceEpoch, Type type, Level level, String title, NotificationSource source, String... messages) {
+ return new Notification(Instant.ofEpochSecond(secondsSinceEpoch), type, level, source, title, List.of(messages));
}
private static ClusterMetrics clusterMetrics(String clusterId,
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
index 15524e2748c..55531dff72d 100644
--- 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
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. 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.collect.ImmutableBiMap;
@@ -8,8 +9,9 @@ import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
+import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
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;
@@ -21,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
+import java.net.URI;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@@ -47,7 +50,8 @@ public class NotifierTest {
Optional.empty(),
Instant.EPOCH,
List.of(),
- Optional.empty());
+ Optional.empty(),
+ PlanId.from("none"));
MockCuratorDb curatorDb = new MockCuratorDb(SystemName.Public);
@@ -61,7 +65,7 @@ public class NotifierTest {
void dispatch() throws IOException {
var mailer = new MockMailer();
var flagSource = new InMemoryFlagSource().withBooleanFlag(PermanentFlags.NOTIFICATION_DISPATCH_FLAG.id(), true);
- var notifier = new Notifier(curatorDb, new ZoneRegistryMock(SystemName.cd), mailer, flagSource);
+ var notifier = new Notifier(curatorDb, new ConsoleUrls(URI.create("https://console.tld")), mailer, flagSource);
var notification = new Notification(Instant.now(), Notification.Type.testPackage, Notification.Level.warning,
NotificationSource.from(ApplicationId.from(tenant, ApplicationName.defaultName(), InstanceName.defaultName())),
@@ -72,7 +76,7 @@ public class NotifierTest {
var mail = mailer.inbox(email.getEmailAddress()).get(0);
assertEquals("[WARNING] Test package Vespa Notification for tenant1.default.default", mail.subject());
- assertEquals(new String(NotifierTest.class.getResourceAsStream("/mail/notification.txt").readAllBytes()), mail.htmlMessage().get());
+ assertEquals(new String(NotifierTest.class.getResourceAsStream("/mail/notification.html").readAllBytes()), mail.htmlMessage().get());
}
@Test
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index 13f7ec2a4ec..41362fd0b97 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
@@ -13,6 +13,7 @@ import com.yahoo.security.KeyUtils;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Instance;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
@@ -121,15 +122,15 @@ public class ApplicationSerializerTest {
ApplicationVersion applicationVersion2 = new ApplicationVersion(id, Optional.of(source), Optional.of("a@b"), Optional.of(compileVersion), Optional.empty(), Optional.of(Instant.ofEpochMilli(496)), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), Optional.empty(), 0);
Instant activityAt = Instant.parse("2018-06-01T10:15:30.00Z");
deployments.add(new Deployment(zone1, CloudAccount.empty, applicationVersion1.id(), Version.fromString("1.2.3"), Instant.ofEpochMilli(3),
- DeploymentMetrics.none, DeploymentActivity.none, QuotaUsage.none, OptionalDouble.empty()));
+ DeploymentMetrics.none, DeploymentActivity.none, QuotaUsage.none, OptionalDouble.empty(), Map.of(TokenId.of("foo"), Instant.ofEpochMilli(333))));
deployments.add(new Deployment(zone2, CloudAccount.from("001122334455"), applicationVersion2.id(), Version.fromString("1.2.3"), Instant.ofEpochMilli(5),
- new DeploymentMetrics(2, 3, 4, 5, 6,
- Optional.of(Instant.now().truncatedTo(ChronoUnit.MILLIS)),
- Map.of(DeploymentMetrics.Warning.all, 3)),
- DeploymentActivity.create(Optional.of(activityAt), Optional.of(activityAt),
- OptionalDouble.of(200), OptionalDouble.of(10)),
- QuotaUsage.create(OptionalDouble.of(23.5)),
- OptionalDouble.of(12.3)));
+ new DeploymentMetrics(2, 3, 4, 5, 6,
+ Optional.of(Instant.now().truncatedTo(ChronoUnit.MILLIS)),
+ Map.of(DeploymentMetrics.Warning.all, 3)),
+ DeploymentActivity.create(Optional.of(activityAt), Optional.of(activityAt),
+ OptionalDouble.of(200), OptionalDouble.of(10)),
+ QuotaUsage.create(OptionalDouble.of(23.5)),
+ OptionalDouble.of(12.3), Map.of()));
var rotationStatus = RotationStatus.from(Map.of(new RotationId("my-rotation"),
new RotationStatus.Targets(
@@ -237,6 +238,9 @@ public class ApplicationSerializerTest {
// Test quota
assertEquals(original.require(id1.instance()).deployments().get(zone2).quota().rate(), serialized.require(id1.instance()).deployments().get(zone2).quota().rate(), 0.001);
+
+ assertEquals(original.require(id1.instance()).deployments().get(zone1).dataPlaneTokens(), serialized.require(id1.instance()).deployments().get(zone1).dataPlaneTokens());
+ assertEquals(original.require(id1.instance()).deployments().get(zone2).dataPlaneTokens(), serialized.require(id1.instance()).deployments().get(zone2).dataPlaneTokens());
}
@Test
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java
index 1d1b1124d22..35fa2d95e1f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ArchiveBucketsSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.CloudAccount;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializerTest.java
index 50e12c43829..ac33f1d9e3a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/AuditLogSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.vespa.hosted.controller.auditlog.AuditLog;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStoreTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStoreTest.java
index 59433a6bb69..563f5ba73f8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStoreTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/BufferedLogStoreTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.ApplicationId;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/CertifiedOsVersionSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/CertifiedOsVersionSerializerTest.java
index fcf5be08abf..7d21905ea3e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/CertifiedOsVersionSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/CertifiedOsVersionSerializerTest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializerTest.java
index a429e9090cc..8c79395b423 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ChangeRequestSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.zone.ZoneId;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ControllerVersionSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ControllerVersionSerializerTest.java
index a7188cfa93e..219a4be6143 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ControllerVersionSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ControllerVersionSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/DnsChallengeSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/DnsChallengeSerializerTest.java
index b224975fe05..ca22978bae0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/DnsChallengeSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/DnsChallengeSerializerTest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.ApplicationId;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializerTest.java
index 4d14376034f..bccc2772da0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/EndpointCertificateSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializerTest.java
index d03a17edde2..bab99314a41 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/LogSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/MailVerificationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/MailVerificationSerializerTest.java
index 69c6e13ba62..17f0fd950fd 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/MailVerificationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/MailVerificationSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.TenantName;
@@ -28,4 +28,4 @@ public class MailVerificationSerializerTest {
var deserialized = MailVerificationSerializer.fromSlime(serialized);
assertEquals(original, deserialized);
}
-} \ No newline at end of file
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializerTest.java
index ad9ebd2a968..4aefc5bc368 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NameServiceQueueSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.HostName;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializerTest.java
index c9755672232..65da43a3ec4 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/NotificationsSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.ApplicationId;
@@ -8,6 +8,7 @@ import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
+import com.yahoo.vespa.hosted.controller.notification.MailTemplating;
import com.yahoo.vespa.hosted.controller.notification.Notification;
import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
import org.junit.jupiter.api.Test;
@@ -15,6 +16,7 @@ import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.time.Instant;
import java.util.List;
+import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -27,6 +29,8 @@ public class NotificationsSerializerTest {
void serialization_test() throws IOException {
NotificationsSerializer serializer = new NotificationsSerializer();
TenantName tenantName = TenantName.from("tenant1");
+ var mail = Notification.MailContent.fromTemplate(MailTemplating.Template.DEFAULT_MAIL_CONTENT).subject("My mail subject")
+ .with("string-param", "string-value").with("list-param", List.of("elem1", "elem2")).build();
List<Notification> notifications = List.of(
new Notification(Instant.ofEpochSecond(1234),
Notification.Type.applicationPackage,
@@ -37,7 +41,8 @@ public class NotificationsSerializerTest {
Notification.Type.deployment,
Notification.Level.error,
NotificationSource.from(new RunId(ApplicationId.from(tenantName.value(), "app1", "instance1"), DeploymentContext.systemTest, 12)),
- List.of("Failed to deploy: Node allocation failure")));
+ "Failed to deploy", List.of("Node allocation failure"),
+ Optional.of(mail)));
Slime serialized = serializer.toSlime(notifications);
assertEquals("{\"notifications\":[" +
@@ -45,21 +50,26 @@ public class NotificationsSerializerTest {
"\"at\":1234000," +
"\"type\":\"applicationPackage\"," +
"\"level\":\"warning\"," +
+ "\"title\":\"\"," +
"\"messages\":[\"Something something deprecated...\"]," +
"\"application\":\"app1\"" +
"},{" +
"\"at\":2345000," +
"\"type\":\"deployment\"," +
"\"level\":\"error\"," +
- "\"messages\":[\"Failed to deploy: Node allocation failure\"]," +
+ "\"title\":\"Failed to deploy\"," +
+ "\"messages\":[\"Node allocation failure\"]," +
"\"application\":\"app1\"," +
"\"instance\":\"instance1\"," +
"\"jobId\":\"test.us-east-1\"," +
- "\"runNumber\":12" +
+ "\"runNumber\":12," +
+ "\"mail-template\":\"default-mail-content\"," +
+ "\"mail-subject\":\"My mail subject\"," +
+ "\"mail-params\":{\"list-param\":[\"elem1\",\"elem2\"],\"string-param\":\"string-value\"}" +
"}]}", new String(SlimeUtils.toJsonBytes(serialized)));
List<Notification> deserialized = serializer.fromSlime(tenantName, serialized);
assertEquals(notifications, deserialized);
}
-} \ No newline at end of file
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializerTest.java
index 7f988f08a89..4170dfccfc3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.google.common.collect.ImmutableSet;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializerTest.java
index e552dfe94e1..259e27515f3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionStatusSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionTargetSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionTargetSerializerTest.java
index 7bec217c889..7335a3e525b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionTargetSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/OsVersionTargetSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
index 8844b2deeac..2ff9e484e57 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/RoutingPolicySerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.ApplicationId;
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 fc1a694e0f7..7a9a1795444 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.google.common.collect.ImmutableMap;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/SupportAccessSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/SupportAccessSerializerTest.java
index 70be0734ed9..5caf6684a48 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/SupportAccessSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/SupportAccessSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.security.KeyAlgorithm;
@@ -167,4 +167,4 @@ public class SupportAccessSerializerTest {
notBefore, notAfter, SignatureAlgorithm.SHA256_WITH_ECDSA, BigInteger.valueOf(1))
.build();
}
-} \ No newline at end of file
+}
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 4369675ba3e..4912c9ae407 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
@@ -1,5 +1,5 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.persistence;// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.persistence;
import com.google.common.collect.ImmutableBiMap;
import com.yahoo.component.Version;
@@ -12,6 +12,7 @@ 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.billing.PlanId;
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;
@@ -23,6 +24,8 @@ import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.DeletedTenant;
import com.yahoo.vespa.hosted.controller.tenant.Email;
import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
+import com.yahoo.vespa.hosted.controller.tenant.PurchaseOrder;
+import com.yahoo.vespa.hosted.controller.tenant.TaxId;
import com.yahoo.vespa.hosted.controller.tenant.TenantAddress;
import com.yahoo.vespa.hosted.controller.tenant.TenantBilling;
import com.yahoo.vespa.hosted.controller.tenant.TenantContact;
@@ -114,12 +117,14 @@ public class TenantSerializerTest {
Optional.empty(),
Instant.EPOCH,
List.of(),
- Optional.empty());
+ Optional.empty(),
+ PlanId.from("none"));
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.name(), serialized.name());
assertEquals(tenant.creator(), serialized.creator());
assertEquals(tenant.developerKeys(), serialized.developerKeys());
assertEquals(tenant.createdAt(), serialized.createdAt());
+ assertEquals("none", serialized.planId().value());
}
@Test
@@ -139,7 +144,8 @@ public class TenantSerializerTest {
Optional.of(Instant.ofEpochMilli(1234567)),
Instant.EPOCH,
List.of(),
- Optional.empty());
+ Optional.empty(),
+ PlanId.from("none"));
CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
assertEquals(tenant.info(), serialized.info());
assertEquals(tenant.tenantSecretStores(), serialized.tenantSecretStores());
@@ -193,7 +199,8 @@ public class TenantSerializerTest {
Instant.EPOCH,
List.of(new CloudAccountInfo(CloudAccount.from("aws:123456789012"), Version.fromString("1.2.3")),
new CloudAccountInfo(CloudAccount.from("gcp:my-project"), Version.fromString("3.2.1"))),
- Optional.empty());
+ Optional.empty(),
+ PlanId.from("none"));
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");
@@ -207,7 +214,7 @@ public class TenantSerializerTest {
Slime slime = new Slime();
Cursor parentObject = slime.setObject();
serializer.toSlime(partialInfo, parentObject);
- assertEquals("{\"info\":{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"\",\"contactEmail\":\"\",\"contactEmailVerified\":true,\"address\":{\"addressLines\":\"\",\"postalCodeOrZip\":\"\",\"city\":\"Hønefoss\",\"stateRegionProvince\":\"\",\"country\":\"\"}}}", slime.toString());
+ assertEquals("{\"info\":{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"\",\"contactEmail\":\"\",\"contactEmailVerified\":false,\"address\":{\"addressLines\":\"\",\"postalCodeOrZip\":\"\",\"city\":\"Hønefoss\",\"stateRegionProvince\":\"\",\"country\":\"\"}}}", slime.toString());
}
@Test
@@ -224,12 +231,16 @@ public class TenantSerializerTest {
.withCode("3510")
.withRegion("Viken"))
.withBilling(TenantBilling.empty()
- .withContact(TenantContact.from("Thomas The Tank Engine", new Email("ceo@mycomp.any", true), "NA"))
+ .withContact(TenantContact.from("Thomas The Tank Engine", new Email("ceo@mycomp.any", false), "NA"))
.withAddress(TenantAddress.empty()
.withCity("Suddery")
.withCountry("Sodor")
.withAddress("Central Station")
- .withRegion("Irish Sea")));
+ .withRegion("Irish Sea"))
+ .withPurchaseOrder(new PurchaseOrder("PO42"))
+ .withTaxId(new TaxId("1234L"))
+ .withInvoiceEmail(new Email("billing@mycomp.any", false))
+ );
Slime slime = new Slime();
Cursor parentCursor = slime.setObject();
@@ -253,6 +264,30 @@ public class TenantSerializerTest {
}
@Test
+ void cloud_tenant_with_plan_id() {
+ 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(),
+ Optional.empty(),
+ Instant.EPOCH,
+ List.of(),
+ Optional.empty(),
+ PlanId.from("pay-as-you-go"));
+ CloudTenant serialized = (CloudTenant) serializer.tenantFrom(serializer.toSlime(tenant));
+ assertEquals(tenant.name(), serialized.name());
+ assertEquals(tenant.creator(), serialized.creator());
+ assertEquals(tenant.developerKeys(), serialized.developerKeys());
+ assertEquals(tenant.createdAt(), serialized.createdAt());
+ assertEquals(tenant.planId(), serialized.planId());
+ }
+
+ @Test
void deleted_tenant() {
DeletedTenant tenant = new DeletedTenant(
TenantName.from("tenant1"), Instant.ofEpochMilli(1234L), Instant.ofEpochMilli(2345L));
@@ -291,7 +326,8 @@ public class TenantSerializerTest {
Optional.empty(),
Instant.EPOCH,
List.of(),
- Optional.of(reference));
+ Optional.of(reference),
+ PlanId.from("none"));
var slime = serializer.toSlime(tenant);
var deserialized = serializer.tenantFrom(slime);
assertEquals(tenant, deserialized);
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/UnassignedCertificateSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/UnassignedCertificateSerializerTest.java
index 606b7e19b19..c1cdef6e4ee 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/UnassignedCertificateSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/UnassignedCertificateSerializerTest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificate;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
index 7c3ba5aeadf..450db8fd36e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/VersionStatusSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.component.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializerTest.java
index f524f4a2d4f..7bbdbe33e6c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ZoneRoutingPolicySerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.persistence;
import com.yahoo.config.provision.zone.ZoneId;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java
index 210e32db4c3..313485677ef 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ConfigServerRestExecutorImplTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.proxy;
import ai.vespa.http.HttpURL.Path;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
index d851eb56890..87207f5c080 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/proxy/ProxyRequestTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.proxy;
import ai.vespa.http.HttpURL.Path;
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 08164de6a8e..49a1abfe0f9 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.proxy;
import ai.vespa.http.HttpURL.Path;
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 52fd7393c4d..54a592ca070 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi;
import com.yahoo.application.container.handler.Request;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
index 4194131e7fb..f2826bad5b5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi;
import com.yahoo.application.container.JDisc;
@@ -143,6 +143,13 @@ public class ContainerTester {
assertResponse(() -> request, expectedResponse, expectedStatusCode);
}
+ public void assertJsonResponse(Supplier<Request> request, String expectedResponse, int expectedStatusCode) {
+ assertResponse(request,
+ (response) -> assertEquals(SlimeUtils.toJson(SlimeUtils.jsonToSlimeOrThrow(expectedResponse).get(), false),
+ SlimeUtils.toJson(SlimeUtils.jsonToSlimeOrThrow(response.getBodyAsString()).get(), false)),
+ expectedStatusCode);
+ }
+
public void assertResponse(Supplier<Request> request, String expectedResponse, int expectedStatusCode) {
assertResponse(request,
(response) -> assertEquals(expectedResponse, new String(response.getBody(), UTF_8)),
@@ -226,4 +233,4 @@ public class ContainerTester {
public interface ConsumerThrowingException<T> {
void accept(T t) throws Exception;
}
-} \ No newline at end of file
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java
index 5fe44038d73..06c2a03d98a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi;
import ai.vespa.hosted.api.MultiPartStreamer;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
index 7522f42f91b..3ada598f4f8 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi;
import com.yahoo.application.Networking;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ResponseHandlerToApplicationResponseWrapper.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ResponseHandlerToApplicationResponseWrapper.java
index d57920e8b3c..765da006deb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ResponseHandlerToApplicationResponseWrapper.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ResponseHandlerToApplicationResponseWrapper.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi;
import com.yahoo.jdisc.Response;
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 3b74fea2b9c..32f0247b3bc 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import ai.vespa.hosted.api.MultiPartStreamer;
@@ -6,14 +6,19 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.CloudAccount;
+import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.InstanceName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.restapi.RestApiException;
+import com.yahoo.slime.Cursor;
+import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.LockedTenant;
import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.FingerPrint;
+import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore;
@@ -35,14 +40,15 @@ import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
import static com.yahoo.application.container.handler.Request.Method.DELETE;
import static com.yahoo.application.container.handler.Request.Method.GET;
import static com.yahoo.application.container.handler.Request.Method.POST;
import static com.yahoo.application.container.handler.Request.Method.PUT;
-import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -72,7 +78,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
void tenant_info_profile() {
var request = request("/application/v4/tenant/scoober/info/profile", GET)
.roles(Set.of(Role.reader(tenantName)));
- tester.assertResponse(request, "{}", 200);
+ tester.assertResponse(request, "{\"contact\":{\"name\":\"\",\"email\":\"\",\"emailVerified\":false},\"tenant\":{\"company\":\"\",\"website\":\"\"}}", 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/\"}}")
@@ -92,34 +98,103 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
@Test
void tenant_info_billing() {
+ var expectedResponse = """
+ {
+ "contact": {
+ "name":"",
+ "email":"",
+ "emailVerified":false,
+ "phone":""
+ },
+ "taxId":"",
+ "purchaseOrder":"",
+ "invoiceEmail":""
+ }
+ """;
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 + "}";
-
+ tester.assertJsonResponse(request, expectedResponse, 200);
+
+ var fullBillingContact = """
+ {
+ "contact": {
+ "name":"name",
+ "email":"foo@example",
+ "phone":"phone"
+ },
+ "taxId":"1234L",
+ "purchaseOrder":"PO9001",
+ "invoiceEmail":"billing@mycomp.any",
+ "address": {
+ "addressLines":"addressLines",
+ "postalCodeOrZip":"postalCodeOrZip",
+ "city":"city",
+ "stateRegionProvince":"stateRegionProvince",
+ "country":"country"
+ }
+ }
+ """;
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);
+ expectedResponse = """
+ {
+ "contact": {
+ "name":"name",
+ "email":"foo@example",
+ "emailVerified": false,
+ "phone":"phone"
+ },
+ "taxId":"1234L",
+ "purchaseOrder":"PO9001",
+ "invoiceEmail":"billing@mycomp.any",
+ "address": {
+ "addressLines":"addressLines",
+ "postalCodeOrZip":"postalCodeOrZip",
+ "city":"city",
+ "stateRegionProvince":"stateRegionProvince",
+ "country":"country"
+ }
+ }
+ """;
+ tester.assertJsonResponse(request, expectedResponse, 200);
}
@Test
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\",\"emailVerified\":false},{\"audiences\":[\"notifications\"],\"email\":\"contact2@example.com\",\"emailVerified\":false},{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"contact3@example.com\",\"emailVerified\":false}]}";
+ tester.assertResponse(request, "{\"contacts\":[{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"developer@scoober\",\"emailVerified\":true}]}", 200);
+
+
+ var fullContacts = """
+ {
+ "contacts":[
+ {
+ "audiences":["tenant"]
+ ,"email":"contact1@example.com",
+ "emailVerified":false
+ },
+ {
+ "audiences":["notifications"],
+ "email":"contact2@example.com",
+ "emailVerified":false
+ },
+ {
+ "audiences":["tenant","notifications"],
+ "email":"contact3@example.com",
+ "emailVerified":false
+ }
+ ]
+ }
+ """;
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);
+ tester.assertJsonResponse(request, fullContacts, 200);
}
@Test
@@ -127,7 +202,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
var infoRequest =
request("/application/v4/tenant/scoober/info", GET)
.roles(Set.of(Role.reader(tenantName)));
- tester.assertResponse(infoRequest, "{}", 200);
+ tester.assertResponse(infoRequest, "{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"\",\"contactEmail\":\"\",\"contactEmailVerified\":false,\"contacts\":[{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"developer@scoober\",\"emailVerified\":true}]}", 200);
String partialInfo = "{\"contactName\":\"newName\", \"contactEmail\": \"foo@example.com\", \"billingContact\":{\"name\":\"billingName\"}}";
var postPartial =
@@ -144,13 +219,79 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
tester.assertResponse(postPartialContacts, "{\"message\":\"Tenant info updated\"}", 200);
// Read back the updated info
- tester.assertResponse(infoRequest, "{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"newName\",\"contactEmail\":\"foo@example.com\",\"contactEmailVerified\":false,\"billingContact\":{\"name\":\"billingName\",\"email\":\"\",\"phone\":\"\"},\"contacts\":[{\"audiences\":[\"tenant\"],\"email\":\"contact1@example.com\",\"emailVerified\":false}]}", 200);
-
- String fullAddress = "{\"addressLines\":\"addressLines\",\"postalCodeOrZip\":\"postalCodeOrZip\",\"city\":\"city\",\"stateRegionProvince\":\"stateRegionProvince\",\"country\":\"country\"}";
- String fullBillingContact = "{\"name\":\"name\",\"email\":\"foo@example\",\"phone\":\"phone\",\"address\":" + fullAddress + "}";
- String fullContacts = "[{\"audiences\":[\"tenant\"],\"email\":\"contact1@example.com\",\"emailVerified\":false},{\"audiences\":[\"notifications\"],\"email\":\"contact2@example.com\",\"emailVerified\":false},{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"contact3@example.com\",\"emailVerified\":false}]";
- String fullInfo = "{\"name\":\"name\",\"email\":\"foo@example\",\"website\":\"https://yahoo.com\",\"contactName\":\"contactName\",\"contactEmail\":\"contact@example.com\",\"contactEmailVerified\":false,\"address\":" + fullAddress + ",\"billingContact\":" + fullBillingContact + ",\"contacts\":" + fullContacts + "}";
-
+ var expectedResponse = """
+ {
+ "name":"",
+ "email":"",
+ "website":"",
+ "contactName":"newName",
+ "contactEmail":"foo@example.com",
+ "contactEmailVerified":false,
+ "billingContact": {
+ "name":"billingName",
+ "email":"","emailVerified":false,
+ "phone":"",
+ "taxId":"",
+ "purchaseOrder":"",
+ "invoiceEmail":""
+ },
+ "contacts": [
+ {"audiences":["tenant"],"email":"contact1@example.com","emailVerified":false}
+ ]
+ }
+ """;
+ tester.assertJsonResponse(infoRequest, expectedResponse, 200);
+
+ var fullInfo = """
+ {
+ "name":"name",
+ "email":"foo@example",
+ "website":"https://yahoo.com",
+ "contactName":"contactName",
+ "contactEmail":"contact@example.com",
+ "contactEmailVerified":false,
+ "address": {
+ "addressLines":"addressLines",
+ "postalCodeOrZip":"postalCodeOrZip",
+ "city":"city",
+ "stateRegionProvince":"stateRegionProvince",
+ "country":"country"
+ },
+ "billingContact": {
+ "name":"name",
+ "email":"foo@example",
+ "emailVerified":false,
+ "phone":"phone",
+ "taxId":"",
+ "purchaseOrder":"",
+ "invoiceEmail":"",
+ "address": {
+ "addressLines":"addressLines",
+ "postalCodeOrZip":"postalCodeOrZip",
+ "city":"city",
+ "stateRegionProvince":"stateRegionProvince",
+ "country":"country"
+ }
+ },
+ "contacts": [
+ {
+ "audiences":["tenant"],
+ "email":"contact1@example.com",
+ "emailVerified":false
+ },
+ {
+ "audiences":["notifications"],
+ "email":"contact2@example.com",
+ "emailVerified":false
+ },
+ {
+ "audiences":["tenant","notifications"]
+ ,"email":"contact3@example.com",
+ "emailVerified":false
+ }
+ ]
+ }
+ """;
// Now set all fields
var postFull =
request("/application/v4/tenant/scoober/info", PUT)
@@ -159,7 +300,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
tester.assertResponse(postFull, "{\"message\":\"Tenant info updated\"}", 200);
// Now compare the updated info with the full info we sent
- tester.assertResponse(infoRequest, fullInfo, 200);
+ tester.assertJsonResponse(infoRequest, fullInfo, 200);
var invalidBody = "{\"mail\":\"contact1@example.com\", \"mailType\":\"blurb\"}";
var resendMailRequest =
@@ -182,7 +323,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
var infoRequest =
request("/application/v4/tenant/scoober/info", GET)
.roles(Set.of(Role.reader(tenantName)));
- tester.assertResponse(infoRequest, "{}", 200);
+ tester.assertResponse(infoRequest, "{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"\",\"contactEmail\":\"\",\"contactEmailVerified\":false,\"contacts\":[{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"developer@scoober\",\"emailVerified\":true}]}", 200);
// name needs to be present and not blank
var partialInfoMissingName = "{\"contactName\": \" \"}";
@@ -470,17 +611,83 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest {
.roles(Role.developer(tenantName)),
"{\"tokens\":[]}", 200);
- String regexGenerateToken = "\\{\"id\":\"myTokenId\",\"token\":\"vespa_cloud_.*\",\"fingerprint\":\".*\"}";
+ AtomicReference<String> tokenValue = new AtomicReference<>();
+ AtomicReference<String> fingerprint = new AtomicReference<>();
tester.assertResponse(request("/application/v4/tenant/scoober/token/myTokenId", POST).roles(Role.developer(tenantName)),
- (response) -> assertTrue(new String(response.getBody(), UTF_8).matches(regexGenerateToken)),
- 200);
-
- String regexListTokens = "\\{\"tokens\":\\[\\{\"id\":\"myTokenId\",\"versions\":\\[\\{\"fingerprint\":\".*\",\"created\":\".*\",\"author\":\"user@test\",\"expiration\":\".*\"}]}]}";
- tester.assertResponse(request("/application/v4/tenant/scoober/token", GET)
- .roles(Role.developer(tenantName)),
- (response) -> assertTrue(new String(response.getBody(), UTF_8).matches(regexListTokens)),
+ (response) -> {
+ Cursor root = SlimeUtils.jsonToSlimeOrThrow(response.getBody()).get();
+ tokenValue.set(root.field("token").asString());
+ fingerprint.set(root.field("fingerprint").asString());
+ assertEquals("""
+ {
+ "id": "myTokenId",
+ "token": "%s",
+ "fingerprint": "%s",
+ "expiration": "2020-10-13T12:26:40Z"
+ }
+ """.formatted(tokenValue.get(), fingerprint.get()),
+ SlimeUtils.toJson(root, false));
+ },
200);
+ tester.assertJsonResponse(request("/application/v4/tenant/scoober/token", GET)
+ .roles(Role.developer(tenantName)),
+ """
+ {
+ "tokens": [
+ {
+ "id": "myTokenId",
+ "lastUpdatedMillis": 1600000000000,
+ "versions": [
+ {
+ "fingerprint": "%s",
+ "created": "2020-09-13T12:26:40Z",
+ "author": "user@test",
+ "expiration": "2020-10-13T12:26:40Z",
+ "state": "unused"
+ }
+ ]
+ }
+ ]
+ }
+ """.formatted(fingerprint.get()),
+ 200);
+
+ ControllerTester wrapped = new ControllerTester(tester);
+ wrapped.upgradeSystem(Version.fromString("7.1"));
+ new DeploymentTester(wrapped).newDeploymentContext(ApplicationId.from(tenantName, applicationName, InstanceName.defaultName()))
+ .submit()
+ .deploy();
+ wrapped.serviceRegistry().configServer().activeTokenFingerprints(null)
+ .put(HostName.of("host1"), Map.of(TokenId.of("myTokenId"), List.of(FingerPrint.of(fingerprint.get()), FingerPrint.of("ff:01"))));
+
+ tester.assertJsonResponse(request("/application/v4/tenant/scoober/token", GET)
+ .roles(Role.developer(tenantName)),
+ """
+ {
+ "tokens": [
+ {
+ "id": "myTokenId",
+ "lastUpdatedMillis": 1600000000000,
+ "versions": [
+ {
+ "fingerprint": "%s",
+ "created": "2020-09-13T12:26:40Z",
+ "author": "user@test",
+ "expiration": "2020-10-13T12:26:40Z",
+ "state": "active"
+ },
+ {
+ "fingerprint": "ff:01",
+ "state": "revoking"
+ }
+ ]
+ }
+ ]
+ }
+ """.formatted(fingerprint.get()),
+ 200);
+
// Rejects invalid tokenIds on create
tester.assertResponse(request("/application/v4/tenant/scoober/token/foo+bar", POST).roles(Role.developer(tenantName)),
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"tokenId must match '[A-Za-z][A-Za-z0-9_-]{0,59}', but got: 'foo bar'\"}",
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 6b377e2069b..66fb17410fd 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import ai.vespa.hosted.api.MultiPartStreamer;
@@ -61,7 +61,6 @@ import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryMock;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
-import com.yahoo.vespa.hosted.controller.notification.Notification;
import com.yahoo.vespa.hosted.controller.notification.NotificationSource;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
@@ -1979,15 +1978,11 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
private void addNotifications(TenantName tenantName) {
- tester.controller().notificationsDb().setNotification(
+ tester.controller().notificationsDb().setApplicationPackageNotification(
NotificationSource.from(TenantAndApplicationId.from(tenantName.value(), "app1")),
- Notification.Type.applicationPackage,
- Notification.Level.warning,
- "Something something deprecated...");
- tester.controller().notificationsDb().setNotification(
- NotificationSource.from(new RunId(ApplicationId.from(tenantName.value(), "app2", "instance1"), DeploymentContext.systemTest, 12)),
- Notification.Type.deployment,
- Notification.Level.error,
+ List.of("Something something deprecated..."));
+ tester.controller().notificationsDb().setDeploymentNotification(
+ new RunId(ApplicationId.from(tenantName.value(), "app2", "instance1"), DeploymentContext.systemTest, 12),
"Failed to deploy: Node allocation failure");
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/CliApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/CliApiHandlerTest.java
index 4166ce8b81e..9c169213b58 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/CliApiHandlerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/CliApiHandlerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java
index 905330c6daf..f9ba5850d2d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelperTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.component.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParserTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParserTest.java
index 37f4c19e27a..2a1caafe1ec 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParserTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/MultipartParserTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.application;
import com.yahoo.container.jdisc.HttpRequest;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1-app2.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1-app2.json
index 556440c40d5..6206e3b277a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1-app2.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1-app2.json
@@ -4,6 +4,7 @@
"at": 1600000000000,
"level": "error",
"type": "deployment",
+ "title": "[System test #12](https://console.tld/tenant/tenant1/application/app2/prod/instance/instance1/job/system-test/run/12) for application **app2.instance1** has failed",
"messages": [
"Failed to deploy: Node allocation failure"
],
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1.json
index 1a731dfe4a9..78deea65008 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/notifications-tenant1.json
@@ -4,6 +4,7 @@
"at": 1600000000000,
"level": "warning",
"type": "applicationPackage",
+ "title": "Application package for [app1](https://console.tld/tenant/tenant1/application/app1/prod/instance) has a warning",
"messages": [
"Something something deprecated..."
],
@@ -13,6 +14,7 @@
"at": 1600000000000,
"level": "error",
"type": "deployment",
+ "title": "[System test #12](https://console.tld/tenant/tenant1/application/app2/prod/instance/instance1/job/system-test/run/12) for application **app2.instance1** has failed",
"messages": [
"Failed to deploy: Node allocation failure"
],
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/overview-enclave.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/overview-enclave.json
index ef9c8a608ab..1d2cd8eaabb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/overview-enclave.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/overview-enclave.json
@@ -81,6 +81,9 @@
"commit": "commit1"
}
},
+ "enclave": {
+ "cloudAccount": "aws:123456789012"
+ },
"steps": [
{
"name": "deployTester",
@@ -177,6 +180,9 @@
"commit": "commit1"
}
},
+ "enclave": {
+ "cloudAccount": "aws:123456789012"
+ },
"steps": [
{
"name": "deployTester",
@@ -264,6 +270,9 @@
"commit": "commit1"
}
},
+ "enclave": {
+ "cloudAccount": "aws:123456789012"
+ },
"steps": [
{
"name": "deployReal",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
index 3a539987443..b7b8e0f8484 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/athenz/AthenzApiTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.athenz;
import com.yahoo.application.container.handler.Request;
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
deleted file mode 100644
index f247b0ed3b6..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java
+++ /dev/null
@@ -1,236 +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.restapi.billing;
-
-import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.TenantName;
-import com.yahoo.vespa.hosted.controller.api.integration.billing.Bill;
-import com.yahoo.vespa.hosted.controller.api.integration.billing.CollectionMethod;
-import com.yahoo.vespa.hosted.controller.api.integration.billing.MockBillingController;
-import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
-import com.yahoo.vespa.hosted.controller.api.role.Role;
-import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
-import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest;
-import com.yahoo.vespa.hosted.controller.security.Auth0Credentials;
-import com.yahoo.vespa.hosted.controller.security.CloudTenantSpec;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.io.File;
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.TreeMap;
-
-import static com.yahoo.application.container.handler.Request.Method.DELETE;
-import static com.yahoo.application.container.handler.Request.Method.GET;
-import static com.yahoo.application.container.handler.Request.Method.PATCH;
-import static com.yahoo.application.container.handler.Request.Method.POST;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * @author olaa
- */
-public class BillingApiHandlerTest extends ControllerContainerCloudTest {
-
- private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/";
- private static final TenantName tenant = TenantName.from("tenant1");
- private static final TenantName tenant2 = TenantName.from("tenant2");
- private static final Set<Role> tenantRole = Set.of(Role.administrator(tenant));
- private static final Set<Role> financeAdmin = Set.of(Role.hostedAccountant());
- private MockBillingController billingController;
-
- private ContainerTester tester;
-
- @BeforeEach
- public void setup() {
- tester = new ContainerTester(container, responseFiles);
- billingController = (MockBillingController) tester.serviceRegistry().billingController();
- }
-
- @Override
- protected SystemName system() {
- return SystemName.PublicCd;
- }
-
- @Override
- protected String variablePartXml() {
- return " <component id='com.yahoo.vespa.hosted.controller.security.CloudAccessControlRequests'/>\n" +
- " <component id='com.yahoo.vespa.hosted.controller.security.CloudAccessControl'/>\n" +
-
- " <handler id='com.yahoo.vespa.hosted.controller.restapi.billing.BillingApiHandler'>\n" +
- " <binding>http://*/billing/v1/*</binding>\n" +
- " </handler>\n" +
-
- " <http>\n" +
- " <server id='default' port='8080' />\n" +
- " <filtering>\n" +
- " <request-chain id='default'>\n" +
- " <filter id='com.yahoo.vespa.hosted.controller.restapi.filter.ControllerAuthorizationFilter'/>\n" +
- " <binding>http://*/*</binding>\n" +
- " </request-chain>\n" +
- " </filtering>\n" +
- " </http>\n";
- }
-
- @Test
- 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
- void response_list_bills() {
- var bill = createBill();
-
- billingController.addBill(tenant, bill, true);
- billingController.addBill(tenant, bill, false);
- billingController.setPlan(tenant, PlanId.from("some-plan"), true, false);
-
- var request = request("/billing/v1/tenant/tenant1/billing?until=2020-05-28").roles(tenantRole);
- tester.assertResponse(request, new File("tenant-billing-view.json"));
-
- }
-
- @Test
- void test_bill_creation() {
- var bills = billingController.getBillsForTenant(tenant);
- assertEquals(0, bills.size());
-
- String requestBody = "{\"tenant\":\"tenant1\", \"startTime\":\"2020-04-20\", \"endTime\":\"2020-05-20\"}";
- var request = request("/billing/v1/invoice", POST)
- .data(requestBody)
- .roles(tenantRole);
-
- tester.assertResponse(request, accessDenied, 403);
- request.roles(financeAdmin);
- tester.assertResponse(request, new File("invoice-creation-response.json"));
-
- bills = billingController.getBillsForTenant(tenant);
- assertEquals(1, bills.size());
- Bill bill = bills.get(0);
- assertEquals("2020-04-20T00:00Z", bill.getStartTime().toString());
- assertEquals("2020-05-21T00:00Z", bill.getEndTime().toString());
-
- assertEquals("2020-04-20", bill.getStartDate().toString());
- assertEquals("2020-05-20", bill.getEndDate().toString());
- }
-
- @Test
- void adding_and_listing_line_item() {
-
- var requestBody = "{" +
- "\"description\":\"some description\"," +
- "\"amount\":\"123.45\" " +
- "}";
-
- var request = request("/billing/v1/invoice/tenant/tenant1/line-item", POST)
- .data(requestBody)
- .roles(financeAdmin);
-
- tester.assertResponse(request, "{\"message\":\"Added line item for tenant tenant1\"}");
-
- var lineItems = billingController.getUnusedLineItems(tenant);
- assertEquals(1, lineItems.size());
- Bill.LineItem lineItem = lineItems.get(0);
- assertEquals("some description", lineItem.description());
- assertEquals(new BigDecimal("123.45"), lineItem.amount());
-
- request = request("/billing/v1/invoice/tenant/tenant1/line-item")
- .roles(financeAdmin);
-
- tester.assertResponse(request, new File("line-item-list.json"));
- }
-
- @Test
- void adding_new_status() {
- billingController.addBill(tenant, createBill(), true);
-
- var requestBody = "{\"status\":\"DONE\"}";
- var request = request("/billing/v1/invoice/id-1/status", POST)
- .data(requestBody)
- .roles(financeAdmin);
- tester.assertResponse(request, "{\"message\":\"Updated status of invoice id-1\"}");
-
- var bill = billingController.getBillsForTenant(tenant).get(0);
- assertEquals("DONE", bill.status());
- }
-
- @Test
- void list_all_unbilled_items() {
- tester.controller().tenants().create(new CloudTenantSpec(tenant, ""), new Auth0Credentials(() -> "foo", Set.of(Role.hostedOperator())));
- tester.controller().tenants().create(new CloudTenantSpec(tenant2, ""), new Auth0Credentials(() -> "foo", Set.of(Role.hostedOperator())));
-
- var bill = createBill();
- billingController.setPlan(tenant, PlanId.from("some-plan"), true, false);
- billingController.setPlan(tenant2, PlanId.from("some-plan"), true, false);
- billingController.addBill(tenant, bill, false);
- billingController.addLineItem(tenant, "support", new BigDecimal("42"), Optional.empty(), "Smith");
- billingController.addBill(tenant2, bill, false);
-
- var request = request("/billing/v1/billing?until=2020-05-28").roles(financeAdmin);
-
- tester.assertResponse(request, new File("billing-all-tenants.json"));
- }
-
- @Test
- void csv_export() {
- var bill = createBill();
- billingController.addBill(tenant, bill, true);
- var csvRequest = request("/billing/v1/invoice/export", GET).roles(financeAdmin);
- tester.assertResponse(csvRequest.get(), new File("billing-all-invoices"), 200, false);
- }
-
- @Test
- void patch_collection_method() {
- test_patch_collection_with_field_name("collectionMethod");
- test_patch_collection_with_field_name("collection");
- }
-
- private void test_patch_collection_with_field_name(String fieldName) {
- var planRequest = request("/billing/v1/tenant/tenant1/collection", PATCH)
- .data("{\"" + fieldName + "\": \"invoice\"}")
- .roles(financeAdmin);
- tester.assertResponse(planRequest, "Collection method updated to INVOICE");
- assertEquals(CollectionMethod.INVOICE, billingController.getCollectionMethod(tenant));
-
- // Test that not event tenant administrators can do this
- planRequest = request("/billing/v1/tenant/tenant1/collection", PATCH)
- .data("{\"collectionMethod\": \"epay\"}")
- .roles(tenantRole);
- tester.assertResponse(planRequest, accessDenied, 403);
- assertEquals(CollectionMethod.INVOICE, billingController.getCollectionMethod(tenant));
- }
-
- static Bill createBill() {
- var start = LocalDate.of(2020, 5, 23).atStartOfDay(ZoneOffset.UTC);
- var end = start.toLocalDate().plusDays(6).atStartOfDay(ZoneOffset.UTC);
- var statusHistory = new Bill.StatusHistory(new TreeMap<>(Map.of(start, "OPEN")));
- return new Bill(
- Bill.Id.of("id-1"),
- TenantName.defaultName(),
- statusHistory,
- List.of(createLineItem(start)),
- start,
- end
- );
- }
-
- static Bill.LineItem createLineItem(ZonedDateTime addedAt) {
- return new Bill.LineItem(
- "some-id",
- "description",
- new BigDecimal("123.00"),
- "paid",
- "Smith",
- addedAt
- );
- }
-
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2Test.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2Test.java
index 43271277ce9..46c1aa107f0 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2Test.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2Test.java
@@ -1,10 +1,13 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.billing;
import com.yahoo.application.container.handler.Request;
import com.yahoo.config.provision.TenantName;
import com.yahoo.test.ManualClock;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.Bill;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.BillStatus;
import com.yahoo.vespa.hosted.controller.api.integration.billing.MockBillingController;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.StatusHistory;
import com.yahoo.vespa.hosted.controller.api.role.Role;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest;
@@ -13,8 +16,15 @@ import com.yahoo.vespa.hosted.controller.security.CloudTenantSpec;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import java.math.BigDecimal;
import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
/**
* @author ogronnesby
@@ -29,11 +39,6 @@ public class BillingApiHandlerV2Test extends ControllerContainerCloudTest {
private static final Set<Role> tenantAdmin = Set.of(Role.administrator(tenant));
private static final Set<Role> financeAdmin = Set.of(Role.hostedAccountant());
- private static final String ACCESS_DENIED = "{\n" +
- " \"code\" : 403,\n" +
- " \"message\" : \"Access denied\"\n" +
- "}";
-
private MockBillingController billingController;
private ContainerTester tester;
@@ -44,7 +49,7 @@ public class BillingApiHandlerV2Test extends ControllerContainerCloudTest {
var clock = (ManualClock) tester.controller().serviceRegistry().clock();
clock.setInstant(Instant.parse("2021-04-13T00:00:00Z"));
billingController = (MockBillingController) tester.serviceRegistry().billingController();
- billingController.addBill(tenant, BillingApiHandlerTest.createBill(), true);
+ billingController.addBill(tenant, createBill(), true);
}
@Override
@@ -103,7 +108,7 @@ public class BillingApiHandlerV2Test extends ControllerContainerCloudTest {
var singleRequest = request("/billing/v2/tenant/" + tenant + "/bill/id-1").roles(tenantReader);
tester.assertResponse(singleRequest, """
- {"id":"id-1","from":"2020-05-23","to":"2020-05-28","total":"123.00","status":"OPEN","statusHistory":[{"at":"2020-05-23T00:00:00Z","status":"OPEN"}],"items":[{"id":"some-id","description":"description","amount":"123.00","plan":{"id":"paid","name":"Paid Plan - for testing purposes"},"majorVersion":0,"cpu":{},"memory":{},"disk":{}}]}""");
+ {"id":"id-1","from":"2020-05-23","to":"2020-05-28","total":"123.00","status":"OPEN","statusHistory":[{"at":"2020-05-23T00:00:00Z","status":"OPEN"}],"items":[{"id":"some-id","description":"description","amount":"123.00","plan":{"id":"paid","name":"Paid Plan - for testing purposes"},"majorVersion":0,"cpu":{},"memory":{},"disk":{},"gpu":{}}]}""");
}
@Test
@@ -116,18 +121,27 @@ public class BillingApiHandlerV2Test extends ControllerContainerCloudTest {
var accountantRequest = request("/billing/v2/accountant").roles(Role.hostedAccountant());
tester.assertResponse(accountantRequest, """
- {"tenants":[{"tenant":"tenant1","plan":{"id":"trial","name":"Free Trial - for testing purposes"},"quota":{"budget":-1.0},"collection":"AUTO","lastBill":null,"unbilled":"0.00"}]}""");
+ {"tenants":[{"tenant":"tenant1","plan":{"id":"trial","name":"Free Trial - for testing purposes"},"quota":{"budget":-1.0},"collection":"AUTO","lastBill":"1970-01-01","unbilled":"0.00"}]}""");
+ }
+
+ @Test
+ void require_accountant_preview() {
+ var accountantRequest = request("/billing/v2/accountant/preview").roles(Role.hostedAccountant());
+ billingController.uncommittedBills.put(tenant, createBill());
+
+ tester.assertResponse(accountantRequest, """
+ {"tenants":[{"tenant":"tenant1","plan":{"id":"trial","name":"Free Trial - for testing purposes"},"quota":{"budget":-1.0},"collection":"AUTO","lastBill":"2020-05-23","unbilled":"123.00"}]}""");
}
@Test
void require_accountant_tenant_preview() {
- var accountantRequest = request("/billing/v2/accountant/preview/tenant/tenant1").roles(Role.hostedAccountant());
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/preview").roles(Role.hostedAccountant());
tester.assertResponse(accountantRequest, "{\"id\":\"empty\",\"from\":\"2021-04-13\",\"to\":\"2021-04-12\",\"total\":\"0.00\",\"status\":\"OPEN\",\"statusHistory\":[{\"at\":\"2021-04-13T00:00:00Z\",\"status\":\"OPEN\"}],\"items\":[]}");
}
@Test
void require_accountant_tenant_bill() {
- var accountantRequest = request("/billing/v2/accountant/preview/tenant/tenant1", Request.Method.POST)
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/preview", Request.Method.POST)
.roles(Role.hostedAccountant())
.data("{\"from\": \"2020-05-01\",\"to\": \"2020-06-01\"}");
tester.assertResponse(accountantRequest, "{\"message\":\"Created bill id-123\"}");
@@ -139,4 +153,126 @@ public class BillingApiHandlerV2Test extends ControllerContainerCloudTest {
.roles(Role.hostedAccountant());
tester.assertResponse(accountantRequest, "{\"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
+ void require_additional_items_empty() {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/items")
+ .roles(Role.hostedAccountant());
+ tester.assertResponse(accountantRequest, """
+ {"items":[]}""");
+ }
+
+ @Test
+ void require_additional_items_with_content() {
+ {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/items", Request.Method.POST)
+ .roles(Role.hostedAccountant())
+ .data("""
+ {
+ "description": "Additional support costs",
+ "amount": "123.45"
+ }""");
+ tester.assertResponse(accountantRequest, """
+ {"message":"Added line item for tenant tenant1"}""");
+ }
+
+ {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/items")
+ .roles(Role.hostedAccountant());
+ tester.assertResponse(accountantRequest, """
+ {"items":[{"id":"line-item-id","description":"Additional support costs","amount":"123.45","plan":{"id":"paid","name":"Paid Plan - for testing purposes"},"majorVersion":0,"cpu":{},"memory":{},"disk":{},"gpu":{}}]}""");
+ }
+
+ {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/item/line-item-id", Request.Method.DELETE)
+ .roles(Role.hostedAccountant());
+ tester.assertResponse(accountantRequest, """
+ {"message":"Successfully deleted line item line-item-id"}""");
+ }
+ }
+
+ @Test
+ void require_current_plan() {
+ {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/plan")
+ .roles(Role.hostedAccountant());
+ tester.assertResponse(accountantRequest, """
+ {"id":"trial","name":"Free Trial - for testing purposes"}""");
+ }
+
+ {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/plan", Request.Method.POST)
+ .roles(Role.hostedAccountant())
+ .data("""
+ {"id": "paid"}""");
+ tester.assertResponse(accountantRequest, """
+ {"message":"Plan: paid"}""");
+ }
+
+ {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/plan")
+ .roles(Role.hostedAccountant());
+ tester.assertResponse(accountantRequest, """
+ {"id":"paid","name":"Paid Plan - for testing purposes"}""");
+ }
+ }
+
+ @Test
+ void require_current_collection() {
+ {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/collection")
+ .roles(Role.hostedAccountant());
+ tester.assertResponse(accountantRequest, """
+ {"collection":"AUTO"}""");
+ }
+
+ {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/collection", Request.Method.POST)
+ .roles(Role.hostedAccountant())
+ .data("""
+ {"collection": "INVOICE"}""");
+ tester.assertResponse(accountantRequest, """
+ {"message":"Collection: INVOICE"}""");
+ }
+
+ {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1/collection")
+ .roles(Role.hostedAccountant());
+ tester.assertResponse(accountantRequest, """
+ {"collection":"INVOICE"}""");
+ }
+ }
+
+ @Test
+ void require_accountant_tenant() {
+ var accountantRequest = request("/billing/v2/accountant/tenant/tenant1")
+ .roles(Role.hostedAccountant());
+ tester.assertResponse(accountantRequest, """
+ {"tenant":"tenant1","plan":{"id":"trial","name":"Free Trial - for testing purposes","billed":false,"supported":false},"billing":{},"collection":"AUTO"}""");
+ }
+
+ private static Bill createBill() {
+ var start = LocalDate.of(2020, 5, 23).atStartOfDay(ZoneOffset.UTC);
+ var end = start.toLocalDate().plusDays(6).atStartOfDay(ZoneOffset.UTC);
+ var statusHistory = new StatusHistory(new TreeMap<>(Map.of(start, BillStatus.OPEN)));
+ return new Bill(
+ Bill.Id.of("id-1"),
+ TenantName.defaultName(),
+ statusHistory,
+ List.of(createLineItem(start)),
+ start,
+ end
+ );
+ }
+
+ static Bill.LineItem createLineItem(ZonedDateTime addedAt) {
+ return new Bill.LineItem(
+ "some-id",
+ "description",
+ new BigDecimal("123.00"),
+ "paid",
+ "Smith",
+ addedAt
+ );
+ }
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-invoices b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-invoices
deleted file mode 100644
index 957ed858951..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-invoices
+++ /dev/null
@@ -1,2 +0,0 @@
-ID,Tenant,From,To,CpuHours,MemoryHours,DiskHours,Cpu,Memory,Disk,Additional
-id-1,default,2020-05-23,2020-05-28,0.00,0.00,0.00,0.00,0.00,0.00,123.00
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants.json
deleted file mode 100644
index d761439667a..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/billing-all-tenants.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
- "until": "2020-05-28",
- "tenants": [
- {
- "tenant": "tenant1",
- "plan": "some-plan",
- "planName": "Plan with id: some-plan",
- "collection": "AUTO",
- "current": {
- "amount": "123.00",
- "status": "accrued",
- "from": "2020-05-23",
- "items": [
- {
- "id": "some-id",
- "description": "description",
- "amount": "123.00",
- "plan": "paid",
- "planName": "Plan with id: paid",
- "majorVersion": 0
- }
- ]
- },
- "additional": {
- "items": [
- {
- "id": "line-item-id",
- "description": "support",
- "amount": "42.00",
- "plan": "some-plan",
- "planName": "Plan with id: some-plan",
- "majorVersion": 0
- }
- ]
- }
- },
- {
- "tenant": "tenant2",
- "plan": "some-plan",
- "planName": "Plan with id: some-plan",
- "collection": "AUTO",
- "current": {
- "amount": "123.00",
- "status": "accrued",
- "from": "2020-05-23",
- "items": [
- {
- "id": "some-id",
- "description": "description",
- "amount": "123.00",
- "plan": "paid",
- "planName": "Plan with id: paid",
- "majorVersion": 0
- }
- ]
- },
- "additional": {
- "items": [ ]
- }
- }
- ]
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response.json
deleted file mode 100644
index 49fde010c58..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/invoice-creation-response.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "message": "Created invoice with ID id-123",
- "id": "id-123"
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list.json
deleted file mode 100644
index fbfc5ce09ee..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/line-item-list.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "lineItems": [
- {
- "id": "line-item-id",
- "description": "some description",
- "amount": "123.45",
- "plan": "some-plan",
- "planName": "Plan with id: some-plan",
- "majorVersion": 0
- }
- ]
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view.json
deleted file mode 100644
index 4e255205e19..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/responses/tenant-billing-view.json
+++ /dev/null
@@ -1,49 +0,0 @@
-{
- "until": "2020-05-28",
- "plan": "some-plan",
- "planName": "Plan with id: some-plan",
- "current": {
- "amount": "123.00",
- "status": "accrued",
- "from": "2020-05-23",
- "items": [
- {
- "id": "some-id",
- "description": "description",
- "amount": "123.00",
- "plan": "paid",
- "planName": "Plan with id: paid",
- "majorVersion": 0
- }
- ]
- },
- "additional": {
- "items": [ ]
- },
- "bills": [
- {
- "id": "id-1",
- "from": "2020-05-23",
- "to": "2020-05-28",
- "amount": "123.00",
- "status": "OPEN",
- "statusHistory": [
- {
- "at": "2020-05-23",
- "status": "OPEN"
- }
- ],
- "items": [
- {
- "id": "some-id",
- "description": "description",
- "amount": "123.00",
- "plan": "paid",
- "planName": "Plan with id: paid",
- "majorVersion": 0
- }
- ]
- }
- ],
- "collection": "AUTO"
-}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandlerTest.java
index 8a915d72b25..80368f4b134 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandlerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/changemanagement/ChangeManagementApiHandlerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.changemanagement;
import com.yahoo.application.container.handler.Request;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
index 82783485158..87be4519177 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/configserver/ConfigServerApiHandlerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.configserver;
import com.yahoo.application.container.handler.Request;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
index 966c7f02cee..eb023aa9fe9 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/ControllerApiTest.java
@@ -1,9 +1,10 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.application.container.handler.Request;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.container.jdisc.HttpRequest;
@@ -162,7 +163,7 @@ public class ControllerApiTest extends ControllerContainerTest {
new NodeResources(12, 48, 1200, 0, NodeResources.DiskSpeed.any, NodeResources.StorageType.any, NodeResources.Architecture.arm64),
new NodeResources(24, 96, 2400, 0, NodeResources.DiskSpeed.any, NodeResources.StorageType.any, NodeResources.Architecture.x86_64));
- var snapshots = resources.stream().map(x -> new ResourceSnapshot(applicationId, x, timestamp, zoneId, 0)).toList();
+ var snapshots = resources.stream().map(x -> new ResourceSnapshot(applicationId, x, timestamp, zoneId, 0, CloudAccount.empty)).toList();
tester.controller().serviceRegistry().resourceDatabase().writeResourceSnapshots(snapshots);
tester.assertResponse(
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/WellKnownApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/WellKnownApiHandlerTest.java
index d46fc3f18cc..ffdf6796d9d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/WellKnownApiHandlerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/controller/WellKnownApiHandlerTest.java
@@ -1,3 +1,4 @@
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.controller;
import com.yahoo.application.container.handler.Request;
@@ -47,4 +48,4 @@ class WellKnownApiHandlerTest extends ControllerContainerTest {
""", SECURITY_TXT);
}
-} \ No newline at end of file
+}
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 8b76613676c..e7f48bf4ffa 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
@@ -49,6 +49,9 @@
"name": "CostReportMaintainer"
},
{
+ "name": "DataPlaneTokenRedeployer"
+ },
+ {
"name": "DefaultOsUpgrader"
},
{
@@ -133,5 +136,7 @@
"name": "VersionStatusUpdater"
}
],
- "inactive": ["DeploymentExpirer"]
+ "inactive": [
+ "DeploymentExpirer"
+ ]
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenServiceTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenServiceTest.java
index acfba03a700..87facaf1218 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenServiceTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/dataplanetoken/DataplaneTokenServiceTest.java
@@ -1,33 +1,172 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.dataplanetoken;
+import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.TenantName;
+import com.yahoo.config.provision.zone.AuthMethod;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.DataplaneToken;
import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.DataplaneTokenVersions;
import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.FingerPrint;
import com.yahoo.vespa.hosted.controller.api.integration.dataplanetoken.TokenId;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
+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.restapi.dataplanetoken.DataplaneTokenService.State;
import org.junit.jupiter.api.Test;
import java.security.Principal;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
+import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class DataplaneTokenServiceTest {
+
private final ControllerTester tester = new ControllerTester(SystemName.Public);
private final DataplaneTokenService dataplaneTokenService = new DataplaneTokenService(tester.controller());
private final TenantName tenantName = TenantName.from("tenant");
- Principal principal = new SimplePrincipal("user");
+ private final Principal principal = new SimplePrincipal("user");
private final TokenId tokenId = TokenId.of("myTokenId");
+ private final Map<HostName, Map<TokenId, List<FingerPrint>>> activeTokens = tester.configServer().activeTokenFingerprints(null);
+
+ @Test
+ void triggers_token_redeployments() {
+ DeploymentTester deploymentTester = new DeploymentTester(tester);
+ DeploymentContext app = deploymentTester.newDeploymentContext(tenantName.value(), "app", "default");
+ ApplicationPackage appPackage = new ApplicationPackageBuilder().region("aws-us-east-1c")
+ .container("default", AuthMethod.token, AuthMethod.token)
+ .build();
+ app.submit(appPackage).deploy();
+
+ // First token version is added after deployment, so re-trigger.
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ assertEquals(List.of(), deploymentTester.jobs().active());
+ FingerPrint print1 = dataplaneTokenService.generateToken(tenantName, TokenId.of("token-1"), null, principal).fingerPrint();
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ app.runJob(JobType.prod("aws-us-east-1c"));
+ assertEquals(List.of(), deploymentTester.jobs().active());
+
+ // New token version is added, so re-trigger.
+ tester.clock().advance(Duration.ofSeconds(1));
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ assertEquals(List.of(), deploymentTester.jobs().active());
+ FingerPrint print2 = dataplaneTokenService.generateToken(tenantName, TokenId.of("token-1"), null, principal).fingerPrint();
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ app.runJob(JobType.prod("aws-us-east-1c"));
+ assertEquals(List.of(), deploymentTester.jobs().active());
+
+ // Another token version is added, so re-trigger.
+ tester.clock().advance(Duration.ofSeconds(1));
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ assertEquals(List.of(), deploymentTester.jobs().active());
+ FingerPrint print3 = dataplaneTokenService.generateToken(tenantName, TokenId.of("token-1"), tester.clock().instant().plusSeconds(10), principal).fingerPrint();
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ app.runJob(JobType.prod("aws-us-east-1c"));
+ assertEquals(List.of(), deploymentTester.jobs().active());
+
+ // An expired token version is deleted, so do _not_ re-trigger.
+ tester.clock().advance(Duration.ofSeconds(11));
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ assertEquals(List.of(), deploymentTester.jobs().active());
+ dataplaneTokenService.deleteToken(tenantName, TokenId.of("token-1"), print3);
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ assertEquals(List.of(), deploymentTester.jobs().active());
+
+ // Some unused token version is added, so do _not_ re-trigger.
+ tester.clock().advance(Duration.ofSeconds(1));
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ assertEquals(List.of(), deploymentTester.jobs().active());
+ dataplaneTokenService.generateToken(tenantName, TokenId.of("token-3"), null, principal);
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ assertEquals(List.of(), deploymentTester.jobs().active());
+
+ // One token version is deleted, so re-trigger.
+ tester.clock().advance(Duration.ofSeconds(1));
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ assertEquals(List.of(), deploymentTester.jobs().active());
+ dataplaneTokenService.deleteToken(tenantName, TokenId.of("token-1"), print2);
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ app.runJob(JobType.prod("aws-us-east-1c"));
+ assertEquals(List.of(), deploymentTester.jobs().active());
+
+ // Last token version is deleted, the token is no longer known, so re-trigger.
+ tester.clock().advance(Duration.ofSeconds(1));
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ assertEquals(List.of(), deploymentTester.jobs().active());
+ dataplaneTokenService.deleteToken(tenantName, TokenId.of("token-1"), print1);
+ dataplaneTokenService.triggerTokenChangeDeployments();
+ app.runJob(JobType.prod("aws-us-east-1c"));
+ assertEquals(List.of(), deploymentTester.jobs().active());
+ }
+
+ @Test
+ void computes_aggregate_state() {
+ DeploymentTester deploymentTester = new DeploymentTester(tester);
+ DeploymentContext app = deploymentTester.newDeploymentContext(tenantName.value(), "app", "default");
+ app.submit().deploy();
+
+ TokenId[] id = new TokenId[5];
+ FingerPrint[][] print = new FingerPrint[5][3];
+ for (int i = 0; i < id.length; i++) {
+ id[i] = TokenId.of("id" + i);
+ for (int j = 0; j < 3; j++) {
+ print[i][j] = dataplaneTokenService.generateToken(tenantName, id[i], null, principal).fingerPrint();
+ }
+ }
+ for (int j = 0; j < 2; j++) {
+ dataplaneTokenService.deleteToken(tenantName, id[2], print[2][j]);
+ dataplaneTokenService.deleteToken(tenantName, id[4], print[4][j]);
+ }
+ for (int j = 0; j < 3; j++) {
+ dataplaneTokenService.deleteToken(tenantName, id[3], print[3][j]);
+ }
+ // "host1" has all versions of all current tokens, except the first versions of tokens 1 and 2.
+ activeTokens.put(HostName.of("host1"),
+ Map.of(id[0], List.of(print[0]),
+ id[1], List.of(print[1][1], print[1][2]),
+ id[2], List.of(print[2][1], print[2][2])));
+ // "host2" has all versions of all current tokens, except the last version of token 1.
+ activeTokens.put(HostName.of("host2"),
+ Map.of(id[0], List.of(print[0]),
+ id[1], List.of(print[1][0], print[1][1]),
+ id[2], List.of(print[2])));
+ // "host3" has no current tokens at all, but has the last version of token 3
+ activeTokens.put(HostName.of("host3"),
+ Map.of(id[3], List.of(print[3][2])));
+
+ // All fingerprints of token 0 are active on all hosts where token 0 is found, so they are all active.
+ // The first and last fingerprints of token 1 are missing from one host each, so these are activating.
+ // The first fingerprints of token 2 are no longer current, but the second is found on a host; both deactivating.
+ // The whole of token 3 is forgotten, but the last fingerprint is found on a host; deactivating.
+ // Only the last fingerprint of token 4 remains, but this token is not used anywhere; unused.
+ assertEquals(new TreeMap<>(Map.of(id[0], new TreeMap<>(Map.of(print[0][0], State.ACTIVE,
+ print[0][1], State.ACTIVE,
+ print[0][2], State.ACTIVE)),
+ id[1], new TreeMap<>(Map.of(print[1][0], State.DEPLOYING,
+ print[1][1], State.ACTIVE,
+ print[1][2], State.DEPLOYING)),
+ id[2], new TreeMap<>(Map.of(print[2][0], State.REVOKING,
+ print[2][1], State.REVOKING,
+ print[2][2], State.ACTIVE)),
+ id[3], new TreeMap<>(Map.of(print[3][2], State.REVOKING)),
+ id[4], new TreeMap<>(Map.of(print[4][2], State.UNUSED)))),
+ new TreeMap<>(dataplaneTokenService.listTokensWithState(tenantName).entrySet().stream()
+ .collect(toMap(tokens -> tokens.getKey().tokenId(),
+ tokens -> new TreeMap<>(tokens.getValue())))));
+ }
@Test
void generates_and_persists_token() {
@@ -82,4 +221,5 @@ public class DataplaneTokenServiceTest {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> dataplaneTokenService.deleteToken(tenantName, tokenId, dataplaneToken.fingerPrint()));
assertEquals("Token does not exist: " + tokenId, exception.getMessage());
}
+
}
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 5dd57b09af4..cfacbfe77f4 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
@@ -1,6 +1,7 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.deployment;
+import com.yahoo.application.container.handler.Request.Method;
import com.yahoo.component.Version;
import com.yahoo.vespa.hosted.controller.ControllerTester;
import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
@@ -9,12 +10,15 @@ import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Duration;
+import java.util.List;
+import java.util.Map;
/**
* @author jonmv
@@ -79,6 +83,18 @@ public class BadgeApiTest extends ControllerContainerTest {
tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default/production-us-west-1?historyLength=0"),
Files.readString(Paths.get(responseFiles + "single-done.svg")), 200);
+
+ tester.assertResponse(() -> authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default", "", Method.HEAD),
+ __ -> { },
+ 200);
+
+ tester.assertResponse(() -> authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default", "", Method.OPTIONS),
+ response -> Assertions.assertEquals(List.of(Map.entry("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS"),
+ Map.entry("Access-Control-Allow-Origin", "*"),
+ Map.entry("Allow", "GET, HEAD, OPTIONS"),
+ Map.entry("Content-Type", "image/svg+xml; charset=UTF-8")),
+ response.getHeaders().entries()),
+ 200);
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
index eea5c9bdccf..5d0608b8bd9 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.deployment;
import com.yahoo.component.Version;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java
index 877ca4a99d2..fe0a3551860 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.filter;
import com.yahoo.config.provision.ApplicationName;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java
index 64dce08e735..0b993fc70a3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilterTest.java
index b85c93a5d90..8abf32c45f9 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/LastLoginUpdateFilterTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.filter;
import com.yahoo.application.container.handler.Request;
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 001e02e1b16..9477e71af33 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.filter;
import ai.vespa.hosted.api.Method;
@@ -11,6 +11,7 @@ import com.yahoo.security.KeyUtils;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.ControllerTester;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId;
import com.yahoo.vespa.hosted.controller.api.role.Role;
import com.yahoo.vespa.hosted.controller.api.role.SecurityContext;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
@@ -70,7 +71,7 @@ public class SignatureFilterTest {
filter = new SignatureFilter(tester.controller());
signer = new RequestSigner(privateKey, id.serializedForm(), tester.clock());
- tester.curator().writeTenant(CloudTenant.create(appId.tenant(), Instant.EPOCH, null));
+ tester.curator().writeTenant(CloudTenant.create(appId.tenant(), Instant.EPOCH, new SimplePrincipal("owner@my-tenant.my-app")));
tester.curator().writeApplication(new Application(appId, tester.clock().instant()));
}
@@ -120,7 +121,8 @@ public class SignatureFilterTest {
Optional.empty(),
Instant.EPOCH,
List.of(),
- Optional.empty()));
+ Optional.empty(),
+ PlanId.from("none")));
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/flags/AuditedFlagsApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java
index ee89f506f17..643ac82b223 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/flags/AuditedFlagsApiTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.flags;
import com.yahoo.application.container.handler.Request;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiTest.java
index fabae508057..87b4bf7e84c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/HorizonApiTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.horizon;
import com.yahoo.config.provision.SystemName;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/TsdbQueryRewriterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/TsdbQueryRewriterTest.java
index 7f826566ebc..87d34874631 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/TsdbQueryRewriterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/horizon/TsdbQueryRewriterTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.horizon;
import com.yahoo.config.provision.SystemName;
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 acb07102008..7a1b9979cfd 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.os;
import com.yahoo.application.container.handler.Request;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/AllowingFilter.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/AllowingFilter.java
index ec3328fbf61..9798f95e703 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/AllowingFilter.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/AllowingFilter.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.playground;
import com.yahoo.jdisc.http.filter.DiscFilterRequest;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/DeploymentPlayground.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/DeploymentPlayground.java
index 38019ec725b..38ae1502b6e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/DeploymentPlayground.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/DeploymentPlayground.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.playground;
import ai.vespa.validation.StringWrapper;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment.xml b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment.xml
index 61749cd89f0..1097a197d96 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment.xml
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment.xml
@@ -1,3 +1,4 @@
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<deployment version='1.0'>
<notifications>
<email role="author" />
@@ -129,4 +130,4 @@
</steps>
</parallel>
-</deployment> \ No newline at end of file
+</deployment>
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_alt_full.xml b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_alt_full.xml
index 61749cd89f0..1097a197d96 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_alt_full.xml
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_alt_full.xml
@@ -1,3 +1,4 @@
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<deployment version='1.0'>
<notifications>
<email role="author" />
@@ -129,4 +130,4 @@
</steps>
</parallel>
-</deployment> \ No newline at end of file
+</deployment>
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_alternative.xml b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_alternative.xml
index 8bd24e977b1..b074792f716 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_alternative.xml
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_alternative.xml
@@ -1,3 +1,4 @@
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<deployment version='1.0'>
<notifications>
<email role="author" />
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_base.xml b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_base.xml
index e3d7933d5e7..715ff4fdb3f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_base.xml
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_base.xml
@@ -1,3 +1,4 @@
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<deployment version='1.0'>
<notifications>
<email role="author" />
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_full.xml b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_full.xml
index 5a7b44f966d..0b85852abdb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_full.xml
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_full.xml
@@ -1,3 +1,4 @@
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<deployment version='1.0'>
<notifications>
<email role="author" />
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simple.xml b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simple.xml
index cdcaadbd957..16986df174c 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simple.xml
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simple.xml
@@ -1,3 +1,4 @@
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<deployment version='1.0'>
<parallel>
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simpler.xml b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simpler.xml
index f0b0ae79d81..8be40e84495 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simpler.xml
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simpler.xml
@@ -1,3 +1,4 @@
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<deployment version='1.0'>
<instance id="alpha"> <!-- Runs system and stress tests -->
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simplest.xml b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simplest.xml
index b023587b6a9..37efaf82b5a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simplest.xml
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/playground/deployment_simplest.xml
@@ -1,3 +1,4 @@
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<deployment version='1.0'>
<instance id="beta"> <!-- Runs system and production tests -->
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java
index b90c886f10d..309501f5679 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/routing/RoutingApiTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.routing;
import com.yahoo.application.container.handler.Request;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResultTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResultTest.java
index 8d643534e0c..44fc86e314e 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResultTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployResultTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.systemflags;
import com.yahoo.config.provision.SystemName;
@@ -73,4 +73,4 @@ public class SystemFlagsDeployResultTest {
OperationError error = errors.get(0);
assertEquals(error.targets(), Set.of(controllerTarget, prodUsWest1Target));
}
-} \ No newline at end of file
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployerTest.java
index cb330d28d22..b53a0847ddb 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/systemflags/SystemFlagsDeployerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.systemflags;
import com.yahoo.config.provision.SystemName;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiOnPremTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiOnPremTest.java
index f7e270b3c68..81db6b02a50 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiOnPremTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiOnPremTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.user;
import com.yahoo.application.container.handler.Request;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
index b3c992cdac7..63ae56e4207 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.user;
import com.yahoo.config.provision.ApplicationId;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializerTest.java
index eb3f9daef53..d0a374dbb3a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserFlagsSerializerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.user;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -130,4 +130,4 @@ public class UserFlagsSerializerTest {
return Objects.hash(integer, string);
}
}
-} \ No newline at end of file
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-info-after-created.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-info-after-created.json
index 6702eff8dde..1926dcc9f82 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-info-after-created.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/responses/tenant-info-after-created.json
@@ -5,5 +5,14 @@
"contactName": "administrator",
"contactEmail": "administrator@tenant",
"contactEmailVerified": true,
- "contacts": [ ]
+ "contacts": [
+ {
+ "audiences": [
+ "tenant",
+ "notifications"
+ ],
+ "email": "administrator@tenant",
+ "emailVerified": true
+ }
+ ]
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java
index 5d5e310503d..183e9968878 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.zone.v1;
import com.yahoo.config.provision.Environment;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java
index 81484f05d1e..b680a4341f3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v2/ZoneApiTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.zone.v2;
import com.yahoo.application.container.handler.Request.Method;
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 b2b34441219..a671f567895 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
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing;
import ai.vespa.http.DomainName;
@@ -46,7 +46,6 @@ import com.yahoo.vespa.hosted.controller.dns.NameServiceQueue;
import com.yahoo.vespa.hosted.controller.dns.RemoveRecords;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
@@ -113,6 +112,11 @@ public class RoutingPoliciesTest {
private static final ZoneId zone5 = zoneApi5.getId();
private static final ZoneId zone6 = zoneApi6.getId();
+ private static final ZoneId testZonePublic = ZoneId.from("test", "aws-us-east-2c");
+ private static final ZoneId stagingZonePublic = ZoneId.from("staging", "aws-us-east-3c");
+ private static final ZoneId testZoneMain = ZoneId.from("test", "us-east-1");
+ private static final ZoneId stagingZoneMain = ZoneId.from("staging", "us-east-3");
+
private static final ApplicationPackage applicationPackage = applicationPackageBuilder().region(zone1.region())
.region(zone2.region())
.build();
@@ -141,6 +145,13 @@ public class RoutingPoliciesTest {
assertEquals(numberOfDeployments * clustersPerZone,
tester.policiesOf(context1.instance().id()).size(),
"Routing policy count is equal to cluster count");
+ assertEquals(List.of(),
+ tester.controllerTester().controller().routing()
+ .readDeclaredEndpointsOf(context1.instanceId())
+ .scope(Endpoint.Scope.zone)
+ .legacy()
+ .asList(),
+ "No endpoints marked as legacy");
// Applications gains a new deployment
ApplicationPackage applicationPackage2 = applicationPackageBuilder()
@@ -305,6 +316,13 @@ public class RoutingPoliciesTest {
);
assertEquals(expectedRecords, tester.recordNames());
assertEquals(4, tester.policiesOf(context1.instanceId()).size());
+ assertEquals(List.of(),
+ tester.controllerTester().controller().routing()
+ .readEndpointsOf(context1.deploymentIdIn(zone1))
+ .scope(Endpoint.Scope.zone)
+ .legacy()
+ .asList(),
+ "No endpoints marked as legacy");
// Next deploy does nothing
context1.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
@@ -386,20 +404,24 @@ public class RoutingPoliciesTest {
}
@Test
- @Disabled // TODO(mpolden): Enable this test when we start creating generated endpoints for shared routing
- void zone_routing_policies_with_shared_routing_and_generated_endpoint() {
+ void zone_routing_policies_with_shared_routing_and_generated_endpoint_config_and_token() {
var tester = new RoutingPoliciesTester(new DeploymentTester(), false);
var context = tester.newDeploymentContext("tenant1", "app1", "default");
- tester.provisionLoadBalancers(1, context.instanceId(), true, zone1, zone2);
- tester.controllerTester().flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true);
+ tester.setEndpointConfig(EndpointConfig.generated);
addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester);
ApplicationPackage applicationPackage = applicationPackageBuilder().region(zone1.region())
.region(zone2.region())
.container("c0", AuthMethod.mtls, AuthMethod.token)
.build();
+ tester.provisionLoadBalancers(1, context.instanceId(), true,
+ testZoneMain, stagingZoneMain, zone1, zone2);
context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
- assertEquals(List.of("c0a25b7c.cafed00d.z.vespa.oath.cloud",
- "dc5e383c.cafed00d.z.vespa.oath.cloud"),
+ // This only creates wildcard endpoint names in DNS because legacy names in shared routing-mode use a static
+ // wildcard DNS record pointing to the routing layer
+ assertEquals(List.of("a9c8c045.cafed00d.z.vespa.oath.cloud",
+ "dc5e383c.cafed00d.z.vespa.oath.cloud",
+ "ebd395b6.cafed00d.z.vespa.oath.cloud",
+ "ee82b867.cafed00d.z.vespa.oath.cloud"),
tester.recordNames());
}
@@ -1054,11 +1076,9 @@ public class RoutingPoliciesTest {
}
@Test
- public void generated_endpoints() {
- var tester = new RoutingPoliciesTester(SystemName.Public);
+ public void combined_endpoint_config() {
+ var tester = new RoutingPoliciesTester(SystemName.Public).setEndpointConfig(EndpointConfig.combined);
var context = tester.newDeploymentContext("tenant1", "app1", "default");
- tester.controllerTester().flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true);
- addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester);
// Deploy application
int clustersPerZone = 2;
@@ -1079,10 +1099,10 @@ public class RoutingPoliciesTest {
// Deployment creates generated zone names
List<String> expectedRecords = List.of(
// save me, jebus!
- "a6414896.cafed00d.aws-eu-west-1.w.vespa-app.cloud",
- "b36bf591.cafed00d.z.vespa-app.cloud",
+ "a6414896.f5549014.aws-eu-west-1.w.vespa-app.cloud",
+ "aa7591aa.f5549014.z.vespa-app.cloud",
"bar.app1.tenant1.a.vespa-app.cloud",
- "bc50b636.cafed00d.z.vespa-app.cloud",
+ "bc50b636.f5549014.z.vespa-app.cloud",
"c0.app1.tenant1.aws-eu-west-1.w.vespa-app.cloud",
"c0.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1.w.vespa-app.cloud",
@@ -1091,32 +1111,43 @@ public class RoutingPoliciesTest {
"c1.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud",
"c1.app1.tenant1.aws-us-east-1a.z.vespa-app.cloud",
"c1.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
- "c33db5ed.cafed00d.z.vespa-app.cloud",
- "d467800f.cafed00d.z.vespa-app.cloud",
- "d71005bf.cafed00d.z.vespa-app.cloud",
- "dd0971b4.cafed00d.z.vespa-app.cloud",
- "eb48ad53.cafed00d.z.vespa-app.cloud",
- "ec1e1288.cafed00d.z.vespa-app.cloud",
- "f2fa41ec.cafed00d.g.vespa-app.cloud",
- "f411d177.cafed00d.z.vespa-app.cloud",
- "f4a4d111.cafed00d.a.vespa-app.cloud",
- "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud",
+ "c33db5ed.f5549014.z.vespa-app.cloud",
+ "d467800f.f5549014.z.vespa-app.cloud",
+ "d71005bf.f5549014.z.vespa-app.cloud",
+ "dd0971b4.f5549014.g.vespa-app.cloud",
+ "eb48ad53.f5549014.z.vespa-app.cloud",
+ "ec1e1288.f5549014.z.vespa-app.cloud",
+ "f2fa41ec.f5549014.a.vespa-app.cloud",
+ "f411d177.f5549014.z.vespa-app.cloud",
+ "f4a4d111.f5549014.z.vespa-app.cloud",
+ "fcf1bd63.f5549014.aws-us-east-1.w.vespa-app.cloud",
"foo.app1.tenant1.g.vespa-app.cloud"
);
assertEquals(expectedRecords, tester.recordNames());
assertEquals(6, tester.policiesOf(context.instanceId()).size());
ClusterSpec.Id cluster0 = ClusterSpec.Id.from("c0");
ClusterSpec.Id cluster1 = ClusterSpec.Id.from("c1");
+ // The expected number of endpoints are created
for (var zone : List.of(zone1, zone2)) {
- EndpointList generated = tester.controllerTester().controller().routing()
- .readEndpointsOf(context.deploymentIdIn(zone))
- .scope(Endpoint.Scope.zone)
- .generated();
+ EndpointList zoneEndpoints = tester.controllerTester().controller().routing()
+ .readEndpointsOf(context.deploymentIdIn(zone))
+ .scope(Endpoint.Scope.zone);
+ EndpointList generated = zoneEndpoints.generated();
assertEquals(1, generated.cluster(cluster0).size());
assertEquals(0, generated.cluster(cluster0).authMethod(AuthMethod.token).size());
assertEquals(2, generated.cluster(cluster1).size());
assertEquals(1, generated.cluster(cluster1).authMethod(AuthMethod.token).size());
+ EndpointList legacy = zoneEndpoints.legacy();
+ assertEquals(1, legacy.cluster(cluster0).size());
+ assertEquals(0, legacy.cluster(cluster0).authMethod(AuthMethod.token).size());
+ assertEquals(1, legacy.cluster(cluster1).size());
+ assertEquals(0, legacy.cluster(cluster1).authMethod(AuthMethod.token).size());
}
+ EndpointList declaredEndpoints = tester.controllerTester().controller().routing().readDeclaredEndpointsOf(context.application());
+ assertEquals(1, declaredEndpoints.scope(Endpoint.Scope.global).generated().size());
+ assertEquals(1, declaredEndpoints.scope(Endpoint.Scope.global).legacy().size());
+ assertEquals(1, declaredEndpoints.scope(Endpoint.Scope.application).generated().size());
+ assertEquals(1, declaredEndpoints.scope(Endpoint.Scope.application).legacy().size());
Map<DeploymentId, Set<ContainerEndpoint>> containerEndpointsInProd = tester.containerEndpoints(Environment.prod);
// Ordinary endpoints point to expected targets
@@ -1153,23 +1184,23 @@ public class RoutingPoliciesTest {
.build();
context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
assertEquals(List.of(
- "b36bf591.cafed00d.z.vespa-app.cloud",
+ "aa7591aa.f5549014.z.vespa-app.cloud",
"bar.app1.tenant1.a.vespa-app.cloud",
- "bc50b636.cafed00d.z.vespa-app.cloud",
+ "bc50b636.f5549014.z.vespa-app.cloud",
"c0.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1a.z.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
"c1.app1.tenant1.aws-eu-west-1a.z.vespa-app.cloud",
"c1.app1.tenant1.aws-us-east-1a.z.vespa-app.cloud",
"c1.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
- "c33db5ed.cafed00d.z.vespa-app.cloud",
- "d467800f.cafed00d.z.vespa-app.cloud",
- "d71005bf.cafed00d.z.vespa-app.cloud",
- "dd0971b4.cafed00d.z.vespa-app.cloud",
- "eb48ad53.cafed00d.z.vespa-app.cloud",
- "ec1e1288.cafed00d.z.vespa-app.cloud",
- "f411d177.cafed00d.z.vespa-app.cloud",
- "f4a4d111.cafed00d.a.vespa-app.cloud"
+ "c33db5ed.f5549014.z.vespa-app.cloud",
+ "d467800f.f5549014.z.vespa-app.cloud",
+ "d71005bf.f5549014.z.vespa-app.cloud",
+ "eb48ad53.f5549014.z.vespa-app.cloud",
+ "ec1e1288.f5549014.z.vespa-app.cloud",
+ "f2fa41ec.f5549014.a.vespa-app.cloud",
+ "f411d177.f5549014.z.vespa-app.cloud",
+ "f4a4d111.f5549014.z.vespa-app.cloud"
), tester.recordNames());
// Removing application removes all records
@@ -1181,16 +1212,71 @@ public class RoutingPoliciesTest {
}
@Test
- public void generated_endpoints_only() {
- var tester = new RoutingPoliciesTester(SystemName.Public);
+ public void generated_endpoint_config_with_token() {
+ var tester = new RoutingPoliciesTester(SystemName.Public).setEndpointConfig(EndpointConfig.generated);
+ var context = tester.newDeploymentContext("tenant1", "app1", "default");
+ addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester);
+
+ // Deploy application without token
+ var zone1 = ZoneId.from("prod", "aws-us-east-1c");
+ ApplicationPackage applicationPackage = applicationPackageBuilder().region(zone1.region())
+ .container("c0", AuthMethod.mtls)
+ .endpoint("foo", "c0")
+ .build();
+ tester.provisionLoadBalancers(1, context.instanceId(), testZonePublic, stagingZonePublic, zone1);
+ context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy();
+ assertEquals(List.of("a9c8c045.cafed00d.g.vespa-app.cloud",
+ "ebd395b6.cafed00d.z.vespa-app.cloud",
+ "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud"),
+ tester.recordNames());
+
+ // Re-deploy with token enabled
+ applicationPackage = applicationPackageBuilder().region(zone1.region())
+ .container("c0", AuthMethod.mtls, AuthMethod.token)
+ .endpoint("foo", "c0")
+ .build();
+ tester.provisionLoadBalancers(1, context.instanceId(), testZonePublic, stagingZonePublic);
+ context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy();
+ // Additional zone- and global-scoped endpoints are added (token)
+ assertEquals(List.of("a9c8c045.cafed00d.g.vespa-app.cloud",
+ "b7e79800.cafed00d.z.vespa-app.cloud",
+ "c60d3149.cafed00d.g.vespa-app.cloud",
+ "ebd395b6.cafed00d.z.vespa-app.cloud",
+ "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud"),
+ tester.recordNames());
+
+ // Add new endpoint is generated for an additional global endpoint
+ applicationPackage = applicationPackageBuilder().region(zone1.region())
+ .container("c0", AuthMethod.mtls, AuthMethod.token)
+ .endpoint("foo", "c0")
+ .endpoint("bar", "c0")
+ .build();
+ tester.provisionLoadBalancers(1, context.instanceId(), testZonePublic, stagingZonePublic);
+ context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy();
+ List<String> expectedRecords = List.of("a9c8c045.cafed00d.g.vespa-app.cloud",
+ "aa7591aa.cafed00d.g.vespa-app.cloud",
+ "b7e79800.cafed00d.z.vespa-app.cloud",
+ "c60d3149.cafed00d.g.vespa-app.cloud",
+ "d467800f.cafed00d.g.vespa-app.cloud",
+ "ebd395b6.cafed00d.z.vespa-app.cloud",
+ "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud");
+ assertEquals(expectedRecords, tester.recordNames());
+
+ // No change on redeployment
+ tester.provisionLoadBalancers(1, context.instanceId(), testZonePublic, stagingZonePublic);
+ context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy();
+ assertEquals(expectedRecords, tester.recordNames());
+ }
+
+ @Test
+ public void generated_endpoint_config() {
+ var tester = new RoutingPoliciesTester(SystemName.Public).setEndpointConfig(EndpointConfig.generated);
var context = tester.newDeploymentContext("tenant1", "app1", "default");
- tester.controllerTester().flagSource()
- .withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true)
- .withBooleanFlag(Flags.LEGACY_ENDPOINTS.id(), false);
addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester);
// Deploy application
var zone1 = ZoneId.from("prod", "aws-us-east-1c");
+ var zone2 = ZoneId.from("prod", "aws-eu-west-1a");
ApplicationPackage applicationPackage = applicationPackageBuilder().region(zone1.region())
.container("c0", AuthMethod.mtls)
.endpoint("foo", "c0")
@@ -1198,8 +1284,7 @@ public class RoutingPoliciesTest {
tester.provisionLoadBalancers(1, context.instanceId(), zone1);
// ConfigServerMock provisions a load balancer for the "default" cluster, but in this scenario we need full
// control over the load balancer name because "default" has no special treatment when using generated endpoints
- tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("test", "aws-us-east-2c"));
- tester.provisionLoadBalancers(1, context.instanceId(), ZoneId.from("staging", "aws-us-east-3c"));
+ tester.provisionLoadBalancers(1, context.instanceId(), testZonePublic, stagingZonePublic);
context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy();
tester.assertTargets(context.instance().id(), EndpointId.of("foo"), ClusterSpec.Id.from("c0"),
0, Map.of(zone1, 1L), true);
@@ -1207,15 +1292,29 @@ public class RoutingPoliciesTest {
"ebd395b6.cafed00d.z.vespa-app.cloud",
"fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud"),
tester.recordNames());
+
+ // Another zone is added to global endpoint
+ applicationPackage = applicationPackageBuilder().region(zone1.region())
+ .region(zone2.region())
+ .container("c0", AuthMethod.mtls)
+ .endpoint("foo", "c0")
+ .build();
+ tester.provisionLoadBalancers(1, context.instanceId(), testZonePublic, stagingZonePublic);
+ tester.provisionLoadBalancers(1, context.instanceId(), zone2);
+ context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.test, Environment.staging, Environment.prod).deploy();
+ assertEquals(List.of("a6414896.cafed00d.aws-eu-west-1.w.vespa-app.cloud",
+ "a9c8c045.cafed00d.g.vespa-app.cloud",
+ "cbff1506.cafed00d.z.vespa-app.cloud",
+ "ebd395b6.cafed00d.z.vespa-app.cloud",
+ "fcf1bd63.cafed00d.aws-us-east-1.w.vespa-app.cloud"),
+ tester.recordNames());
}
@Test
- public void generated_endpoints_multi_instance() {
- var tester = new RoutingPoliciesTester(SystemName.Public);
+ public void combined_endpoint_config_with_multiple_instances() {
+ var tester = new RoutingPoliciesTester(SystemName.Public).setEndpointConfig(EndpointConfig.combined);
var context0 = tester.newDeploymentContext("tenant1", "app1", "default");
var context1 = tester.newDeploymentContext("tenant1", "app1", "beta");
- tester.controllerTester().flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true);
- addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester);
// Deploy application
int clustersPerZone = 1;
@@ -1231,11 +1330,11 @@ public class RoutingPoliciesTest {
tester.provisionLoadBalancers(clustersPerZone, context1.instanceId(), zone1);
context0.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
assertEquals(List.of("a0.app1.tenant1.a.vespa-app.cloud",
- "a9c8c045.cafed00d.z.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
"c0.beta.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
- "e144a11b.cafed00d.z.vespa-app.cloud",
- "ee82b867.cafed00d.a.vespa-app.cloud"),
+ "cbff1506.f5549014.z.vespa-app.cloud",
+ "e144a11b.f5549014.a.vespa-app.cloud",
+ "ee82b867.f5549014.z.vespa-app.cloud"),
tester.recordNames());
tester.assertTargets(context0.application().id(), EndpointId.of("a0"), ClusterSpec.Id.from("c0"), 0,
Map.of(context0.deploymentIdIn(zone1), 1, context1.deploymentIdIn(zone1), 1));
@@ -1249,11 +1348,11 @@ public class RoutingPoliciesTest {
.build();
context0.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
assertEquals(List.of("a0.app1.tenant1.a.vespa-app.cloud",
- "a9c8c045.cafed00d.z.vespa-app.cloud",
"c0.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
"c0.beta.app1.tenant1.aws-us-east-1c.z.vespa-app.cloud",
- "e144a11b.cafed00d.z.vespa-app.cloud",
- "ee82b867.cafed00d.a.vespa-app.cloud"),
+ "cbff1506.f5549014.z.vespa-app.cloud",
+ "e144a11b.f5549014.a.vespa-app.cloud",
+ "ee82b867.f5549014.z.vespa-app.cloud"),
tester.recordNames());
tester.assertTargets(context0.application().id(), EndpointId.of("a0"), ClusterSpec.Id.from("c0"), 0,
Map.of(context1.deploymentIdIn(zone1), 1));
@@ -1267,10 +1366,9 @@ public class RoutingPoliciesTest {
}
@Test
- public void generated_endpoint_migration_with_global_endpoint() {
- var tester = new RoutingPoliciesTester(SystemName.Public);
+ public void migrate_legacy_to_combined_endpoint_config_with_global_endpoint() {
+ var tester = new RoutingPoliciesTester(SystemName.Public).setEndpointConfig(EndpointConfig.legacy);
var context = tester.newDeploymentContext("tenant1", "app1", "default");
- addCertificateToPool("cafed00d", UnassignedCertificate.State.ready, tester);
// Deploy application
int clustersPerZone = 2;
@@ -1285,8 +1383,8 @@ public class RoutingPoliciesTest {
context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
tester.assertTargets(context.instanceId(), EndpointId.of("foo"), 0, zone1, zone2);
- // Switch to generated
- tester.controllerTester().flagSource().withBooleanFlag(Flags.RANDOMIZED_ENDPOINT_NAMES.id(), true);
+ // Switch to combined
+ tester.setEndpointConfig(EndpointConfig.combined);
context.submit(applicationPackage).deferLoadBalancerProvisioningIn(Environment.prod).deploy();
tester.assertTargets(context.instance().id(), EndpointId.of("foo"), ClusterSpec.Id.from("c0"),
0, Map.of(zone1, 1L, zone2, 1L), true);
@@ -1296,9 +1394,13 @@ public class RoutingPoliciesTest {
EndpointCertificate cert = new EndpointCertificate("testKey", "testCert", 1, 0,
"request-id",
Optional.of("leaf-request-uuid"),
- List.of("name1", "name2"),
- "", Optional.empty(),
- Optional.empty(), Optional.of(id));
+ List.of("*." + id + ".z.vespa-app.cloud",
+ "*." + id + ".g.vespa-app.cloud",
+ "*." + id + ".a.vespa-app.cloud"),
+ "",
+ Optional.empty(),
+ Optional.empty(),
+ Optional.of(id));
UnassignedCertificate pooledCert = new UnassignedCertificate(cert, state);
tester.controllerTester().controller().curator().writeUnassignedCertificate(pooledCert);
}
@@ -1414,6 +1516,11 @@ public class RoutingPoliciesTest {
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
+ public RoutingPoliciesTester setEndpointConfig(EndpointConfig config) {
+ tester.controllerTester().flagSource().withStringFlag(Flags.ENDPOINT_CONFIG.id(), config.name());
+ return this;
+ }
+
public RoutingPolicies routingPolicies() {
return tester.controllerTester().controller().routing().policies();
}
@@ -1555,7 +1662,7 @@ public class RoutingPoliciesTest {
} else {
global = global.not().generated();
}
- String globalEndpoint = global.primary()
+ String globalEndpoint = global.first()
.map(Endpoint::dnsName)
.orElse("<none>");
assertEquals(latencyTargets, Set.copyOf(aliasDataOf(globalEndpoint)), "Global endpoint " + globalEndpoint + " points to expected latency targets");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java
index 6190680d098..43be5631727 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.routing.rotation;
import com.yahoo.config.provision.RegionName;
@@ -146,7 +146,7 @@ public class RotationRepositoryTest {
application2.submit(applicationPackage).deploy();
assertEquals(List.of(new RotationId("foo-1")), rotationIds(application2.instance().rotations()));
assertEquals("https://cd.app2.tenant2.global.cd.vespa.oath.cloud/",
- tester.controller().routing().readDeclaredEndpointsOf(application2.instanceId()).primary().get().url().toString());
+ tester.controller().routing().readDeclaredEndpointsOf(application2.instanceId()).first().get().url().toString());
}
@Test
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/security/CloudUserSessionManagerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/security/CloudUserSessionManagerTest.java
index 710e75fb235..16485019355 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/security/CloudUserSessionManagerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/security/CloudUserSessionManagerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.security;
import com.yahoo.config.provision.SystemName;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/Keys.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/Keys.java
index 7a293e661c9..e3e76920bca 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/Keys.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/Keys.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.tls;
import com.yahoo.security.KeyAlgorithm;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecretStoreMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecretStoreMock.java
index 0de065b3eaf..2498668e28a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecretStoreMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecretStoreMock.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.tls;
import com.yahoo.component.annotation.Inject;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecureContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecureContainerTest.java
index 05a0aedb18b..76f3b229028 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecureContainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/tls/SecureContainerTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.tls;
import com.yahoo.application.Networking;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClientTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClientTest.java
index 33b1792accb..1d9a98df0df 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClientTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/MavenRepositoryClientTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.vespa.hosted.controller.api.integration.maven.ArtifactId;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
index abce1c309ae..4c46dbf6142 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
@@ -1,4 +1,4 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.versions;
import com.yahoo.component.Version;
@@ -16,7 +16,9 @@ import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
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.deployment.DeploymentTrigger.ChangesToCancel;
import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb;
@@ -701,6 +703,80 @@ public class VersionStatusTest {
}
}
+ @Test
+ void testPinnedAppsAreIgnoredForIncreasingConfidenceWhenLessThanHalfArePinned() {
+ DeploymentContext canaries[] = new DeploymentContext[3];
+ DeploymentContext defaults[] = new DeploymentContext[3];
+ DeploymentTester tester = new DeploymentTester().atMondayMorning();
+ Version version1 = new Version("6.2");
+ tester.controllerTester().upgradeSystem(version1);
+
+ for (int i = 0; i < 3; i++) {
+ canaries[i] = tester.newDeploymentContext("t" + i, "a", "default");
+ canaries[i].submit(canaryApplicationPackage).deploy();
+ defaults[i] = tester.newDeploymentContext("t" + i, "b", "default");
+ defaults[i].submit(defaultApplicationPackage).deploy();
+ }
+
+ assertEquals(Confidence.high, confidence(tester.controller(), version1));
+
+ // All apps are pinned to version1, and then version2 releases. Initial confidence is low.
+ for (int i = 0; i < 3; i++) {
+ tester.deploymentTrigger().forceChange(canaries[i].instanceId(), Change.empty().withPlatformPin());
+ tester.deploymentTrigger().forceChange(defaults[i].instanceId(), Change.empty().withPlatformPin());
+ }
+ Version version2 = new Version("6.3");
+ tester.controllerTester().upgradeSystem(version2);
+ tester.upgrader().maintain();
+ tester.triggerJobs();
+ assertEquals(List.of(), tester.jobs().active());
+ assertEquals(Confidence.low, confidence(tester.controller(), version2));
+
+ // One canary and one default are unpinned and upgrade. Confidence remains low,
+ // as more than half the apps are pinned, and less tan 100%/90% have upgraded.
+ tester.deploymentTrigger().cancelChange(canaries[0].instanceId(), ChangesToCancel.ALL);
+ tester.deploymentTrigger().forceChange(canaries[0].instanceId(), Change.of(version2));
+ canaries[0].deployPlatform(version2);
+ tester.deploymentTrigger().cancelChange(defaults[0].instanceId(), ChangesToCancel.ALL);
+ tester.deploymentTrigger().forceChange(defaults[0].instanceId(), Change.of(version2));
+ defaults[0].deployPlatform(version2);
+ tester.controllerTester().computeVersionStatus();
+ assertEquals(Confidence.low, confidence(tester.controller(), version2));
+
+ // All apps are unpinned, and another canary and default upgrade. Confidence still remains low,
+ // as less than half of the unpinned apps have upgraded.
+ tester.deploymentTrigger().cancelChange(canaries[1].instanceId(), ChangesToCancel.ALL);
+ tester.deploymentTrigger().cancelChange(canaries[2].instanceId(), ChangesToCancel.ALL);
+ tester.deploymentTrigger().forceChange(canaries[1].instanceId(), Change.of(version2));
+ tester.deploymentTrigger().cancelChange(defaults[1].instanceId(), ChangesToCancel.ALL);
+ tester.deploymentTrigger().cancelChange(defaults[2].instanceId(), ChangesToCancel.ALL);
+ tester.deploymentTrigger().forceChange(defaults[1].instanceId(), Change.of(version2));
+ tester.controllerTester().computeVersionStatus();
+ assertEquals(Confidence.low, confidence(tester.controller(), version2));
+
+ // The second canary upgrades while the last is unpinned.
+ canaries[1].deployPlatform(version2);
+ tester.controllerTester().computeVersionStatus();
+ assertEquals(Confidence.low, confidence(tester.controller(), version2));
+
+ // When the last remaining canary is pinned, less than half are pinned, and all have upgraded,
+ // so confidence finally increases to normal.
+ tester.deploymentTrigger().forceChange(canaries[2].instanceId(), Change.empty().withPlatformPin());
+ tester.controllerTester().computeVersionStatus();
+ assertEquals(Confidence.normal, confidence(tester.controller(), version2));
+
+ // The second default upgrades while the last is unpinned.
+ defaults[1].deployPlatform(version2);
+ tester.controllerTester().computeVersionStatus();
+ assertEquals(Confidence.normal, confidence(tester.controller(), version2));
+
+ // When the last remaining default is pinned, less than half are pinned, and more than 90% have upgraded,
+ // so confidence increases to high.
+ tester.deploymentTrigger().forceChange(defaults[2].instanceId(), Change.empty().withPlatformPin());
+ tester.controllerTester().computeVersionStatus();
+ assertEquals(Confidence.high, confidence(tester.controller(), version2));
+ }
+
private void assertOnVersion(Version version, ApplicationId instance, DeploymentTester tester) {
var vespaVersion = tester.controller().readVersionStatus().version(version);
assertNotNull(vespaVersion, "Statistics for version " + version + " exist");
diff --git a/controller-server/src/test/resources/config-models/cd/config-models-cd.xml b/controller-server/src/test/resources/config-models/cd/config-models-cd.xml
index 2ed82101f73..3562d7fd997 100644
--- a/controller-server/src/test/resources/config-models/cd/config-models-cd.xml
+++ b/controller-server/src/test/resources/config-models/cd/config-models-cd.xml
@@ -1,3 +1,4 @@
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<component id='VespaModelFactory.8.218.31' class='com.yahoo.vespa.model.VespaModelFactory' bundle='config-model-fat-amended:8.218.31' />
<component id='YahooAdminModelAmender.8.218.31' class='com.yahoo.vespa.model.admin.amender.YahooAdminModelAmender' bundle='config-model-fat-amended:8.218.31' />
<component id='YahooModelValidator.8.218.31' class='com.yahoo.vespa.model.application.validation.YahooModelValidator' bundle='config-model-fat-amended:8.218.31' />
diff --git a/controller-server/src/test/resources/config-models/cd/config-models-main.xml b/controller-server/src/test/resources/config-models/cd/config-models-main.xml
index 3297840acd6..55c0e8b8bb6 100644
--- a/controller-server/src/test/resources/config-models/cd/config-models-main.xml
+++ b/controller-server/src/test/resources/config-models/cd/config-models-main.xml
@@ -1,3 +1,4 @@
+<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
<component id='VespaModelFactory.8.218.31' class='com.yahoo.vespa.model.VespaModelFactory' bundle='config-model-fat-amended:8.218.31' />
<component id='YahooAdminModelAmender.8.218.31' class='com.yahoo.vespa.model.admin.amender.YahooAdminModelAmender' bundle='config-model-fat-amended:8.218.31' />
<component id='YahooModelValidator.8.218.31' class='com.yahoo.vespa.model.application.validation.YahooModelValidator' bundle='config-model-fat-amended:8.218.31' />
diff --git a/controller-server/src/main/resources/mail/mail-notification.tmpl b/controller-server/src/test/resources/mail/notification.html
index 5bf5530b433..2a0edeea7e1 100644
--- a/controller-server/src/main/resources/mail/mail-notification.tmpl
+++ b/controller-server/src/test/resources/mail/notification.html
@@ -383,11 +383,12 @@
style="vertical-align: top"
width="100%"
>
- <tbody>
- <tr>
- <td
- align="left"
- style="
+
+<tbody>
+<tr>
+ <td
+ align="left"
+ style="
font-size: 0px;
padding: 0px 25px 0px 25px;
padding-top: 0px;
@@ -396,9 +397,9 @@
padding-left: 50px;
word-break: break-word;
"
- >
- <div
- style="
+ >
+ <div
+ style="
font-family: Open Sans, Helvetica, Arial,
sans-serif;
font-size: 13px;
@@ -406,23 +407,23 @@
text-align: left;
color: #797e82;
"
- >
- <h1
- style="
+ >
+ <h1
+ style="
text-align: center;
color: #000000;
line-height: 32px;
"
- >
- Vespa Cloud Notifications
- </h1>
- </div>
- </td>
- </tr>
- <tr>
- <td
- align="left"
- style="
+ >
+ Vespa Cloud Notifications
+ </h1>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="left"
+ style="
font-size: 0px;
padding: 0px 25px 0px 25px;
padding-top: 0px;
@@ -431,9 +432,9 @@
padding-left: 50px;
word-break: break-word;
"
- >
- <div
- style="
+ >
+ <div
+ style="
font-family: Open Sans, Helvetica, Arial,
sans-serif;
font-size: 13px;
@@ -441,51 +442,54 @@
text-align: left;
color: #797e82;
"
- >
- <p>
- [[NOTIFICATION_HEADER]]:
- </p>
- [[NOTIFICATION_ITEMS]]
- </div>
- </td>
- </tr>
- <tr>
- <td
- align="center"
- vertical-align="middle"
- style="
+ >
+
+<p>
+ There are problems with tests for default.default:
+</p>
+<p>Test package has production tests, but no production tests are declared in deployment.xml</p>
+<p>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</p>
+
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="center"
+ vertical-align="middle"
+ style="
font-size: 0px;
padding: 10px 25px;
padding-top: 20px;
padding-bottom: 20px;
word-break: break-word;
"
- >
- <table
- border="0"
- cellpadding="0"
- cellspacing="0"
- role="presentation"
- style="border-collapse: separate; line-height: 100%"
- >
- <tbody>
- <tr>
- <td
- align="center"
- bgcolor="#005A8E"
- role="presentation"
- style="
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="border-collapse: separate; line-height: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ bgcolor="#005A8E"
+ role="presentation"
+ style="
border: none;
border-radius: 100px;
cursor: auto;
mso-padding-alt: 15px 25px 15px 25px;
background: #005a8e;
"
- valign="middle"
- >
- <a
- href="[[LINK_TO_NOTIFICATION]]"
- style="
+ valign="middle"
+ >
+ <a
+ href="https://console.tld/tenant/tenant1/application/default/prod/instance/default"
+ style="
display: inline-block;
background: #005a8e;
color: #ffffff;
@@ -501,20 +505,20 @@
mso-padding-alt: 0px;
border-radius: 100px;
"
- target="_blank"
- ><b style="font-weight: 700"
- ><b style="font-weight: 700"
- >Go to Console</b
- ></b
- ></a
- >
- </td>
- </tr>
- </tbody>
- </table>
- </td>
- </tr>
- </tbody>
+ target="_blank"
+ ><b style="font-weight: 700"
+ ><b style="font-weight: 700"
+ >Go to Console</b
+ ></b
+ ></a
+ >
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+</tr>
+</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
@@ -592,7 +596,7 @@
target="_blank"
rel="noopener noreferrer"
style="color: #005a8e"
- href="[[LINK_TO_PRIVACY_POLICY]]"
+ href="https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html"
><span style="color: #005a8e"
>Yahoo Privacy Policy</span
></a
@@ -602,7 +606,7 @@
target="_blank"
rel="noopener noreferrer"
style="color: #005a8e"
- href="[[LINK_TO_TERMS_OF_SERVICE]]"
+ href="https://console.tld/terms-of-service-trial.html"
><span style="color: #005a8e"
>Terms of Service</span
></a
@@ -612,7 +616,7 @@
target="_blank"
rel="noopener noreferrer"
style="color: #005a8e"
- href="[[LINK_TO_SUPPORT]]"
+ href="https://console.tld/support"
><span style="color: #005a8e">Support</span></a
>
</p>
@@ -621,7 +625,7 @@
target="_blank"
rel="noopener noreferrer"
style="color: inherit; text-decoration: none"
- href="[[LINK_TO_ACCOUNT_NOTIFICATIONS]]"
+ href="https://console.tld/tenant/tenant1/account/notifications"
>Click
<span style="color: #005a8e"><u>here</u></span>
to manage your notifications setting.</a
diff --git a/controller-server/src/test/resources/mail/notification.txt b/controller-server/src/test/resources/mail/trial-expired.html
index 35db37fbc12..bdeafe8c7d3 100644
--- a/controller-server/src/test/resources/mail/notification.txt
+++ b/controller-server/src/test/resources/mail/trial-expired.html
@@ -383,11 +383,12 @@
style="vertical-align: top"
width="100%"
>
- <tbody>
- <tr>
- <td
- align="left"
- style="
+
+<tbody>
+<tr>
+ <td
+ align="left"
+ style="
font-size: 0px;
padding: 0px 25px 0px 25px;
padding-top: 0px;
@@ -396,9 +397,9 @@
padding-left: 50px;
word-break: break-word;
"
- >
- <div
- style="
+ >
+ <div
+ style="
font-family: Open Sans, Helvetica, Arial,
sans-serif;
font-size: 13px;
@@ -406,23 +407,23 @@
text-align: left;
color: #797e82;
"
- >
- <h1
- style="
+ >
+ <h1
+ style="
text-align: center;
color: #000000;
line-height: 32px;
"
- >
- Vespa Cloud Notifications
- </h1>
- </div>
- </td>
- </tr>
- <tr>
- <td
- align="left"
- style="
+ >
+ Your Vespa Cloud trial has expired
+ </h1>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="left"
+ style="
font-size: 0px;
padding: 0px 25px 0px 25px;
padding-top: 0px;
@@ -431,9 +432,9 @@
padding-left: 50px;
word-break: break-word;
"
- >
- <div
- style="
+ >
+ <div
+ style="
font-family: Open Sans, Helvetica, Arial,
sans-serif;
font-size: 13px;
@@ -441,51 +442,51 @@
text-align: left;
color: #797e82;
"
- >
- <p>
- There are problems with tests for default.default:
- </p>
- <p>Test package has production tests, but no production tests are declared in deployment.xml</p><p>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</p>
- </div>
- </td>
- </tr>
- <tr>
- <td
- align="center"
- vertical-align="middle"
- style="
+ >
+
+<p>
+ Your Vespa Cloud trial has expired. Please reach out to us if you have any questions or feedback.
+</p>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="center"
+ vertical-align="middle"
+ style="
font-size: 0px;
padding: 10px 25px;
padding-top: 20px;
padding-bottom: 20px;
word-break: break-word;
"
- >
- <table
- border="0"
- cellpadding="0"
- cellspacing="0"
- role="presentation"
- style="border-collapse: separate; line-height: 100%"
- >
- <tbody>
- <tr>
- <td
- align="center"
- bgcolor="#005A8E"
- role="presentation"
- style="
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="border-collapse: separate; line-height: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ bgcolor="#005A8E"
+ role="presentation"
+ style="
border: none;
border-radius: 100px;
cursor: auto;
mso-padding-alt: 15px 25px 15px 25px;
background: #005a8e;
"
- valign="middle"
- >
- <a
- href="https://dashboard.tld/tenant/tenant1/application/default/prod/instance"
- style="
+ valign="middle"
+ >
+ <a
+ href="https://console.tld/tenant/trial-tenant"
+ style="
display: inline-block;
background: #005a8e;
color: #ffffff;
@@ -501,20 +502,20 @@
mso-padding-alt: 0px;
border-radius: 100px;
"
- target="_blank"
- ><b style="font-weight: 700"
- ><b style="font-weight: 700"
- >Go to Console</b
- ></b
- ></a
- >
- </td>
- </tr>
- </tbody>
- </table>
- </td>
- </tr>
- </tbody>
+ target="_blank"
+ ><b style="font-weight: 700"
+ ><b style="font-weight: 700"
+ >Go to Console</b
+ ></b
+ ></a
+ >
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+</tr>
+</tbody>
</table>
</div>
<!--[if mso | IE]></td></tr></table><![endif]-->
@@ -602,7 +603,7 @@
target="_blank"
rel="noopener noreferrer"
style="color: #005a8e"
- href="https://dashboard.tld/terms-of-service-trial.html"
+ href="https://console.tld/terms-of-service-trial.html"
><span style="color: #005a8e"
>Terms of Service</span
></a
@@ -612,7 +613,7 @@
target="_blank"
rel="noopener noreferrer"
style="color: #005a8e"
- href="https://dashboard.tld/support"
+ href="https://console.tld/support"
><span style="color: #005a8e">Support</span></a
>
</p>
@@ -621,7 +622,7 @@
target="_blank"
rel="noopener noreferrer"
style="color: inherit; text-decoration: none"
- href="https://dashboard.tld/tenant/tenant1/account/notifications"
+ href="https://console.tld/tenant/trial-tenant/account/notifications"
>Click
<span style="color: #005a8e"><u>here</u></span>
to manage your notifications setting.</a
diff --git a/controller-server/src/test/resources/mail/trial-expiring-immediately.html b/controller-server/src/test/resources/mail/trial-expiring-immediately.html
new file mode 100644
index 00000000000..db89eca195a
--- /dev/null
+++ b/controller-server/src/test/resources/mail/trial-expiring-immediately.html
@@ -0,0 +1,646 @@
+<!DOCTYPE html>
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:v="urn:schemas-microsoft-com:vml"
+ xmlns:o="urn:schemas-microsoft-com:office:office"
+>
+ <head>
+ <title></title>
+ <!--[if !mso]><!-->
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <!--<![endif]-->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <style type="text/css">
+ #outlook a {
+ padding: 0;
+ }
+
+ body {
+ margin: 0;
+ padding: 0;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ }
+
+ table,
+ td {
+ border-collapse: collapse;
+ mso-table-lspace: 0pt;
+ mso-table-rspace: 0pt;
+ }
+
+ img {
+ border: 0;
+ height: auto;
+ line-height: 100%;
+ outline: none;
+ text-decoration: none;
+ -ms-interpolation-mode: bicubic;
+ }
+
+ p {
+ display: block;
+ margin: 13px 0;
+ }
+ </style>
+ <!--[if mso]>
+ <noscript>
+ <xml>
+ <o:OfficeDocumentSettings>
+ <o:AllowPNG />
+ <o:PixelsPerInch>96</o:PixelsPerInch>
+ </o:OfficeDocumentSettings>
+ </xml>
+ </noscript>
+ <![endif]-->
+ <!--[if lte mso 11]>
+ <style type="text/css">
+ .mj-outlook-group-fix {
+ width: 100% !important;
+ }
+ </style>
+ <![endif]-->
+ <!--[if !mso]><!-->
+ <link
+ href="https://fonts.googleapis.com/css?family=Open Sans"
+ rel="stylesheet"
+ type="text/css"
+ />
+ <style type="text/css">
+ @import url(https://fonts.googleapis.com/css?family=Open Sans);
+ </style>
+ <!--<![endif]-->
+ <style type="text/css">
+ @media only screen and (min-width: 480px) {
+ .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ }
+ </style>
+ <style media="screen and (min-width:480px)">
+ .moz-text-html .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ </style>
+ <style type="text/css">
+ [owa] .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ </style>
+ <style type="text/css">
+ @media only screen and (max-width: 480px) {
+ table.mj-full-width-mobile {
+ width: 100% !important;
+ }
+
+ td.mj-full-width-mobile {
+ width: auto !important;
+ }
+ }
+ </style>
+ </head>
+
+ <body style="word-spacing: normal; background-color: #f2f7fa">
+ <div style="background-color: #f2f7fa">
+ <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div style="margin: 0px auto; max-width: 600px">
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 0px;
+ padding-top: 0px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 0px 0px 25px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 11px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+ <br />
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div
+ style="
+ background: #ffffff;
+ background-color: #ffffff;
+ margin: 0px auto;
+ max-width: 600px;
+ "
+ >
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="background: #ffffff; background-color: #ffffff; width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0;
+ padding-bottom: 0px;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 0px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 0px;
+ padding-right: 0px;
+ padding-bottom: 40px;
+ padding-left: 0px;
+ word-break: break-word;
+ "
+ >
+ <p
+ style="
+ border-top: solid 8px #005a8e;
+ font-size: 1px;
+ margin: 0px auto;
+ width: 100%;
+ "
+ ></p>
+ <!--[if mso | IE
+ ]><table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ style="
+ border-top: solid 8px #005a8e;
+ font-size: 1px;
+ margin: 0px auto;
+ width: 600px;
+ "
+ role="presentation"
+ width="600px"
+ >
+ <tr>
+ <td style="height: 0; line-height: 0">
+ &nbsp;
+ </td>
+ </tr>
+ </table><!
+ [endif]-->
+ </td>
+ </tr>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="
+ border-collapse: collapse;
+ border-spacing: 0px;
+ "
+ >
+ <tbody>
+ <tr>
+ <td style="width: 121px">
+ <img
+ alt=""
+ height="auto"
+ src="https://data.vespa.oath.cloud/assets/vespa-cloud-logo.png"
+ style="
+ border: none;
+ display: block;
+ outline: none;
+ text-decoration: none;
+ height: auto;
+ width: 100%;
+ font-size: 13px;
+ "
+ width="121"
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div
+ style="
+ background: #ffffff;
+ background-color: #ffffff;
+ margin: 0px auto;
+ max-width: 600px;
+ "
+ >
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="background: #ffffff; background-color: #ffffff; width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 70px;
+ padding-top: 30px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+
+<tbody>
+<tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 25px 0px 25px;
+ padding-top: 0px;
+ padding-right: 50px;
+ padding-bottom: 0px;
+ padding-left: 50px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+ <h1
+ style="
+ text-align: center;
+ color: #000000;
+ line-height: 32px;
+ "
+ >
+ Your Vespa Cloud trial expires tomorrow
+ </h1>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 25px 0px 25px;
+ padding-top: 0px;
+ padding-right: 50px;
+ padding-bottom: 0px;
+ padding-left: 50px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+
+<p>
+ Your Vespa Cloud trial expires tomorrow. Please reach out to us if you have any questions or feedback.
+</p>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="center"
+ vertical-align="middle"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ word-break: break-word;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="border-collapse: separate; line-height: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ bgcolor="#005A8E"
+ role="presentation"
+ style="
+ border: none;
+ border-radius: 100px;
+ cursor: auto;
+ mso-padding-alt: 15px 25px 15px 25px;
+ background: #005a8e;
+ "
+ valign="middle"
+ >
+ <a
+ href="https://console.tld/tenant/trial-tenant"
+ style="
+ display: inline-block;
+ background: #005a8e;
+ color: #ffffff;
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 120%;
+ margin: 0;
+ text-decoration: none;
+ text-transform: none;
+ padding: 15px 25px 15px 25px;
+ mso-padding-alt: 0px;
+ border-radius: 100px;
+ "
+ target="_blank"
+ ><b style="font-weight: 700"
+ ><b style="font-weight: 700"
+ >Go to Console</b
+ ></b
+ ></a
+ >
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+</tr>
+</tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div style="margin: 0px auto; max-width: 600px">
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 0px;
+ padding-top: 20px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 0px 20px 0px 20px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 11px;
+ line-height: 22px;
+ text-align: center;
+ color: #797e82;
+ "
+ >
+ <p style="margin: 10px 0">
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html"
+ ><span style="color: #005a8e"
+ >Yahoo Privacy Policy</span
+ ></a
+ ><span style="color: #797e82"
+ >&nbsp; &nbsp;|&nbsp; &nbsp;</span
+ ><a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://console.tld/terms-of-service-trial.html"
+ ><span style="color: #005a8e"
+ >Terms of Service</span
+ ></a
+ ><span style="color: #797e82"
+ >&nbsp; &nbsp;|&nbsp; &nbsp;</span
+ ><a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://console.tld/support"
+ ><span style="color: #005a8e">Support</span></a
+ >
+ </p>
+ <p style="margin: 10px 0">
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: inherit; text-decoration: none"
+ href="https://console.tld/tenant/trial-tenant/account/notifications"
+ >Click
+ <span style="color: #005a8e"><u>here</u></span>
+ to manage your notifications setting.</a
+ ><br />
+ </p>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </div>
+ </body>
+</html>
diff --git a/controller-server/src/test/resources/mail/trial-expiring-soon.html b/controller-server/src/test/resources/mail/trial-expiring-soon.html
new file mode 100644
index 00000000000..17c59240cc4
--- /dev/null
+++ b/controller-server/src/test/resources/mail/trial-expiring-soon.html
@@ -0,0 +1,646 @@
+<!DOCTYPE html>
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:v="urn:schemas-microsoft-com:vml"
+ xmlns:o="urn:schemas-microsoft-com:office:office"
+>
+ <head>
+ <title></title>
+ <!--[if !mso]><!-->
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <!--<![endif]-->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <style type="text/css">
+ #outlook a {
+ padding: 0;
+ }
+
+ body {
+ margin: 0;
+ padding: 0;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ }
+
+ table,
+ td {
+ border-collapse: collapse;
+ mso-table-lspace: 0pt;
+ mso-table-rspace: 0pt;
+ }
+
+ img {
+ border: 0;
+ height: auto;
+ line-height: 100%;
+ outline: none;
+ text-decoration: none;
+ -ms-interpolation-mode: bicubic;
+ }
+
+ p {
+ display: block;
+ margin: 13px 0;
+ }
+ </style>
+ <!--[if mso]>
+ <noscript>
+ <xml>
+ <o:OfficeDocumentSettings>
+ <o:AllowPNG />
+ <o:PixelsPerInch>96</o:PixelsPerInch>
+ </o:OfficeDocumentSettings>
+ </xml>
+ </noscript>
+ <![endif]-->
+ <!--[if lte mso 11]>
+ <style type="text/css">
+ .mj-outlook-group-fix {
+ width: 100% !important;
+ }
+ </style>
+ <![endif]-->
+ <!--[if !mso]><!-->
+ <link
+ href="https://fonts.googleapis.com/css?family=Open Sans"
+ rel="stylesheet"
+ type="text/css"
+ />
+ <style type="text/css">
+ @import url(https://fonts.googleapis.com/css?family=Open Sans);
+ </style>
+ <!--<![endif]-->
+ <style type="text/css">
+ @media only screen and (min-width: 480px) {
+ .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ }
+ </style>
+ <style media="screen and (min-width:480px)">
+ .moz-text-html .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ </style>
+ <style type="text/css">
+ [owa] .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ </style>
+ <style type="text/css">
+ @media only screen and (max-width: 480px) {
+ table.mj-full-width-mobile {
+ width: 100% !important;
+ }
+
+ td.mj-full-width-mobile {
+ width: auto !important;
+ }
+ }
+ </style>
+ </head>
+
+ <body style="word-spacing: normal; background-color: #f2f7fa">
+ <div style="background-color: #f2f7fa">
+ <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div style="margin: 0px auto; max-width: 600px">
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 0px;
+ padding-top: 0px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 0px 0px 25px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 11px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+ <br />
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div
+ style="
+ background: #ffffff;
+ background-color: #ffffff;
+ margin: 0px auto;
+ max-width: 600px;
+ "
+ >
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="background: #ffffff; background-color: #ffffff; width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0;
+ padding-bottom: 0px;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 0px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 0px;
+ padding-right: 0px;
+ padding-bottom: 40px;
+ padding-left: 0px;
+ word-break: break-word;
+ "
+ >
+ <p
+ style="
+ border-top: solid 8px #005a8e;
+ font-size: 1px;
+ margin: 0px auto;
+ width: 100%;
+ "
+ ></p>
+ <!--[if mso | IE
+ ]><table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ style="
+ border-top: solid 8px #005a8e;
+ font-size: 1px;
+ margin: 0px auto;
+ width: 600px;
+ "
+ role="presentation"
+ width="600px"
+ >
+ <tr>
+ <td style="height: 0; line-height: 0">
+ &nbsp;
+ </td>
+ </tr>
+ </table><!
+ [endif]-->
+ </td>
+ </tr>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="
+ border-collapse: collapse;
+ border-spacing: 0px;
+ "
+ >
+ <tbody>
+ <tr>
+ <td style="width: 121px">
+ <img
+ alt=""
+ height="auto"
+ src="https://data.vespa.oath.cloud/assets/vespa-cloud-logo.png"
+ style="
+ border: none;
+ display: block;
+ outline: none;
+ text-decoration: none;
+ height: auto;
+ width: 100%;
+ font-size: 13px;
+ "
+ width="121"
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div
+ style="
+ background: #ffffff;
+ background-color: #ffffff;
+ margin: 0px auto;
+ max-width: 600px;
+ "
+ >
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="background: #ffffff; background-color: #ffffff; width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 70px;
+ padding-top: 30px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+
+<tbody>
+<tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 25px 0px 25px;
+ padding-top: 0px;
+ padding-right: 50px;
+ padding-bottom: 0px;
+ padding-left: 50px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+ <h1
+ style="
+ text-align: center;
+ color: #000000;
+ line-height: 32px;
+ "
+ >
+ Your Vespa Cloud trial expires in 2 days
+ </h1>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 25px 0px 25px;
+ padding-top: 0px;
+ padding-right: 50px;
+ padding-bottom: 0px;
+ padding-left: 50px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+
+<p>
+ Your Vespa Cloud trial expires in 2 days. Please reach out to us if you have any questions or feedback.
+</p>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="center"
+ vertical-align="middle"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ word-break: break-word;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="border-collapse: separate; line-height: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ bgcolor="#005A8E"
+ role="presentation"
+ style="
+ border: none;
+ border-radius: 100px;
+ cursor: auto;
+ mso-padding-alt: 15px 25px 15px 25px;
+ background: #005a8e;
+ "
+ valign="middle"
+ >
+ <a
+ href="https://console.tld/tenant/trial-tenant"
+ style="
+ display: inline-block;
+ background: #005a8e;
+ color: #ffffff;
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 120%;
+ margin: 0;
+ text-decoration: none;
+ text-transform: none;
+ padding: 15px 25px 15px 25px;
+ mso-padding-alt: 0px;
+ border-radius: 100px;
+ "
+ target="_blank"
+ ><b style="font-weight: 700"
+ ><b style="font-weight: 700"
+ >Go to Console</b
+ ></b
+ ></a
+ >
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+</tr>
+</tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div style="margin: 0px auto; max-width: 600px">
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 0px;
+ padding-top: 20px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 0px 20px 0px 20px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 11px;
+ line-height: 22px;
+ text-align: center;
+ color: #797e82;
+ "
+ >
+ <p style="margin: 10px 0">
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html"
+ ><span style="color: #005a8e"
+ >Yahoo Privacy Policy</span
+ ></a
+ ><span style="color: #797e82"
+ >&nbsp; &nbsp;|&nbsp; &nbsp;</span
+ ><a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://console.tld/terms-of-service-trial.html"
+ ><span style="color: #005a8e"
+ >Terms of Service</span
+ ></a
+ ><span style="color: #797e82"
+ >&nbsp; &nbsp;|&nbsp; &nbsp;</span
+ ><a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://console.tld/support"
+ ><span style="color: #005a8e">Support</span></a
+ >
+ </p>
+ <p style="margin: 10px 0">
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: inherit; text-decoration: none"
+ href="https://console.tld/tenant/trial-tenant/account/notifications"
+ >Click
+ <span style="color: #005a8e"><u>here</u></span>
+ to manage your notifications setting.</a
+ ><br />
+ </p>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </div>
+ </body>
+</html>
diff --git a/controller-server/src/test/resources/mail/trial-reminder.html b/controller-server/src/test/resources/mail/trial-reminder.html
new file mode 100644
index 00000000000..fbe0d573538
--- /dev/null
+++ b/controller-server/src/test/resources/mail/trial-reminder.html
@@ -0,0 +1,646 @@
+<!DOCTYPE html>
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:v="urn:schemas-microsoft-com:vml"
+ xmlns:o="urn:schemas-microsoft-com:office:office"
+>
+ <head>
+ <title></title>
+ <!--[if !mso]><!-->
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <!--<![endif]-->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <style type="text/css">
+ #outlook a {
+ padding: 0;
+ }
+
+ body {
+ margin: 0;
+ padding: 0;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ }
+
+ table,
+ td {
+ border-collapse: collapse;
+ mso-table-lspace: 0pt;
+ mso-table-rspace: 0pt;
+ }
+
+ img {
+ border: 0;
+ height: auto;
+ line-height: 100%;
+ outline: none;
+ text-decoration: none;
+ -ms-interpolation-mode: bicubic;
+ }
+
+ p {
+ display: block;
+ margin: 13px 0;
+ }
+ </style>
+ <!--[if mso]>
+ <noscript>
+ <xml>
+ <o:OfficeDocumentSettings>
+ <o:AllowPNG />
+ <o:PixelsPerInch>96</o:PixelsPerInch>
+ </o:OfficeDocumentSettings>
+ </xml>
+ </noscript>
+ <![endif]-->
+ <!--[if lte mso 11]>
+ <style type="text/css">
+ .mj-outlook-group-fix {
+ width: 100% !important;
+ }
+ </style>
+ <![endif]-->
+ <!--[if !mso]><!-->
+ <link
+ href="https://fonts.googleapis.com/css?family=Open Sans"
+ rel="stylesheet"
+ type="text/css"
+ />
+ <style type="text/css">
+ @import url(https://fonts.googleapis.com/css?family=Open Sans);
+ </style>
+ <!--<![endif]-->
+ <style type="text/css">
+ @media only screen and (min-width: 480px) {
+ .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ }
+ </style>
+ <style media="screen and (min-width:480px)">
+ .moz-text-html .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ </style>
+ <style type="text/css">
+ [owa] .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ </style>
+ <style type="text/css">
+ @media only screen and (max-width: 480px) {
+ table.mj-full-width-mobile {
+ width: 100% !important;
+ }
+
+ td.mj-full-width-mobile {
+ width: auto !important;
+ }
+ }
+ </style>
+ </head>
+
+ <body style="word-spacing: normal; background-color: #f2f7fa">
+ <div style="background-color: #f2f7fa">
+ <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div style="margin: 0px auto; max-width: 600px">
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 0px;
+ padding-top: 0px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 0px 0px 25px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 11px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+ <br />
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div
+ style="
+ background: #ffffff;
+ background-color: #ffffff;
+ margin: 0px auto;
+ max-width: 600px;
+ "
+ >
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="background: #ffffff; background-color: #ffffff; width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0;
+ padding-bottom: 0px;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 0px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 0px;
+ padding-right: 0px;
+ padding-bottom: 40px;
+ padding-left: 0px;
+ word-break: break-word;
+ "
+ >
+ <p
+ style="
+ border-top: solid 8px #005a8e;
+ font-size: 1px;
+ margin: 0px auto;
+ width: 100%;
+ "
+ ></p>
+ <!--[if mso | IE
+ ]><table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ style="
+ border-top: solid 8px #005a8e;
+ font-size: 1px;
+ margin: 0px auto;
+ width: 600px;
+ "
+ role="presentation"
+ width="600px"
+ >
+ <tr>
+ <td style="height: 0; line-height: 0">
+ &nbsp;
+ </td>
+ </tr>
+ </table><!
+ [endif]-->
+ </td>
+ </tr>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="
+ border-collapse: collapse;
+ border-spacing: 0px;
+ "
+ >
+ <tbody>
+ <tr>
+ <td style="width: 121px">
+ <img
+ alt=""
+ height="auto"
+ src="https://data.vespa.oath.cloud/assets/vespa-cloud-logo.png"
+ style="
+ border: none;
+ display: block;
+ outline: none;
+ text-decoration: none;
+ height: auto;
+ width: 100%;
+ font-size: 13px;
+ "
+ width="121"
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div
+ style="
+ background: #ffffff;
+ background-color: #ffffff;
+ margin: 0px auto;
+ max-width: 600px;
+ "
+ >
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="background: #ffffff; background-color: #ffffff; width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 70px;
+ padding-top: 30px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+
+<tbody>
+<tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 25px 0px 25px;
+ padding-top: 0px;
+ padding-right: 50px;
+ padding-bottom: 0px;
+ padding-left: 50px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+ <h1
+ style="
+ text-align: center;
+ color: #000000;
+ line-height: 32px;
+ "
+ >
+ How is your Vespa Cloud trial going?
+ </h1>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 25px 0px 25px;
+ padding-top: 0px;
+ padding-right: 50px;
+ padding-bottom: 0px;
+ padding-left: 50px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+
+<p>
+ How is your Vespa Cloud trial going? Please reach out to us if you have any questions or feedback.
+</p>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="center"
+ vertical-align="middle"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ word-break: break-word;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="border-collapse: separate; line-height: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ bgcolor="#005A8E"
+ role="presentation"
+ style="
+ border: none;
+ border-radius: 100px;
+ cursor: auto;
+ mso-padding-alt: 15px 25px 15px 25px;
+ background: #005a8e;
+ "
+ valign="middle"
+ >
+ <a
+ href="https://console.tld/tenant/trial-tenant"
+ style="
+ display: inline-block;
+ background: #005a8e;
+ color: #ffffff;
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 120%;
+ margin: 0;
+ text-decoration: none;
+ text-transform: none;
+ padding: 15px 25px 15px 25px;
+ mso-padding-alt: 0px;
+ border-radius: 100px;
+ "
+ target="_blank"
+ ><b style="font-weight: 700"
+ ><b style="font-weight: 700"
+ >Go to Console</b
+ ></b
+ ></a
+ >
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+</tr>
+</tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div style="margin: 0px auto; max-width: 600px">
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 0px;
+ padding-top: 20px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 0px 20px 0px 20px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 11px;
+ line-height: 22px;
+ text-align: center;
+ color: #797e82;
+ "
+ >
+ <p style="margin: 10px 0">
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html"
+ ><span style="color: #005a8e"
+ >Yahoo Privacy Policy</span
+ ></a
+ ><span style="color: #797e82"
+ >&nbsp; &nbsp;|&nbsp; &nbsp;</span
+ ><a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://console.tld/terms-of-service-trial.html"
+ ><span style="color: #005a8e"
+ >Terms of Service</span
+ ></a
+ ><span style="color: #797e82"
+ >&nbsp; &nbsp;|&nbsp; &nbsp;</span
+ ><a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://console.tld/support"
+ ><span style="color: #005a8e">Support</span></a
+ >
+ </p>
+ <p style="margin: 10px 0">
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: inherit; text-decoration: none"
+ href="https://console.tld/tenant/trial-tenant/account/notifications"
+ >Click
+ <span style="color: #005a8e"><u>here</u></span>
+ to manage your notifications setting.</a
+ ><br />
+ </p>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </div>
+ </body>
+</html>
diff --git a/controller-server/src/test/resources/mail/welcome.html b/controller-server/src/test/resources/mail/welcome.html
new file mode 100644
index 00000000000..2e652532db8
--- /dev/null
+++ b/controller-server/src/test/resources/mail/welcome.html
@@ -0,0 +1,646 @@
+<!DOCTYPE html>
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:v="urn:schemas-microsoft-com:vml"
+ xmlns:o="urn:schemas-microsoft-com:office:office"
+>
+ <head>
+ <title></title>
+ <!--[if !mso]><!-->
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <!--<![endif]-->
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <style type="text/css">
+ #outlook a {
+ padding: 0;
+ }
+
+ body {
+ margin: 0;
+ padding: 0;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ }
+
+ table,
+ td {
+ border-collapse: collapse;
+ mso-table-lspace: 0pt;
+ mso-table-rspace: 0pt;
+ }
+
+ img {
+ border: 0;
+ height: auto;
+ line-height: 100%;
+ outline: none;
+ text-decoration: none;
+ -ms-interpolation-mode: bicubic;
+ }
+
+ p {
+ display: block;
+ margin: 13px 0;
+ }
+ </style>
+ <!--[if mso]>
+ <noscript>
+ <xml>
+ <o:OfficeDocumentSettings>
+ <o:AllowPNG />
+ <o:PixelsPerInch>96</o:PixelsPerInch>
+ </o:OfficeDocumentSettings>
+ </xml>
+ </noscript>
+ <![endif]-->
+ <!--[if lte mso 11]>
+ <style type="text/css">
+ .mj-outlook-group-fix {
+ width: 100% !important;
+ }
+ </style>
+ <![endif]-->
+ <!--[if !mso]><!-->
+ <link
+ href="https://fonts.googleapis.com/css?family=Open Sans"
+ rel="stylesheet"
+ type="text/css"
+ />
+ <style type="text/css">
+ @import url(https://fonts.googleapis.com/css?family=Open Sans);
+ </style>
+ <!--<![endif]-->
+ <style type="text/css">
+ @media only screen and (min-width: 480px) {
+ .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ }
+ </style>
+ <style media="screen and (min-width:480px)">
+ .moz-text-html .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ </style>
+ <style type="text/css">
+ [owa] .mj-column-per-100 {
+ width: 100% !important;
+ max-width: 100%;
+ }
+ </style>
+ <style type="text/css">
+ @media only screen and (max-width: 480px) {
+ table.mj-full-width-mobile {
+ width: 100% !important;
+ }
+
+ td.mj-full-width-mobile {
+ width: auto !important;
+ }
+ }
+ </style>
+ </head>
+
+ <body style="word-spacing: normal; background-color: #f2f7fa">
+ <div style="background-color: #f2f7fa">
+ <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div style="margin: 0px auto; max-width: 600px">
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 0px;
+ padding-top: 0px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 0px 0px 25px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 11px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+ <br />
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div
+ style="
+ background: #ffffff;
+ background-color: #ffffff;
+ margin: 0px auto;
+ max-width: 600px;
+ "
+ >
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="background: #ffffff; background-color: #ffffff; width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0;
+ padding-bottom: 0px;
+ padding-left: 0px;
+ padding-right: 0px;
+ padding-top: 0px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 0px;
+ padding-right: 0px;
+ padding-bottom: 40px;
+ padding-left: 0px;
+ word-break: break-word;
+ "
+ >
+ <p
+ style="
+ border-top: solid 8px #005a8e;
+ font-size: 1px;
+ margin: 0px auto;
+ width: 100%;
+ "
+ ></p>
+ <!--[if mso | IE
+ ]><table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ style="
+ border-top: solid 8px #005a8e;
+ font-size: 1px;
+ margin: 0px auto;
+ width: 600px;
+ "
+ role="presentation"
+ width="600px"
+ >
+ <tr>
+ <td style="height: 0; line-height: 0">
+ &nbsp;
+ </td>
+ </tr>
+ </table><!
+ [endif]-->
+ </td>
+ </tr>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="
+ border-collapse: collapse;
+ border-spacing: 0px;
+ "
+ >
+ <tbody>
+ <tr>
+ <td style="width: 121px">
+ <img
+ alt=""
+ height="auto"
+ src="https://data.vespa.oath.cloud/assets/vespa-cloud-logo.png"
+ style="
+ border: none;
+ display: block;
+ outline: none;
+ text-decoration: none;
+ height: auto;
+ width: 100%;
+ font-size: 13px;
+ "
+ width="121"
+ />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" bgcolor="#ffffff" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div
+ style="
+ background: #ffffff;
+ background-color: #ffffff;
+ margin: 0px auto;
+ max-width: 600px;
+ "
+ >
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="background: #ffffff; background-color: #ffffff; width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 70px;
+ padding-top: 30px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+
+<tbody>
+<tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 25px 0px 25px;
+ padding-top: 0px;
+ padding-right: 50px;
+ padding-bottom: 0px;
+ padding-left: 50px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+ <h1
+ style="
+ text-align: center;
+ color: #000000;
+ line-height: 32px;
+ "
+ >
+ Welcome to Vespa Cloud
+ </h1>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="left"
+ style="
+ font-size: 0px;
+ padding: 0px 25px 0px 25px;
+ padding-top: 0px;
+ padding-right: 50px;
+ padding-bottom: 0px;
+ padding-left: 50px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ line-height: 22px;
+ text-align: left;
+ color: #797e82;
+ "
+ >
+
+<p>
+ Welcome to Vespa Cloud! We hope you will enjoy your trial. Please reach out to us if you have any questions or feedback.
+</p>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td
+ align="center"
+ vertical-align="middle"
+ style="
+ font-size: 0px;
+ padding: 10px 25px;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ word-break: break-word;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="border-collapse: separate; line-height: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ bgcolor="#005A8E"
+ role="presentation"
+ style="
+ border: none;
+ border-radius: 100px;
+ cursor: auto;
+ mso-padding-alt: 15px 25px 15px 25px;
+ background: #005a8e;
+ "
+ valign="middle"
+ >
+ <a
+ href="https://console.tld/tenant/trial-tenant"
+ style="
+ display: inline-block;
+ background: #005a8e;
+ color: #ffffff;
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 13px;
+ font-weight: normal;
+ line-height: 120%;
+ margin: 0;
+ text-decoration: none;
+ text-transform: none;
+ padding: 15px 25px 15px 25px;
+ mso-padding-alt: 0px;
+ border-radius: 100px;
+ "
+ target="_blank"
+ ><b style="font-weight: 700"
+ ><b style="font-weight: 700"
+ >Go to Console</b
+ ></b
+ ></a
+ >
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </td>
+</tr>
+</tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><table align="center" border="0" cellpadding="0" cellspacing="0" class="" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+ <div style="margin: 0px auto; max-width: 600px">
+ <table
+ align="center"
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="width: 100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ style="
+ direction: ltr;
+ font-size: 0px;
+ padding: 20px 0px 20px 0px;
+ padding-bottom: 0px;
+ padding-top: 20px;
+ text-align: center;
+ "
+ >
+ <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+ <div
+ class="mj-column-per-100 mj-outlook-group-fix"
+ style="
+ font-size: 0px;
+ text-align: left;
+ direction: ltr;
+ display: inline-block;
+ vertical-align: top;
+ width: 100%;
+ "
+ >
+ <table
+ border="0"
+ cellpadding="0"
+ cellspacing="0"
+ role="presentation"
+ style="vertical-align: top"
+ width="100%"
+ >
+ <tbody>
+ <tr>
+ <td
+ align="center"
+ style="
+ font-size: 0px;
+ padding: 0px 20px 0px 20px;
+ padding-top: 0px;
+ padding-bottom: 0px;
+ word-break: break-word;
+ "
+ >
+ <div
+ style="
+ font-family: Open Sans, Helvetica, Arial,
+ sans-serif;
+ font-size: 11px;
+ line-height: 22px;
+ text-align: center;
+ color: #797e82;
+ "
+ >
+ <p style="margin: 10px 0">
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html"
+ ><span style="color: #005a8e"
+ >Yahoo Privacy Policy</span
+ ></a
+ ><span style="color: #797e82"
+ >&nbsp; &nbsp;|&nbsp; &nbsp;</span
+ ><a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://console.tld/terms-of-service-trial.html"
+ ><span style="color: #005a8e"
+ >Terms of Service</span
+ ></a
+ ><span style="color: #797e82"
+ >&nbsp; &nbsp;|&nbsp; &nbsp;</span
+ ><a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: #005a8e"
+ href="https://console.tld/support"
+ ><span style="color: #005a8e">Support</span></a
+ >
+ </p>
+ <p style="margin: 10px 0">
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ style="color: inherit; text-decoration: none"
+ href="https://console.tld/tenant/trial-tenant/account/notifications"
+ >Click
+ <span style="color: #005a8e"><u>here</u></span>
+ to manage your notifications setting.</a
+ ><br />
+ </p>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <!--[if mso | IE]></td></tr></table><![endif]-->
+ </div>
+ </body>
+</html>