public PrepareResult prepare(HostValidator hostValidator, DeployLogger logger, PrepareParams params,
                                 Optional<ApplicationVersions> activeApplicationVersions, Instant now, File serverDbSessionDir,
                                 ApplicationPackage applicationPackage, SessionZooKeeperClient sessionZooKeeperClient) {
        ApplicationId applicationId = params.getApplicationId();
        Preparation preparation = new Preparation(hostValidator, logger, params, activeApplicationVersions,
                                                  TenantRepository.getTenantPath(applicationId.tenant()),
                                                  serverDbSessionDir, applicationPackage, sessionZooKeeperClient,
                                                  onnxModelCost, endpointCertificateSecretStores);
        preparation.preprocess();
        try {
            AllocatedHosts allocatedHosts = preparation.buildModels(now);
            preparation.makeResult(allocatedHosts);
            if ( ! params.isDryRun()) {
                FileReference fileReference = preparation.triggerDistributionOfApplicationPackage();
                preparation.writeStateZK(fileReference);
                preparation.writeEndpointCertificateMetadataZK();
                preparation.writeContainerEndpointsZK();
            }
            log.log(Level.FINE, () -> "time used " + params.getTimeoutBudget().timesUsed() + " : " + applicationId);
            return preparation.result();
        }
        catch (IllegalArgumentException e) {
            if (e instanceof InvalidApplicationException)
                throw e;
            throw new InvalidApplicationException("Invalid application package", e);
        }
    }
@Test
    public void require_that_container_endpoints_are_written_and_used() throws Exception {
        var modelFactory = new TestModelFactory(version123);
        preparer = createPreparer(new ModelFactoryRegistry(List.of(modelFactory)), HostProvisionerProvider.empty());
        var endpoints = "[\n" +
                        "  {\n" +
                        "    \"clusterId\": \"foo\",\n" +
                        "    \"names\": [\n" +
                        "      \"foo.app1.tenant1.global.vespa.example.com\",\n" +
                        "      \"rotation-042.vespa.global.routing\"\n" +
                        "    ],\n" +
                        "    \"scope\": \"global\", \n" +
                        "    \"routingMethod\": \"shared\"\n" +
                        "  },\n" +
                        "  {\n" +
                        "    \"clusterId\": \"bar\",\n" +
                        "    \"names\": [\n" +
                        "      \"bar.app1.tenant1.global.vespa.example.com\",\n" +
                        "      \"rotation-043.vespa.global.routing\"\n" +
                        "    ],\n" +
                        "    \"scope\": \"global\",\n" +
                        "    \"routingMethod\": \"sharedLayer4\"\n" +
                        "  }\n" +
                        "]";
        var applicationId = applicationId("test");
        var params = new PrepareParams.Builder().applicationId(applicationId)
                                                .containerEndpoints(endpoints)
                                                .build();
        prepare(new File("src/test/resources/deploy/hosted-app"), params);

        var expected = List.of(new ContainerEndpoint("foo",
                                                     ApplicationClusterEndpoint.Scope.global,
                                                     List.of("foo.app1.tenant1.global.vespa.example.com",
                                                             "rotation-042.vespa.global.routing"),
                                                     OptionalInt.empty(),
                                                     ApplicationClusterEndpoint.RoutingMethod.shared),
                               new ContainerEndpoint("bar",
                                                     ApplicationClusterEndpoint.Scope.global,
                                                     List.of("bar.app1.tenant1.global.vespa.example.com",
                                                             "rotation-043.vespa.global.routing"),
                                                     OptionalInt.empty(),
                                                     ApplicationClusterEndpoint.RoutingMethod.sharedLayer4));
        assertEquals(expected, readContainerEndpoints(applicationId));


        var modelContext = modelFactory.getModelContext();
        var containerEndpointsFromModel = modelContext.properties().endpoints();
        assertEquals(Set.copyOf(expected), containerEndpointsFromModel);

        // Preparing with null container endpoints keeps old value. This is what happens when deployments happen from
        // an existing session (e.g. internal redeployment).
        params = new PrepareParams.Builder().applicationId(applicationId).build();
        prepare(new File("src/test/resources/deploy/hosted-app"), params);
        assertEquals(expected, readContainerEndpoints(applicationId));

        // Preparing with empty container endpoints clears endpoints
        params = new PrepareParams.Builder().applicationId(applicationId).containerEndpoints("[]").build();
        prepare(new File("src/test/resources/deploy/hosted-app"), params);
        assertEquals(List.of(), readContainerEndpoints(applicationId));
    }