diff options
Diffstat (limited to 'controller-server')
33 files changed, 149 insertions, 640 deletions
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 bab86e7bfde..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 @@ -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(), 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/application/MailVerifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/MailVerifier.java index 7d8f0d260fc..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 @@ -4,9 +4,9 @@ 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.api.integration.zone.ZoneRegistry; import com.yahoo.vespa.hosted.controller.notification.MailTemplating; import com.yahoo.vespa.hosted.controller.persistence.CuratorDb; import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; @@ -35,12 +35,12 @@ public class MailVerifier { private final Clock clock; private final MailTemplating mailTemplating; - public MailVerifier(ZoneRegistry zoneRegistry, 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.mailTemplating = new MailTemplating(zoneRegistry); + this.mailTemplating = new MailTemplating(consoleUrls); } public PendingMailVerification sendMailVerification(TenantName tenantName, String email, PendingMailVerification.MailType mailType) { 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 c9719c3dd55..d62e477a51f 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 @@ -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()); } 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 a12c341c1d1..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 @@ -5,7 +5,7 @@ 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.Bill; +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; @@ -66,7 +66,7 @@ public class BillingReportMaintainer extends ControllerMaintainer { InvoiceUpdate maintainInvoices() { var billsNeedingMaintenance = databaseClient.readBills().stream() .filter(bill -> bill.getExportedId().isPresent()) - .filter(exported -> ! exported.status().equals("ISSUED")) // TODO: This status does not yet exist. + .filter(exported -> exported.status() == BillStatus.OPEN) .toList(); var updates = new InvoiceUpdate.Counter(); 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 d0426416349..9121c139b00 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 @@ -166,7 +166,7 @@ public class CloudTrialExpirer extends ControllerMaintainer { .with("mailMessageTemplate", "cloud-trial-notification") .with("cloudTrialMessage", emailMsg) .with("mailTitle", emailSubject) - .with("consoleLink", controller().zoneRegistry().dashboardUrl(tenant.name()).toString()) + .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 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 9402092f789..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,7 +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; /** @@ -10,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 index e8fb7289f4c..1c05330702e 100644 --- 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 @@ -1,10 +1,8 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. - package com.yahoo.vespa.hosted.controller.notification; import com.yahoo.config.provision.TenantName; -import com.yahoo.restapi.UriBuilder; -import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; +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; @@ -15,7 +13,6 @@ import org.apache.velocity.runtime.resource.util.StringResourceRepository; import org.apache.velocity.tools.generic.EscapeTool; import java.io.StringWriter; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Map; @@ -43,19 +40,19 @@ public class MailTemplating { private final VelocityEngine velocity; private final EscapeTool escapeTool = new EscapeTool(); - private final URI dashboardUri; + private final ConsoleUrls consoleUrls; - public MailTemplating(ZoneRegistry zoneRegistry) { + public MailTemplating(ConsoleUrls consoleUrls) { this.velocity = createTemplateEngine(); - this.dashboardUri = zoneRegistry.dashboardUrl(); + this.consoleUrls = consoleUrls; } public String generateDefaultMailHtml(Template mailBodyTemplate, Map<String, Object> params, TenantName tenant) { var ctx = createVelocityContext(); - ctx.put("accountNotificationLink", accountNotificationsUri(tenant)); + ctx.put("accountNotificationLink", consoleUrls.tenantNotifications(tenant)); ctx.put("privacyPolicyLink", "https://legal.yahoo.com/xw/en/yahoo/privacy/topic/b2bprivacypolicy/index.html"); - ctx.put("termsOfServiceLink", consoleUri("terms-of-service-trial.html")); - ctx.put("supportLink", consoleUri("support")); + ctx.put("termsOfServiceLink", consoleUrls.termsOfService()); + ctx.put("supportLink", consoleUrls.support()); ctx.put("mailBodyTemplate", mailBodyTemplate.getId()); params.forEach(ctx::put); return render(ctx, Template.MAIL); @@ -63,9 +60,8 @@ public class MailTemplating { public String generateMailVerificationHtml(PendingMailVerification pmf) { var ctx = createVelocityContext(); - ctx.put("consoleLink", dashboardUri.getHost()); + ctx.put("verifyLink", consoleUrls.verifyEmail(pmf.getVerificationCode())); ctx.put("email", pmf.getMailAddress()); - ctx.put("code", pmf.getVerificationCode()); return render(ctx, Template.MAIL_VERIFICATION); } @@ -102,16 +98,4 @@ public class MailTemplating { }); repo.putStringResource(name, templateStr); } - - private String accountNotificationsUri(TenantName tenant) { - return new UriBuilder(dashboardUri) - .append("tenant/") - .append(tenant.value()) - .append("account/notifications") - .toString(); - } - - private String consoleUri(String path) { - return new UriBuilder(dashboardUri).append(path).toString(); - } } 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 de99d03cc82..243e1af8f35 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 @@ -2,17 +2,13 @@ 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. @@ -22,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) { @@ -35,7 +31,7 @@ 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())); }; } @@ -47,8 +43,7 @@ public class NotificationFormatter { application, instance, 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) { @@ -58,7 +53,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) { @@ -68,68 +63,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/Notifier.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/notification/Notifier.java index e3bfb8b4c56..b0b43866fae 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 @@ -2,21 +2,22 @@ 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.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.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; @@ -34,8 +35,8 @@ 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()); @@ -43,13 +44,13 @@ public class Notifier { // 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.mailTemplating = new MailTemplating(zoneRegistry); + this.consoleUrls = Objects.requireNonNull(consoleUrls); + this.formatter = new NotificationFormatter(consoleUrls); + this.mailTemplating = new MailTemplating(consoleUrls); } public void dispatch(List<Notification> notifications, NotificationSource source) { @@ -133,7 +134,7 @@ public class Notifier { .with("mailTitle", "Vespa Cloud Notifications") .with("notificationHeader", f.messagePrefix()) .with("notificationItems", items) - .with("consoleLink", notificationLink(f.notification().source())) + .with("consoleLink", notificationLink(consoleUrls, f.notification().source())) .build(); } @@ -150,24 +151,16 @@ public class Notifier { return sb.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(); + 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()); + + 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/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java index 4404063456c..d5bb47c94b0 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 @@ -282,13 +282,10 @@ public class TenantSerializer { } private TenantBilling tenantInfoBillingContactFromSlime(Inspector billingObject) { - //TODO: Remove validity check once emailVerified has been written for all tenants - var emailVerified = billingObject.field("emailVerified").valid() ? - billingObject.field("emailVerified").asBool() : true; return TenantBilling.empty() .withContact(TenantContact.from( billingObject.field("name").asString(), - new Email(billingObject.field("email").asString(), emailVerified), + new Email(billingObject.field("email").asString(), billingObject.field("emailVerified").asBool()), billingObject.field("phone").asString())) .withAddress(tenantInfoAddressFromSlime(billingObject.field("address"))); } 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 03e6125a73a..6a6c8a51d72 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 @@ -692,6 +692,7 @@ 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()); toSlime(billingContact.address(), root); // will create "address" on the parent diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java index 696f759d16e..6dc29ebe08c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java @@ -19,12 +19,14 @@ 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.BillStatus; 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.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; @@ -238,7 +240,7 @@ public class BillingApiHandler extends ThreadedHttpRequestHandler { 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); + billingController.updateBillStatus(Bill.Id.of(billId), userId, BillStatus.from(status)); return new MessageResponse("Updated status of invoice " + billId); } @@ -380,7 +382,7 @@ public class BillingApiHandler extends ThreadedHttpRequestHandler { billCursor.setString("to", bill.getEndDate().format(DATE_TIME_FORMATTER)); billCursor.setString("amount", bill.sum().toString()); - billCursor.setString("status", bill.status()); + billCursor.setString("status", bill.status().value()); var statusCursor = billCursor.setArray("statusHistory"); renderStatusHistory(statusCursor, bill.statusHistory()); @@ -392,14 +394,14 @@ public class BillingApiHandler extends ThreadedHttpRequestHandler { }); } - private void renderStatusHistory(Cursor cursor, Bill.StatusHistory statusHistory) { + private void renderStatusHistory(Cursor cursor, 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()); + c.setString("status", entry.getValue().value()); }); } 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 35d09fd541b..edec869f559 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 @@ -25,6 +25,7 @@ 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; @@ -461,7 +462,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) { @@ -476,16 +477,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()); }); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandler.java deleted file mode 100644 index 8ca2936eee7..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandler.java +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 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.pricing; - -import com.yahoo.collections.Pair; -import com.yahoo.component.annotation.Inject; -import com.yahoo.config.provision.ClusterResources; -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.Path; -import com.yahoo.restapi.SlimeJsonResponse; -import com.yahoo.slime.Cursor; -import com.yahoo.slime.Slime; -import com.yahoo.text.Text; -import com.yahoo.vespa.hosted.controller.Controller; -import com.yahoo.vespa.hosted.controller.api.integration.billing.Plan; -import com.yahoo.vespa.hosted.controller.api.integration.pricing.ApplicationResources; -import com.yahoo.vespa.hosted.controller.api.integration.pricing.PriceInformation; -import com.yahoo.vespa.hosted.controller.api.integration.pricing.Prices; -import com.yahoo.vespa.hosted.controller.api.integration.pricing.PricingController; -import com.yahoo.vespa.hosted.controller.api.integration.pricing.PricingInfo; -import com.yahoo.vespa.hosted.controller.restapi.ErrorResponses; -import com.yahoo.yolean.Exceptions; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.logging.Logger; - -import static com.yahoo.jdisc.http.HttpRequest.Method.GET; -import static com.yahoo.restapi.ErrorResponse.methodNotAllowed; -import static com.yahoo.vespa.hosted.controller.api.integration.pricing.PricingInfo.SupportLevel; -import static java.math.BigDecimal.ZERO; -import static java.nio.charset.StandardCharsets.UTF_8; - -/** - * API for calculating price information - * - * @author hmusum - */ -@SuppressWarnings("unused") // Handler -public class PricingApiHandler extends ThreadedHttpRequestHandler { - - private static final Logger log = Logger.getLogger(PricingApiHandler.class.getName()); - - private final Controller controller; - private final PricingController pricingController; - - @Inject - public PricingApiHandler(Context parentCtx, Controller controller, PricingController pricingController) { - super(parentCtx); - this.controller = controller; - this.pricingController = pricingController; - } - - @Override - public HttpResponse handle(HttpRequest request) { - if (request.getMethod() != GET) - return methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); - - try { - return handleGET(request); - } catch (IllegalArgumentException e) { - return ErrorResponse.badRequest(Exceptions.toMessageString(e)); - } catch (RuntimeException e) { - return ErrorResponses.logThrowing(request, log, e); - } - } - - private HttpResponse handleGET(HttpRequest request) { - Path path = new Path(request.getUri()); - if (path.matches("/pricing/v1/pricing")) return pricing(request); - - return ErrorResponse.notFoundError(Text.format("No '%s' handler at '%s'", request.getMethod(), - request.getUri().getPath())); - } - - private HttpResponse pricing(HttpRequest request) { - String rawQuery = request.getUri().getRawQuery(); - var priceParameters = parseQuery(rawQuery); - Prices price = calculatePrice(priceParameters); - return response(price, priceParameters); - } - - private Prices calculatePrice(PriceParameters priceParameters) { - return pricingController.priceForApplications(priceParameters.appResources, priceParameters.pricingInfo, priceParameters.plan); - } - - private PriceParameters parseQuery(String rawQuery) { - if (rawQuery == null) throw new IllegalArgumentException("No price information found in query"); - List<String> elements = Arrays.stream(URLDecoder.decode(rawQuery, UTF_8).split("&")).toList(); - return parseQuery(elements); - } - - private PriceParameters parseQuery(List<String> elements) { - var supportLevel = SupportLevel.BASIC; - var enclave = false; - var committedSpend = ZERO; - var applicationName = "default"; - var plan = controller.serviceRegistry().planRegistry().defaultPlan(); // fallback to default plan if not supplied - List<ApplicationResources> appResources = new ArrayList<>(); - - for (Pair<String, String> entry : keysAndValues(elements)) { - var value = entry.getSecond(); - switch (entry.getFirst().toLowerCase()) { - case "committedspend" -> committedSpend = new BigDecimal(value); - case "planid" -> plan = plan(value).orElseThrow(() -> new IllegalArgumentException("Unknown plan id " + value)); - case "supportlevel" -> supportLevel = SupportLevel.valueOf(value.toUpperCase()); - case "application" -> appResources.add(applicationResources(value)); - default -> throw new IllegalArgumentException("Unknown query parameter '" + entry.getFirst() + '\''); - } - } - - PricingInfo pricingInfo = new PricingInfo(supportLevel, committedSpend); - return new PriceParameters(List.of(), pricingInfo, plan, appResources); - } - - private ApplicationResources applicationResources(String appResourcesString) { - List<String> elements = List.of(appResourcesString.split(",")); - - var vcpu = ZERO; - var memoryGb = ZERO; - var diskGb = ZERO; - var gpuMemoryGb = ZERO; - var enclaveVcpu = ZERO; - var enclaveMemoryGb = ZERO; - var enclaveDiskGb = ZERO; - var enclaveGpuMemoryGb = ZERO; - - for (var element : keysAndValues(elements)) { - var value = element.getSecond(); - switch (element.getFirst().toLowerCase()) { - case "vcpu" -> vcpu = new BigDecimal(value); - case "memorygb" -> memoryGb = new BigDecimal(value); - case "diskgb" -> diskGb = new BigDecimal(value); - case "gpumemorygb" -> gpuMemoryGb = new BigDecimal(value); - - case "enclavevcpu" -> enclaveVcpu = new BigDecimal(value); - case "enclavememorygb" -> enclaveMemoryGb = new BigDecimal(value); - case "enclavediskgb" -> enclaveDiskGb = new BigDecimal(value); - case "enclavegpumemorygb" -> enclaveGpuMemoryGb = new BigDecimal(value); - - default -> throw new IllegalArgumentException("Unknown key '" + element.getFirst() + '\''); - } - } - - return new ApplicationResources(vcpu, memoryGb, diskGb, gpuMemoryGb, - enclaveVcpu, enclaveMemoryGb, enclaveDiskGb, enclaveGpuMemoryGb); - } - - private List<Pair<String, String>> keysAndValues(List<String> elements) { - return elements.stream().map(element -> { - var index = element.indexOf("="); - if (index <= 0 || index == element.length() - 1) - throw new IllegalArgumentException("Error in query parameter, expected '=' between key and value: '" + element + '\''); - return new Pair<>(element.substring(0, index), element.substring(index + 1)); - }) - .toList(); - } - - private Optional<Plan> plan(String element) { - return controller.serviceRegistry().planRegistry().plan(element); - } - - private static SlimeJsonResponse response(Prices prices, PriceParameters priceParameters) { - var slime = new Slime(); - Cursor cursor = slime.setObject(); - - var applicationsArray = cursor.setArray("applications"); - applicationPrices(applicationsArray, prices.priceInformationApplications(), priceParameters); - - var priceInfoArray = cursor.setArray("priceInfo"); - addItem(priceInfoArray, "Enclave (minimum $10k per month)", prices.totalPriceInformation().enclaveDiscount()); - addItem(priceInfoArray, "Committed spend", prices.totalPriceInformation().committedAmountDiscount()); - - setBigDecimal(cursor, "totalAmount", prices.totalPriceInformation().totalAmount()); - - return new SlimeJsonResponse(slime); - } - - private static void applicationPrices(Cursor applicationPricesArray, List<PriceInformation> applicationPrices, PriceParameters priceParameters) { - applicationPrices.forEach(priceInformation -> { - var element = applicationPricesArray.addObject(); - var array = element.setArray("priceInfo"); - addItem(array, supportLevelDescription(priceParameters), priceInformation.listPriceWithSupport()); - addItem(array, "Enclave", priceInformation.enclaveDiscount()); - addItem(array, "Volume discount", priceInformation.volumeDiscount()); - }); - } - - private static String supportLevelDescription(PriceParameters priceParameters) { - String supportLevel = priceParameters.pricingInfo.supportLevel().name(); - return supportLevel.substring(0,1).toUpperCase() + supportLevel.substring(1).toLowerCase() + " support unit price"; - } - - private static void addItem(Cursor array, String name, BigDecimal amount) { - if (amount.compareTo(BigDecimal.ZERO) != 0) { - var o = array.addObject(); - o.setString("description", name); - setBigDecimal(o, "amount", amount); - } - } - - private static void setBigDecimal(Cursor cursor, String name, BigDecimal value) { - cursor.setString(name, value.setScale(2, RoundingMode.HALF_UP).toPlainString()); - } - - private record PriceParameters(List<ClusterResources> clusterResources, PricingInfo pricingInfo, Plan plan, - List<ApplicationResources> appResources) { - - } - -} diff --git a/controller-server/src/main/resources/mail/mail-verification.vm b/controller-server/src/main/resources/mail/mail-verification.vm index 6905a292ee7..340895812ca 100644 --- a/controller-server/src/main/resources/mail/mail-verification.vm +++ b/controller-server/src/main/resources/mail/mail-verification.vm @@ -411,7 +411,7 @@ valign="middle" > <a - href="https://$consoleUrl/verify?code=$code" + href="$verifyLink" style=" display: inline-block; background: #3b9fde; @@ -471,9 +471,9 @@ <a target="_blank" rel="noopener noreferrer" - href="https://$consoleUrl/verify?code=$code" + href="$verifyLink" style="color: #3b9fde" - >https://$consoleUrl/verify?code=$code</a + >$verifyLink</a > </p> </div> diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java index a7af0916e59..4fbf39f8d8b 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/MailVerifierTest.java @@ -30,7 +30,7 @@ class MailVerifierTest { private final ControllerTester tester = new ControllerTester(SystemName.Public); private final MockMailer mailer = tester.serviceRegistry().mailer(); - private final MailVerifier mailVerifier = new MailVerifier(tester.zoneRegistry(), tester.controller().tenants(), mailer, tester.curator(), tester.clock()); + private final MailVerifier mailVerifier = new MailVerifier(tester.serviceRegistry().consoleUrls(), tester.controller().tenants(), mailer, tester.curator(), tester.clock()); private static final TenantName tenantName = TenantName.from("scoober"); private static final String mail = "unverified@bar.com"; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java index 5c6853bccdb..39d867d813d 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ServiceRegistryMock.java @@ -10,6 +10,7 @@ import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.SystemName; import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.controller.api.identifiers.ControllerVersion; +import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls; import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry; import com.yahoo.vespa.hosted.controller.api.integration.archive.ArchiveService; import com.yahoo.vespa.hosted.controller.api.integration.archive.MockArchiveService; @@ -54,6 +55,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.user.RoleMaintainer; import com.yahoo.vespa.hosted.controller.api.integration.user.RoleMaintainerMock; import com.yahoo.vespa.hosted.controller.api.integration.vcmr.MockChangeRequestClient; +import java.net.URI; import java.time.Instant; import java.util.Optional; @@ -68,6 +70,7 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg private final ControllerVersion controllerVersion; private final ZoneRegistryMock zoneRegistryMock; private final ConfigServerMock configServerMock; + private final ConsoleUrls consoleUrls = new ConsoleUrls(URI.create("https://console.tld")); private final MemoryNameService memoryNameService = new MemoryNameService(); private final MockVpcEndpointService vpcEndpointService = new MockVpcEndpointService(clock, memoryNameService); private final MockMailer mockMailer = new MockMailer(); @@ -218,6 +221,11 @@ public class ServiceRegistryMock extends AbstractComponent implements ServiceReg } @Override + public ConsoleUrls consoleUrls() { + return consoleUrls; + } + + @Override public MockResourceTagger resourceTagger() { return mockResourceTagger; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java index 70198ae35fd..c5b11fe21b0 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java @@ -2,8 +2,6 @@ package com.yahoo.vespa.hosted.controller.integration; import com.yahoo.component.AbstractComponent; -import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.AthenzDomain; import com.yahoo.config.provision.CloudAccount; import com.yahoo.config.provision.CloudName; @@ -21,7 +19,6 @@ import com.yahoo.text.Text; import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzService; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; -import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import java.net.URI; @@ -220,36 +217,6 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry } @Override - public URI dashboardUrl() { - return URI.create("https://dashboard.tld"); - } - - @Override - public URI dashboardUrl(ApplicationId id) { - return URI.create("https://dashboard.tld/" + id); - } - - @Override - public URI dashboardUrl(TenantName tenantName, ApplicationName applicationName) { - return URI.create("https://dashboard.tld/" + tenantName + "/" + applicationName); - } - - @Override - public URI dashboardUrl(TenantName tenantName) { - return URI.create("https://dashboard.tld/" + tenantName); - } - - @Override - public URI dashboardUrl(RunId id) { - return URI.create("https://dashboard.tld/" + id.application() + "/" + id.type().jobName() + "/" + id.number()); - } - - @Override - public URI supportUrl() { - return URI.create("https://help.tld"); - } - - @Override public URI apiUrl() { return URI.create("https://api.tld:4443/"); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java index a224af83401..5cb46664a75 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BillingReportMaintainerTest.java @@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.hosted.controller.ControllerTester; +import com.yahoo.vespa.hosted.controller.api.integration.billing.BillStatus; import com.yahoo.vespa.hosted.controller.api.integration.billing.InvoiceUpdate; import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistryMock; import com.yahoo.vespa.hosted.controller.tenant.BillingReference; @@ -43,25 +44,38 @@ public class BillingReportMaintainerTest { } @Test - void only_bills_with_exported_id_are_maintained() { + void only_open_bills_with_exported_id_are_maintained() { var t1 = tester.createTenant("t1"); var billingController = tester.controller().serviceRegistry().billingController(); var billingDb = tester.controller().serviceRegistry().billingDatabase(); var start = LocalDate.of(2020, 5, 23).atStartOfDay(ZoneOffset.UTC); var end = start.toLocalDate().plusDays(6).atStartOfDay(ZoneOffset.UTC); + var bill1 = billingDb.createBill(t1, start, end, "non-exported"); var bill2 = billingDb.createBill(t1, start, end, "exported"); + var bill3 = billingDb.createBill(t1, start, end, "exported-and-frozen"); + billingDb.setStatus(bill3, "foo", BillStatus.FROZEN); billingController.setPlan(t1, PlanRegistryMock.paidPlan.id(), false, true); tester.controller().serviceRegistry().billingReporter().exportBill(billingDb.readBill(bill2).get(), "FOO", cloudTenant(t1)); + tester.controller().serviceRegistry().billingReporter().exportBill(billingDb.readBill(bill3).get(), "FOO", cloudTenant(t1)); var updates = maintainer.maintainInvoices(); - assertEquals(new InvoiceUpdate(0, 0, 1), updates); + assertEquals(new InvoiceUpdate(1, 0, 0), updates); + + assertTrue(billingDb.readBill(bill1).get().getExportedId().isEmpty()); var exportedBill = billingDb.readBill(bill2).get(); assertEquals("EXT-ID-123", exportedBill.getExportedId().get()); - assertTrue(billingDb.readBill(bill1).get().getExportedId().isEmpty()); + var lineItems = exportedBill.lineItems(); + assertEquals(1, lineItems.size()); + assertEquals("maintained", lineItems.get(0).id()); + + var frozenBill = billingDb.readBill(bill3).get(); + assertEquals("EXT-ID-123", frozenBill.getExportedId().get()); + assertEquals(0, frozenBill.lineItems().size()); + } private CloudTenant cloudTenant(TenantName tenantName) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatterTest.java index 751d0123e40..875487144d9 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationFormatterTest.java @@ -6,16 +6,16 @@ import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.RegionName; -import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.zone.ZoneId; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; +import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId; import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; -import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock; import org.junit.jupiter.api.Test; +import java.net.URI; import java.time.Instant; import java.util.List; @@ -31,9 +31,8 @@ public class NotificationFormatterTest { private final ApplicationId applicationId = ApplicationId.from(tenant, application, instance); private final DeploymentId deploymentId = new DeploymentId(applicationId, ZoneId.defaultId()); private final ClusterSpec.Id cluster = new ClusterSpec.Id("content"); - private final ZoneRegistryMock zoneRegistry = new ZoneRegistryMock(SystemName.Public); - private final NotificationFormatter formatter = new NotificationFormatter(zoneRegistry); + private final NotificationFormatter formatter = new NotificationFormatter(new ConsoleUrls(URI.create("https://console.tld"))); @Test void applicationPackage() { @@ -41,7 +40,7 @@ public class NotificationFormatterTest { var content = formatter.format(notification); assertEquals("Application package", content.prettyType()); assertEquals("Application package for myapp.beta has 2 warnings", content.messagePrefix()); - assertEquals("https://dashboard.tld/scoober.myapp.beta", content.uri().toString()); + assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance/beta", content.uri()); } @Test @@ -51,7 +50,7 @@ public class NotificationFormatterTest { var content = formatter.format(notification); assertEquals("Deployment", content.prettyType()); assertEquals("production-default #1001 for myapp.beta has a warning", content.messagePrefix()); - assertEquals("https://dashboard.tld/scoober.myapp.beta/production-default/1001", content.uri().toString()); + assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance/beta/job/production-default/run/1001", content.uri()); } @Test @@ -61,7 +60,7 @@ public class NotificationFormatterTest { var content = formatter.format(notification); assertEquals("Deployment", content.prettyType()); assertEquals("production-default #1001 for myapp.beta has failed", content.messagePrefix()); - assertEquals("https://dashboard.tld/scoober.myapp.beta/production-default/1001", content.uri().toString()); + assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance/beta/job/production-default/run/1001", content.uri()); } @Test @@ -70,7 +69,7 @@ public class NotificationFormatterTest { var content = formatter.format(notification); assertEquals("Test package", content.prettyType()); assertEquals("There is a problem with tests for myapp", content.messagePrefix()); - assertEquals("https://dashboard.tld/scoober/myapp", content.uri().toString()); + assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance", content.uri()); } @Test @@ -79,7 +78,7 @@ public class NotificationFormatterTest { var content = formatter.format(notification); assertEquals("Reindex", content.prettyType()); assertEquals("Cluster content in prod.default for myapp.beta is reindexing", content.messagePrefix()); - assertEquals("https://dashboard.tld/scoober.myapp.beta?beta.prod.default=clusters%2Ccontent%3Dstatus", content.uri().toString()); + assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance/beta?beta.prod.default=clusters%2Ccontent%3Dreindexing", content.uri()); } @Test @@ -88,6 +87,6 @@ public class NotificationFormatterTest { var content = formatter.format(notification); assertEquals("Nearly feed blocked", content.prettyType()); assertEquals("Cluster content in prod.default for myapp.beta is nearly feed blocked", content.messagePrefix()); - assertEquals("https://dashboard.tld/scoober.myapp.beta?beta.prod.default=clusters%2Ccontent", content.uri().toString()); + assertEquals("https://console.tld/tenant/scoober/application/myapp/prod/instance/beta?beta.prod.default=clusters%2Ccontent", content.uri()); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java index 003a7f59eef..39c3f0f2b74 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotificationsDbTest.java @@ -14,13 +14,13 @@ import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.hosted.controller.api.application.v4.model.ClusterMetrics; import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId; +import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls; import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; import com.yahoo.vespa.hosted.controller.api.integration.configserver.ApplicationReindexing; import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer; import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext; -import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock; import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess; import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; @@ -31,6 +31,7 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantInfo; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.net.URI; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -84,7 +85,7 @@ public class NotificationsDbTest { private final MockCuratorDb curatorDb = new MockCuratorDb(SystemName.Public); private final MockMailer mailer = new MockMailer(); private final FlagSource flagSource = new InMemoryFlagSource().withBooleanFlag(PermanentFlags.NOTIFICATION_DISPATCH_FLAG.id(), true); - private final NotificationsDb notificationsDb = new NotificationsDb(clock, curatorDb, new Notifier(curatorDb, new ZoneRegistryMock(SystemName.cd), mailer, flagSource)); + private final NotificationsDb notificationsDb = new NotificationsDb(clock, curatorDb, new Notifier(curatorDb, new ConsoleUrls(URI.create("https://console.tld")), mailer, flagSource)); @Test void list_test() { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java index f64ed3740d2..55531dff72d 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/notification/NotifierTest.java @@ -9,9 +9,9 @@ import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.flags.InMemoryFlagSource; import com.yahoo.vespa.flags.PermanentFlags; +import com.yahoo.vespa.hosted.controller.api.integration.ConsoleUrls; import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; import com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer; -import com.yahoo.vespa.hosted.controller.integration.ZoneRegistryMock; import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; import com.yahoo.vespa.hosted.controller.tenant.ArchiveAccess; import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.net.URI; import java.time.Instant; import java.util.List; import java.util.Map; @@ -64,7 +65,7 @@ public class NotifierTest { void dispatch() throws IOException { var mailer = new MockMailer(); var flagSource = new InMemoryFlagSource().withBooleanFlag(PermanentFlags.NOTIFICATION_DISPATCH_FLAG.id(), true); - var notifier = new Notifier(curatorDb, new ZoneRegistryMock(SystemName.cd), mailer, flagSource); + var notifier = new Notifier(curatorDb, new ConsoleUrls(URI.create("https://console.tld")), mailer, flagSource); var notification = new Notification(Instant.now(), Notification.Type.testPackage, Notification.Level.warning, NotificationSource.from(ApplicationId.from(tenant, ApplicationName.defaultName(), InstanceName.defaultName())), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java index cb867c76f1f..9de856971c9 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java @@ -212,7 +212,7 @@ public class TenantSerializerTest { Slime slime = new Slime(); Cursor parentObject = slime.setObject(); serializer.toSlime(partialInfo, parentObject); - assertEquals("{\"info\":{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"\",\"contactEmail\":\"\",\"contactEmailVerified\":true,\"address\":{\"addressLines\":\"\",\"postalCodeOrZip\":\"\",\"city\":\"Hønefoss\",\"stateRegionProvince\":\"\",\"country\":\"\"}}}", slime.toString()); + assertEquals("{\"info\":{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"\",\"contactEmail\":\"\",\"contactEmailVerified\":false,\"address\":{\"addressLines\":\"\",\"postalCodeOrZip\":\"\",\"city\":\"Hønefoss\",\"stateRegionProvince\":\"\",\"country\":\"\"}}}", slime.toString()); } @Test diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index 6103b715744..3ada598f4f8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -77,7 +77,6 @@ public class ControllerContainerTest { <component id='com.yahoo.vespa.hosted.controller.Controller'/> <component id='com.yahoo.vespa.hosted.controller.integration.ConfigServerProxyMock'/> <component id='com.yahoo.vespa.hosted.controller.maintenance.ControllerMaintenance'/> - <component id='com.yahoo.vespa.hosted.controller.api.integration.MockPricingController'/> <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMavenRepository'/> <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockUserManagement'/> <component id='com.yahoo.vespa.hosted.controller.integration.SecretStoreMock'/> @@ -118,9 +117,6 @@ public class ControllerContainerTest { <handler id='com.yahoo.vespa.hosted.controller.restapi.changemanagement.ChangeManagementApiHandler'> <binding>http://localhost/changemanagement/v1/*</binding> </handler> - <handler id='com.yahoo.vespa.hosted.controller.restapi.pricing.PricingApiHandler'> - <binding>http://localhost/pricing/v1/*</binding> - </handler> %s </container> """.formatted(system().value(), variablePartXml()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java index 90bd2323cb3..2b01d87c903 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java @@ -78,7 +78,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { void tenant_info_profile() { var request = request("/application/v4/tenant/scoober/info/profile", GET) .roles(Set.of(Role.reader(tenantName))); - tester.assertResponse(request, "{\"contact\":{\"name\":\"\",\"email\":\"\",\"emailVerified\":true},\"tenant\":{\"company\":\"\",\"website\":\"\"}}", 200); + tester.assertResponse(request, "{\"contact\":{\"name\":\"\",\"email\":\"\",\"emailVerified\":false},\"tenant\":{\"company\":\"\",\"website\":\"\"}}", 200); var updateRequest = request("/application/v4/tenant/scoober/info/profile", PUT) .data("{\"contact\":{\"name\":\"Some Name\",\"email\":\"foo@example.com\"},\"tenant\":{\"company\":\"Scoober, Inc.\",\"website\":\"https://example.com/\"}}") @@ -100,7 +100,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { void tenant_info_billing() { var request = request("/application/v4/tenant/scoober/info/billing", GET) .roles(Set.of(Role.reader(tenantName))); - tester.assertResponse(request, "{\"contact\":{\"name\":\"\",\"email\":\"\",\"phone\":\"\"}}", 200); + tester.assertResponse(request, "{\"contact\":{\"name\":\"\",\"email\":\"\",\"emailVerified\":false,\"phone\":\"\"}}", 200); var fullAddress = "{\"addressLines\":\"addressLines\",\"postalCodeOrZip\":\"postalCodeOrZip\",\"city\":\"city\",\"stateRegionProvince\":\"stateRegionProvince\",\"country\":\"country\"}"; var fullBillingContact = "{\"contact\":{\"name\":\"name\",\"email\":\"foo@example\",\"phone\":\"phone\"},\"address\":" + fullAddress + "}"; @@ -110,7 +110,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { .roles(Set.of(Role.administrator(tenantName))); tester.assertResponse(updateRequest, "{\"message\":\"Tenant info updated\"}", 200); - tester.assertResponse(request, "{\"contact\":{\"name\":\"name\",\"email\":\"foo@example\",\"phone\":\"phone\"},\"address\":{\"addressLines\":\"addressLines\",\"postalCodeOrZip\":\"postalCodeOrZip\",\"city\":\"city\",\"stateRegionProvince\":\"stateRegionProvince\",\"country\":\"country\"}}", 200); + tester.assertResponse(request, "{\"contact\":{\"name\":\"name\",\"email\":\"foo@example\",\"emailVerified\":false,\"phone\":\"phone\"},\"address\":{\"addressLines\":\"addressLines\",\"postalCodeOrZip\":\"postalCodeOrZip\",\"city\":\"city\",\"stateRegionProvince\":\"stateRegionProvince\",\"country\":\"country\"}}", 200); } @Test @@ -133,7 +133,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { var infoRequest = request("/application/v4/tenant/scoober/info", GET) .roles(Set.of(Role.reader(tenantName))); - tester.assertResponse(infoRequest, "{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"\",\"contactEmail\":\"\",\"contactEmailVerified\":true,\"contacts\":[{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"developer@scoober\",\"emailVerified\":true}]}", 200); + tester.assertResponse(infoRequest, "{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"\",\"contactEmail\":\"\",\"contactEmailVerified\":false,\"contacts\":[{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"developer@scoober\",\"emailVerified\":true}]}", 200); String partialInfo = "{\"contactName\":\"newName\", \"contactEmail\": \"foo@example.com\", \"billingContact\":{\"name\":\"billingName\"}}"; var postPartial = @@ -150,7 +150,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { tester.assertResponse(postPartialContacts, "{\"message\":\"Tenant info updated\"}", 200); // Read back the updated info - tester.assertResponse(infoRequest, "{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"newName\",\"contactEmail\":\"foo@example.com\",\"contactEmailVerified\":false,\"billingContact\":{\"name\":\"billingName\",\"email\":\"\",\"emailVerified\":true,\"phone\":\"\"},\"contacts\":[{\"audiences\":[\"tenant\"],\"email\":\"contact1@example.com\",\"emailVerified\":false}]}", 200); + tester.assertResponse(infoRequest, "{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"newName\",\"contactEmail\":\"foo@example.com\",\"contactEmailVerified\":false,\"billingContact\":{\"name\":\"billingName\",\"email\":\"\",\"emailVerified\":false,\"phone\":\"\"},\"contacts\":[{\"audiences\":[\"tenant\"],\"email\":\"contact1@example.com\",\"emailVerified\":false}]}", 200); String fullAddress = "{\"addressLines\":\"addressLines\",\"postalCodeOrZip\":\"postalCodeOrZip\",\"city\":\"city\",\"stateRegionProvince\":\"stateRegionProvince\",\"country\":\"country\"}"; String fullBillingContact = "{\"name\":\"name\",\"email\":\"foo@example\",\"emailVerified\":false,\"phone\":\"phone\",\"address\":" + fullAddress + "}"; @@ -188,7 +188,7 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { var infoRequest = request("/application/v4/tenant/scoober/info", GET) .roles(Set.of(Role.reader(tenantName))); - tester.assertResponse(infoRequest, "{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"\",\"contactEmail\":\"\",\"contactEmailVerified\":true,\"contacts\":[{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"developer@scoober\",\"emailVerified\":true}]}", 200); + tester.assertResponse(infoRequest, "{\"name\":\"\",\"email\":\"\",\"website\":\"\",\"contactName\":\"\",\"contactEmail\":\"\",\"contactEmailVerified\":false,\"contacts\":[{\"audiences\":[\"tenant\",\"notifications\"],\"email\":\"developer@scoober\",\"emailVerified\":true}]}", 200); // name needs to be present and not blank var partialInfoMissingName = "{\"contactName\": \" \"}"; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java index 7f68fbf6909..f3147d2adde 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerTest.java @@ -4,9 +4,11 @@ package com.yahoo.vespa.hosted.controller.restapi.billing; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.hosted.controller.api.integration.billing.Bill; +import com.yahoo.vespa.hosted.controller.api.integration.billing.BillStatus; import com.yahoo.vespa.hosted.controller.api.integration.billing.CollectionMethod; import com.yahoo.vespa.hosted.controller.api.integration.billing.MockBillingController; import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; +import com.yahoo.vespa.hosted.controller.api.integration.billing.StatusHistory; import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest; @@ -26,12 +28,10 @@ import java.util.Optional; import java.util.Set; import java.util.TreeMap; -import static com.yahoo.application.container.handler.Request.Method.DELETE; import static com.yahoo.application.container.handler.Request.Method.GET; import static com.yahoo.application.container.handler.Request.Method.PATCH; import static com.yahoo.application.container.handler.Request.Method.POST; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author olaa @@ -152,14 +152,14 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { void adding_new_status() { billingController.addBill(tenant, createBill(), true); - var requestBody = "{\"status\":\"DONE\"}"; + var requestBody = "{\"status\":\"CLOSED\"}"; var request = request("/billing/v1/invoice/id-1/status", POST) .data(requestBody) .roles(financeAdmin); tester.assertResponse(request, "{\"message\":\"Updated status of invoice id-1\"}"); var bill = billingController.getBillsForTenant(tenant).get(0); - assertEquals("DONE", bill.status()); + assertEquals(BillStatus.CLOSED, bill.status()); } @Test @@ -211,7 +211,7 @@ public class BillingApiHandlerTest extends ControllerContainerCloudTest { static Bill createBill() { var start = LocalDate.of(2020, 5, 23).atStartOfDay(ZoneOffset.UTC); var end = start.toLocalDate().plusDays(6).atStartOfDay(ZoneOffset.UTC); - var statusHistory = new Bill.StatusHistory(new TreeMap<>(Map.of(start, "OPEN"))); + var statusHistory = new StatusHistory(new TreeMap<>(Map.of(start, BillStatus.OPEN))); return new Bill( Bill.Id.of("id-1"), TenantName.defaultName(), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandlerTest.java deleted file mode 100644 index f2ce0dfeef2..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/pricing/PricingApiHandlerTest.java +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 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.pricing; - -import com.yahoo.config.provision.SystemName; -import com.yahoo.vespa.hosted.controller.api.integration.pricing.PricingInfo; -import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; -import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest; -import org.junit.jupiter.api.Test; - -import java.net.URLEncoder; - -import static com.yahoo.vespa.hosted.controller.api.integration.pricing.PricingInfo.SupportLevel.BASIC; -import static com.yahoo.vespa.hosted.controller.api.integration.pricing.PricingInfo.SupportLevel.COMMERCIAL; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * @author hmusum - */ -public class PricingApiHandlerTest extends ControllerContainerCloudTest { - - @Test - void testPricingInfoBasic() { - tester().assertJsonResponse(request("/pricing/v1/pricing?supportLevel=basic&committedSpend=0"), - """ - { "applications": [ ], "priceInfo": [ ], "totalAmount": "0.00" } - """, - 200); - - var request = request("/pricing/v1/pricing?" + urlEncodedPriceInformation1App(BASIC)); - tester().assertJsonResponse(request, """ - { - "applications": [ - { - "priceInfo": [ - {"description": "Basic support unit price", "amount": "4.30"}, - {"description": "Volume discount", "amount": "-0.10"} - ] - } - ], - "priceInfo": [ - {"description": "Committed spend", "amount": "-0.20"} - ], - "totalAmount": "4.00" - } - """, - 200); - } - - @Test - void testPricingInfoBasicEnclave() { - var request = request("/pricing/v1/pricing?" + urlEncodedPriceInformation1AppEnclave(BASIC)); - tester().assertJsonResponse(request, """ - { - "applications": [ - { - "priceInfo": [ - {"description": "Basic support unit price", "amount": "4.30"}, - {"description": "Enclave", "amount": "-0.15"}, - {"description": "Volume discount", "amount": "-0.10"} - ] - } - ], - "priceInfo": [ - {"description": "Enclave (minimum $10k per month)", "amount": "10.15"}, - {"description": "Committed spend", "amount": "-0.20"} - ], - "totalAmount": "3.85" - } - """, - 200); - } - - @Test - void testPricingInfoCommercialEnclave() { - var request = request("/pricing/v1/pricing?" + urlEncodedPriceInformation1AppEnclave(COMMERCIAL)); - tester().assertJsonResponse(request, """ - { - "applications": [ - { - "priceInfo": [ - {"description": "Commercial support unit price", "amount": "13.30"}, - {"description": "Enclave", "amount": "-0.15"}, - {"description": "Volume discount", "amount": "-0.10"} - ] - } - ], - "priceInfo": [ - {"description": "Enclave (minimum $10k per month)", "amount": "1.15"}, - {"description": "Committed spend", "amount": "-0.20"} - ], - "totalAmount": "12.85" - } - """, - 200); - } - - @Test - void testPricingInfoCommercialEnclave2Apps() { - var request = request("/pricing/v1/pricing?" + urlEncodedPriceInformation2AppsEnclave(COMMERCIAL)); - tester().assertJsonResponse(request, """ - { - "applications": [ - { - "priceInfo": [ - {"description": "Commercial support unit price", "amount": "13.30"}, - {"description": "Enclave", "amount": "-0.15"}, - {"description": "Volume discount", "amount": "-0.10"} - ] - }, - { - "priceInfo": [ - {"description": "Commercial support unit price", "amount": "13.30"}, - {"description": "Enclave", "amount": "-0.15"}, - {"description": "Volume discount", "amount": "-0.10"} - ] - } - ], - "priceInfo": [ ], - "totalAmount": "26.10" - } - """, - 200); - } - - @Test - void testInvalidRequests() { - ContainerTester tester = tester(); - tester.assertJsonResponse(request("/pricing/v1/pricing"), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"No price information found in query\"}", - 400); - tester.assertJsonResponse(request("/pricing/v1/pricing?"), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Error in query parameter, expected '=' between key and value: ''\"}", - 400); - tester.assertJsonResponse(request("/pricing/v1/pricing?supportLevel=basic&committedSpend=0&resources"), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Error in query parameter, expected '=' between key and value: 'resources'\"}", - 400); - tester.assertJsonResponse(request("/pricing/v1/pricing?supportLevel=basic&committedSpend=0&resources="), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Error in query parameter, expected '=' between key and value: 'resources='\"}", - 400); - tester.assertJsonResponse(request("/pricing/v1/pricing?supportLevel=basic&committedSpend=0&key=value"), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Unknown query parameter 'key'\"}", - 400); - tester.assertJsonResponse(request("/pricing/v1/pricing?supportLevel=basic&committedSpend=0&application=key%3Dvalue"), - "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Unknown key 'key'\"}", - 400); - } - - private ContainerTester tester() { - ContainerTester tester = new ContainerTester(container, null); - assertEquals(SystemName.Public, tester.controller().system()); - return tester; - } - - /** - * 1 app, with 2 clusters (with total resources for all clusters with each having - * 1 node, with 4 vcpu, 8 Gb memory, 100 Gb disk and no GPU, - * price will be 20000 + 2000 + 200 - */ - String urlEncodedPriceInformation1App(PricingInfo.SupportLevel supportLevel) { - return "application=" + URLEncoder.encode("vcpu=4,memoryGb=8,diskGb=100,gpuMemoryGb=0", UTF_8) + - "&supportLevel=" + supportLevel.name().toLowerCase() + "&committedSpend=20"; - } - - /** - * 1 app, with 2 clusters (with total resources for all clusters with each having - * 1 node, with 4 vcpu, 8 Gb memory, 100 Gb disk and no GPU, - * price will be 20000 + 2000 + 200 - */ - String urlEncodedPriceInformation1AppEnclave(PricingInfo.SupportLevel supportLevel) { - return "application=" + URLEncoder.encode("enclaveVcpu=4,enclaveMemoryGb=8,enclaveDiskGb=100,enclaveGpuMemoryGb=0", UTF_8) + - "&supportLevel=" + supportLevel.name().toLowerCase() + "&committedSpend=20"; - } - - /** - * 2 apps, with 1 cluster (with total resources for all clusters with each having - * 1 node, with 4 vcpu, 8 Gb memory, 100 Gb disk and no GPU, - */ - String urlEncodedPriceInformation2AppsEnclave(PricingInfo.SupportLevel supportLevel) { - return "application=" + URLEncoder.encode("enclaveVcpu=4,enclaveMemoryGb=8,enclaveDiskGb=100,enclaveGpuMemoryGb=0", UTF_8) + - "&application=" + URLEncoder.encode("enclaveVcpu=4,enclaveMemoryGb=8,enclaveDiskGb=100,enclaveGpuMemoryGb=0", UTF_8) + - "&supportLevel=" + supportLevel.name().toLowerCase() + "&committedSpend=0"; - } - -} diff --git a/controller-server/src/test/resources/mail/notification.html b/controller-server/src/test/resources/mail/notification.html index c8d0037426b..2a0edeea7e1 100644 --- a/controller-server/src/test/resources/mail/notification.html +++ b/controller-server/src/test/resources/mail/notification.html @@ -488,7 +488,7 @@ valign="middle" > <a - href="https://dashboard.tld/tenant/tenant1/application/default/prod/instance" + href="https://console.tld/tenant/tenant1/application/default/prod/instance/default" style=" display: inline-block; background: #005a8e; @@ -606,7 +606,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/terms-of-service-trial.html" + href="https://console.tld/terms-of-service-trial.html" ><span style="color: #005a8e" >Terms of Service</span ></a @@ -616,7 +616,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/support" + href="https://console.tld/support" ><span style="color: #005a8e">Support</span></a > </p> @@ -625,7 +625,7 @@ target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none" - href="https://dashboard.tld/tenant/tenant1/account/notifications" + href="https://console.tld/tenant/tenant1/account/notifications" >Click <span style="color: #005a8e"><u>here</u></span> to manage your notifications setting.</a diff --git a/controller-server/src/test/resources/mail/trial-expired.html b/controller-server/src/test/resources/mail/trial-expired.html index 4e6fda61b33..bdeafe8c7d3 100644 --- a/controller-server/src/test/resources/mail/trial-expired.html +++ b/controller-server/src/test/resources/mail/trial-expired.html @@ -485,7 +485,7 @@ valign="middle" > <a - href="https://dashboard.tld/trial-tenant" + href="https://console.tld/tenant/trial-tenant" style=" display: inline-block; background: #005a8e; @@ -603,7 +603,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/terms-of-service-trial.html" + href="https://console.tld/terms-of-service-trial.html" ><span style="color: #005a8e" >Terms of Service</span ></a @@ -613,7 +613,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/support" + href="https://console.tld/support" ><span style="color: #005a8e">Support</span></a > </p> @@ -622,7 +622,7 @@ target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none" - href="https://dashboard.tld/tenant/trial-tenant/account/notifications" + href="https://console.tld/tenant/trial-tenant/account/notifications" >Click <span style="color: #005a8e"><u>here</u></span> to manage your notifications setting.</a diff --git a/controller-server/src/test/resources/mail/trial-expiring-immediately.html b/controller-server/src/test/resources/mail/trial-expiring-immediately.html index 4b16619fe9c..db89eca195a 100644 --- a/controller-server/src/test/resources/mail/trial-expiring-immediately.html +++ b/controller-server/src/test/resources/mail/trial-expiring-immediately.html @@ -485,7 +485,7 @@ valign="middle" > <a - href="https://dashboard.tld/trial-tenant" + href="https://console.tld/tenant/trial-tenant" style=" display: inline-block; background: #005a8e; @@ -603,7 +603,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/terms-of-service-trial.html" + href="https://console.tld/terms-of-service-trial.html" ><span style="color: #005a8e" >Terms of Service</span ></a @@ -613,7 +613,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/support" + href="https://console.tld/support" ><span style="color: #005a8e">Support</span></a > </p> @@ -622,7 +622,7 @@ target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none" - href="https://dashboard.tld/tenant/trial-tenant/account/notifications" + href="https://console.tld/tenant/trial-tenant/account/notifications" >Click <span style="color: #005a8e"><u>here</u></span> to manage your notifications setting.</a diff --git a/controller-server/src/test/resources/mail/trial-expiring-soon.html b/controller-server/src/test/resources/mail/trial-expiring-soon.html index b4c85173171..17c59240cc4 100644 --- a/controller-server/src/test/resources/mail/trial-expiring-soon.html +++ b/controller-server/src/test/resources/mail/trial-expiring-soon.html @@ -485,7 +485,7 @@ valign="middle" > <a - href="https://dashboard.tld/trial-tenant" + href="https://console.tld/tenant/trial-tenant" style=" display: inline-block; background: #005a8e; @@ -603,7 +603,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/terms-of-service-trial.html" + href="https://console.tld/terms-of-service-trial.html" ><span style="color: #005a8e" >Terms of Service</span ></a @@ -613,7 +613,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/support" + href="https://console.tld/support" ><span style="color: #005a8e">Support</span></a > </p> @@ -622,7 +622,7 @@ target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none" - href="https://dashboard.tld/tenant/trial-tenant/account/notifications" + href="https://console.tld/tenant/trial-tenant/account/notifications" >Click <span style="color: #005a8e"><u>here</u></span> to manage your notifications setting.</a diff --git a/controller-server/src/test/resources/mail/trial-reminder.html b/controller-server/src/test/resources/mail/trial-reminder.html index 2644b187764..fbe0d573538 100644 --- a/controller-server/src/test/resources/mail/trial-reminder.html +++ b/controller-server/src/test/resources/mail/trial-reminder.html @@ -485,7 +485,7 @@ valign="middle" > <a - href="https://dashboard.tld/trial-tenant" + href="https://console.tld/tenant/trial-tenant" style=" display: inline-block; background: #005a8e; @@ -603,7 +603,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/terms-of-service-trial.html" + href="https://console.tld/terms-of-service-trial.html" ><span style="color: #005a8e" >Terms of Service</span ></a @@ -613,7 +613,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/support" + href="https://console.tld/support" ><span style="color: #005a8e">Support</span></a > </p> @@ -622,7 +622,7 @@ target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none" - href="https://dashboard.tld/tenant/trial-tenant/account/notifications" + href="https://console.tld/tenant/trial-tenant/account/notifications" >Click <span style="color: #005a8e"><u>here</u></span> to manage your notifications setting.</a diff --git a/controller-server/src/test/resources/mail/welcome.html b/controller-server/src/test/resources/mail/welcome.html index a21a7cdf45f..2e652532db8 100644 --- a/controller-server/src/test/resources/mail/welcome.html +++ b/controller-server/src/test/resources/mail/welcome.html @@ -485,7 +485,7 @@ valign="middle" > <a - href="https://dashboard.tld/trial-tenant" + href="https://console.tld/tenant/trial-tenant" style=" display: inline-block; background: #005a8e; @@ -603,7 +603,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/terms-of-service-trial.html" + href="https://console.tld/terms-of-service-trial.html" ><span style="color: #005a8e" >Terms of Service</span ></a @@ -613,7 +613,7 @@ target="_blank" rel="noopener noreferrer" style="color: #005a8e" - href="https://dashboard.tld/support" + href="https://console.tld/support" ><span style="color: #005a8e">Support</span></a > </p> @@ -622,7 +622,7 @@ target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: none" - href="https://dashboard.tld/tenant/trial-tenant/account/notifications" + href="https://console.tld/tenant/trial-tenant/account/notifications" >Click <span style="color: #005a8e"><u>here</u></span> to manage your notifications setting.</a |