aboutsummaryrefslogtreecommitdiffstats
path: root/controller-server/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server/src/main/java')
-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
279 files changed, 2153 insertions, 1569 deletions
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;