org.codice.ddf.platform.migratable.impl.PlatformMigratableTest.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.platform.migratable.impl.PlatformMigratableTest.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.platform.migratable.impl;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.karaf.system.SystemService;
import org.codice.ddf.configuration.migration.ConfigurationMigrationManager;
import org.codice.ddf.migration.ImportMigrationContext;
import org.codice.ddf.migration.Migratable;
import org.codice.ddf.migration.MigrationException;
import org.codice.ddf.migration.MigrationMessage;
import org.codice.ddf.migration.MigrationReport;
import org.codice.ddf.migration.MigrationWarning;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

/**
 * This test creates a "mock" ddf directory structure for the system exported from (ddfExport) and
 * "mock" directory structure for the system imported into (ddfImport). These directory structures
 * are located under the created TemporaryFolder. For example, if the TemporaryFolder is
 * /private/var/folders/2j/q2gjqn4s2mv53c2q53_m9d_w0000gn/T/junit314771109111926703, one would see a
 * similar directory structure to the following after initial setup and an import:
 *
 * <p>// This is the system exported from: ./ddfExport ./ddfExport/bin ./ddfExport/bin/karaf
 * ./ddfExport/bin/karaf.bat ./ddfExport/etc ./ddfExport/etc/certs ./ddfExport/etc/certs/1
 * ./ddfExport/etc/certs/meta ./ddfExport/etc/config.properties ./ddfExport/etc/custom.properties
 * ./ddfExport/etc/ddf-wrapper.conf ./ddfExport/etc/fipsToIso.properties ./ddfExport/etc/keystores
 * ./ddfExport/etc/keystores/serverKeystore.jks ./ddfExport/etc/keystores/serverTruststore.jks
 * ./ddfExport/etc/log4j2.xml ./ddfExport/etc/org.codice.ddf.admin.applicationlist.properties
 * ./ddfExport/etc/pdp ./ddfExport/etc/pdp/ddf-metacard-attribute-ruleset.cfg
 * ./ddfExport/etc/pdp/ddf-user-attribute-ruleset.cfg ./ddfExport/etc/startup.properties
 * ./ddfExport/etc/custom.system.properties ./ddfExport/etc/users.attributes
 * ./ddfExport/etc/users.properties ./ddfExport/etc/ws-security ./ddfExport/etc/ws-security/issuer
 * ./ddfExport/etc/ws-security/issuer/encryption.properties
 * ./ddfExport/etc/ws-security/issuer/signature.properties ./ddfExport/etc/ws-security/server
 * ./ddfExport/etc/ws-security/server/encryption.properties
 * ./ddfExport/etc/ws-security/server/signature.properties ./ddfExport/Version.txt
 *
 * <p>// This is the system imported into: ./ddfImport ./ddfImport/bin ./ddfImport/bin/karaf
 * ./ddfImport/bin/karaf.bat ./ddfImport/etc ./ddfImport/etc/certs ./ddfImport/etc/certs/1
 * ./ddfImport/etc/certs/meta ./ddfImport/etc/config.properties ./ddfImport/etc/custom.properties
 * ./ddfImport/etc/ddf-wrapper.conf ./ddfImport/etc/fipsToIso.properties ./ddfImport/etc/keystores
 * ./ddfImport/etc/keystores/serverKeystore.jks ./ddfImport/etc/keystores/serverTruststore.jks
 * ./ddfImport/etc/log4j2.xml ./ddfImport/etc/org.codice.ddf.admin.applicationlist.properties
 * ./ddfImport/etc/pdp ./ddfImport/etc/pdp/ddf-metacard-attribute-ruleset.cfg
 * ./ddfImport/etc/pdp/ddf-user-attribute-ruleset.cfg ./ddfImport/etc/startup.properties
 * ./ddfImport/etc/custom.system.properties ./ddfImport/etc/users.attributes
 * ./ddfImport/etc/users.properties ./ddfImport/etc/ws-security ./ddfImport/etc/ws-security/issuer
 * ./ddfImport/etc/ws-security/issuer/encryption.properties
 * ./ddfImport/etc/ws-security/issuer/signature.properties ./ddfImport/etc/ws-security/server
 * ./ddfImport/etc/ws-security/server/encryption.properties
 * ./ddfImport/etc/ws-security/server/signature.properties ./ddfImport/Version.txt
 *
 * <p>// The backup from the imported system will look similar to this:
 * ./exported-1.0-20170810T110012.zip
 *
 * <p>// Thw exported zip from ddfExport will look similar to this: ./exported-1.0.zip
 */
@RunWith(MockitoJUnitRunner.class)
public class PlatformMigratableTest {

    private static final String KEYSTORE_SYSTEM_PROP_KEY = "javax.net.ssl.keyStore";

    private static final String TRUSTSTORE_SYSTEM_PROP_KEY = "javax.net.ssl.trustStore";

    private static final Path KEYSTORE_PATH_SYSTEM_PROP_VALUE = Paths.get("etc").resolve("keystores")
            .resolve("serverKeystore.jks");

    private static final Path TRUSTSTORE_PATH_SYSTEM_PROP_VALUE = Paths.get("etc").resolve("keystores")
            .resolve("serverTruststore.jks");

    private static final ImmutableMap<String, Path> KEYSTORES_MAP = ImmutableMap.<String, Path>builder()
            .put("keystore", Paths.get("etc", "keystores", "serverKeystore.jks"))
            .put("truststore", Paths.get("etc", "keystores", "serverTruststore.jks")).build();

    private static final Path WS_SECURITY_DIR_REL_PATH = Paths.get("etc", "ws-security");

    private static final List<Path> WS_SECURITY_FILES = ImmutableList.of(
            Paths.get("etc", "ws-security", "issuer", "encryption.properties"),
            Paths.get("etc", "ws-security", "issuer", "signature.properties"),
            Paths.get("etc", "ws-security", "server", "encryption.properties"),
            Paths.get("etc", "ws-security", "server", "signature.properties"));

    private static final Path CUSTOM_SYSTEM_PROPERTIES_PATH = Paths.get("etc", "custom.system.properties");

    private static final List<Path> REQUIRED_SYSTEM_FILES = ImmutableList.of(
            Paths.get("etc", "custom.system.properties"), Paths.get("etc", "system.properties"),
            Paths.get("etc", "startup.properties"), Paths.get("etc", "custom.properties"),
            Paths.get("etc", "config.properties"));

    private static final List<Path> UPGRADEABLE_SYSTEM_FILES = ImmutableList.of( //
            Paths.get("etc", "users.properties"), Paths.get("etc", "users.attributes"));

    private static final List<Path> OPTIONAL_SYSTEM_FILES = ImmutableList.of( //
            Paths.get("etc", "users.properties"), Paths.get("etc", "users.attributes"),
            Paths.get("etc", "pdp", "ddf-metacard-attribute-ruleset.cfg"),
            Paths.get("etc", "pdp", "ddf-user-attribute-ruleset.cfg"),
            Paths.get("etc", "org.codice.ddf.admin.applicationlist.properties"),
            Paths.get("etc", "fipsToIso.properties"), Paths.get("etc", "log4j2.xml"),
            Paths.get("etc", "certs", "meta"), Paths.get("etc", "certs", "1"), Paths.get("bin", "karaf"),
            Paths.get("bin", "karaf.bat"));

    private static final Path SERVICE_WRAPPER = Paths.get("etc", "ddf-wrapper.conf");

    private static final Path SERVICE_WRAPPER_2 = Paths.get("bin", "setenv-wrapper.conf");

    private static final String SUPPORTED_BRANDING = "test";

    private static final String SUPPORTED_VERSION = "2.0";

    private static final String IMPORTING_PRODUCT_VERSION = "3.0";

    private static final String UNSUPPORTED_VERSION = "666.0";

    private static final String DDF_EXPORTED_HOME = "ddfExport";

    private static final String DDF_IMPORTED_HOME = "ddfImport";

    private static final String DDF_EXPORTED_TAG_TEMPLATE = "exported_from_%s";

    private static final String DDF_IMPORTED_TAG = "original";

    private static final String DDF_HOME_SYSTEM_PROP_KEY = "ddf.home";

    private static final String SYSTEM_PROP_CHANGED_KEY = "test";

    private static final String SYSTEM_PROP_TO_PRESERVE_KEY = "solr.password";

    private static final String SYSTEM_PROP_NEW_KEY = "test2";

    private static final String SYSTEM_PROP_EXPORTED_VALUE = "exported";

    private static final String SYSTEM_PROP_IMPORTING_VALUE = "importing";

    private static final Path MIGRATION_PROPERTIES_PATH = Paths.get("etc", "migration.properties");

    private static final PrintStream OUT = System.out;

    @Rule
    public TemporaryFolder tempDir = new TemporaryFolder();

    private Path ddfHome;

    @Mock
    private ImportMigrationContext mockImportMigrationContext;

    @Mock
    private MigrationReport mockMigrationReport;

    @Mock
    private SystemService systemService;

    @Before
    public void setup() throws IOException {
        // The tag is written to all exported files so that we can verify the file actually
        // got copied on import (ie. read the tags in files in the imported system and
        // verify that they are the ones from the exported system).
        String tag = String.format(DDF_EXPORTED_TAG_TEMPLATE, DDF_EXPORTED_HOME);
        setup(DDF_EXPORTED_HOME, tag, SUPPORTED_VERSION);
    }

    /**
     * Verify that when an unsupported exported version is imported, an exception is recorded in the
     * migration report.
     */
    @Test
    public void testDoImportUnsupportedMigratedVersion() {
        // Setup
        when(mockImportMigrationContext.getReport()).thenReturn(mockMigrationReport);
        when(mockImportMigrationContext.getMigratableVersion()).thenReturn(Optional.of(UNSUPPORTED_VERSION));
        PlatformMigratable platformMigratable = new PlatformMigratable();

        // Perform Test
        platformMigratable.doVersionUpgradeImport(mockImportMigrationContext);

        // Verify
        verify(mockImportMigrationContext).getReport();
        verify(mockMigrationReport).record(any(MigrationException.class));
        verify(mockImportMigrationContext, times(3)).getMigratableVersion();
        verifyNoMoreInteractions(mockImportMigrationContext);
    }

    /**
     * Verify that when the system is in a default configuration, all of Platform Migratable's files
     * are successfully exported from system ddfExport and successfully imported into system
     * ddfImport.
     */
    @Test
    public void testDoExportAndDoImport() throws IOException {
        // Setup export
        Path exportDir = tempDir.getRoot().toPath().toRealPath();

        MigrationReport exportReport = doExport(exportDir);

        // Verify export
        assertThat("The export report has errors.", exportReport.hasErrors(), is(false));
        assertThat("The export report has warnings.", exportReport.hasWarnings(), is(false));
        assertThat("Export was not successful.", exportReport.wasSuccessful(), is(true));
        String exportedZipBaseName = String.format("%s-%s.dar", SUPPORTED_BRANDING, SUPPORTED_VERSION);
        Path exportedZip = exportDir.resolve(exportedZipBaseName).toRealPath();
        assertThat("Export zip does not exist.", exportedZip.toFile().exists(), is(true));
        assertThat("Exported zip is empty.", exportedZip.toFile().length(), greaterThan(0L));

        // Setup import
        setup(DDF_IMPORTED_HOME, DDF_IMPORTED_TAG, SUPPORTED_VERSION);

        PlatformMigratable iPlatformMigratable = new PlatformMigratable();
        List<Migratable> iMigratables = Arrays.asList(iPlatformMigratable);
        ConfigurationMigrationManager iConfigurationMigrationManager = new ConfigurationMigrationManager(
                iMigratables, systemService);

        MigrationReport importReport = iConfigurationMigrationManager.doImport(exportDir, this::print);

        // Verify import
        assertThat("The import report has errors.", importReport.hasErrors(), is(false));
        assertThat("The import report has warnings.", importReport.hasWarnings(), is(false));
        assertThat("Import was not successful.", importReport.wasSuccessful(), is(true));
        verifyRequiredSystemFilesImported();
        verifyOptionalSystemFilesImported();
        verifyWsSecurityFilesImported();
        verifyKeystoresImported();
        verifyServiceWrapperImported();
    }

    /**
     * Verify that when the system is in a default configuration, all of Platform Migratable's
     * relevant files are successfully imported into system for a version upgrade import.
     */
    @Test
    public void testDoVersionUpgradeImport() throws IOException {
        // Setup export
        Path exportDir = tempDir.getRoot().toPath().toRealPath();

        MigrationReport exportReport = doExport(exportDir);

        // Setup import
        setup(DDF_IMPORTED_HOME, DDF_IMPORTED_TAG, IMPORTING_PRODUCT_VERSION);

        PlatformMigratable iPlatformMigratable = spy(new PlatformMigratable());
        when(iPlatformMigratable.getVersion()).thenReturn("3.0");
        List<Migratable> iMigratables = Arrays.asList(iPlatformMigratable);
        ConfigurationMigrationManager iConfigurationMigrationManager = new ConfigurationMigrationManager(
                iMigratables, systemService);

        MigrationReport importReport = iConfigurationMigrationManager.doImport(exportDir, this::print);

        // Verify import
        assertThat("The import report has errors.", importReport.hasErrors(), is(false));
        assertThat("The import report has warnings.", importReport.hasWarnings(), is(false));
        assertThat("Import was not successful.", importReport.wasSuccessful(), is(true));
        verifyUpgradeableSystemFilesImported();
        verifyKeystoresImported();
        verifyServiceWrapperImported();
    }

    /** Verifies that when an optional file is missing the export succeeds and so does the import. */
    @Test
    public void testDoExportAndDoImportOptionalFileNotExported() throws IOException {
        // Setup export
        Path exportDir = tempDir.getRoot().toPath().toRealPath();

        // Delete etc/users.properties. This file is optional, so it will not generate
        // an error during export.
        Files.delete(ddfHome.resolve("etc").resolve("users.properties").toRealPath());
        List<Path> optionalFilesExported = OPTIONAL_SYSTEM_FILES.stream()
                .filter(p -> !p.equals(Paths.get("etc", "users.properties"))).collect(Collectors.toList());

        MigrationReport exportReport = doExport(exportDir);

        // Verify export
        assertThat("The export report has errors.", exportReport.hasErrors(), is(false));
        assertThat("The export report has warnings.", exportReport.hasWarnings(), is(false));
        assertThat("Export was not successful.", exportReport.wasSuccessful(), is(true));
        String exportedZipBaseName = String.format("%s-%s.dar", SUPPORTED_BRANDING, SUPPORTED_VERSION);
        Path exportedZip = exportDir.resolve(exportedZipBaseName).toRealPath();
        assertThat("Export zip does not exist.", exportedZip.toFile().exists(), is(true));
        assertThat("Exported zip is empty.", exportedZip.toFile().length(), greaterThan(0L));

        // Setup import
        setup(DDF_IMPORTED_HOME, DDF_IMPORTED_TAG, SUPPORTED_VERSION);

        PlatformMigratable iPlatformMigratable = new PlatformMigratable();
        List<Migratable> iMigratables = Arrays.asList(iPlatformMigratable);
        ConfigurationMigrationManager iConfigurationMigrationManager = new ConfigurationMigrationManager(
                iMigratables, systemService);

        MigrationReport importReport = iConfigurationMigrationManager.doImport(exportDir, this::print);

        // Verify import
        assertThat("The import report has errors.", importReport.hasErrors(), is(false));
        assertThat("The import report has warnings.", importReport.hasWarnings(), is(false));
        assertThat("Import was not successful.", importReport.wasSuccessful(), is(true));
        verifyRequiredSystemFilesImported();
        verifyImported(optionalFilesExported);
        verifyWsSecurityFilesImported();
        verifyKeystoresImported();
        verifyServiceWrapperImported();
    }

    /** Verifies that when an optional file is missing the version upgrade import still succeeds. */
    @Test
    public void testDoVersionUpgradeImportOptionalFileNotExported() throws IOException {
        // Setup export
        Path exportDir = tempDir.getRoot().toPath().toRealPath();

        // Delete etc/users.properties. This file is optional, so it will not generate
        // an error during export.
        Files.delete(ddfHome.resolve("etc").resolve("users.properties").toRealPath());
        List<Path> optionalFilesExported = OPTIONAL_SYSTEM_FILES.stream()
                .filter(p -> !p.equals(Paths.get("etc", "users.properties"))).collect(Collectors.toList());

        doExport(exportDir);

        // Setup import
        setup(DDF_IMPORTED_HOME, DDF_IMPORTED_TAG, IMPORTING_PRODUCT_VERSION);

        PlatformMigratable iPlatformMigratable = spy(new PlatformMigratable());
        when(iPlatformMigratable.getVersion()).thenReturn("3.0");
        List<Migratable> iMigratables = Arrays.asList(iPlatformMigratable);
        ConfigurationMigrationManager iConfigurationMigrationManager = new ConfigurationMigrationManager(
                iMigratables, systemService);

        MigrationReport importReport = iConfigurationMigrationManager.doImport(exportDir, this::print);

        // Verify import
        assertThat("The import report has errors.", importReport.hasErrors(), is(false));
        assertThat("The import report has warnings.", importReport.hasWarnings(), is(false));
        assertThat("Import was not successful.", importReport.wasSuccessful(), is(true));
        verifyKeystoresImported();
        verifyServiceWrapperImported();
    }

    /**
     * Verifies that when a required files is missing, the export fails and no zip file is created.
     */
    @Test
    public void testDoExportRequiredFileNotExported() throws IOException {
        // Setup export
        Path exportDir = tempDir.getRoot().toPath().toRealPath();

        // Delete etc/system.properties. This file is required, so we should
        // should get an export error.
        Files.delete(ddfHome.resolve("etc").resolve("custom.system.properties").toRealPath());

        MigrationReport exportReport = doExport(exportDir);

        // Verify export
        assertThat("The export report doesn't not have errors.", exportReport.hasErrors(), is(true));
        assertThat("The export report has warnings.", exportReport.hasWarnings(), is(false));
        assertThat("Export was successful.", exportReport.wasSuccessful(), is(false));
        String exportedZipBaseName = String.format("%s-%s.dar", SUPPORTED_BRANDING, SUPPORTED_VERSION);
        Path exportedZip = exportDir.resolve(exportedZipBaseName);
        assertThat(String.format("Export zip [%s] exists.", exportedZip), exportedZip.toFile().exists(), is(false));
    }

    /**
     * Verify that when the keystore and truststore are located outside of they system home directory,
     * warnings are recorded on export but not on import. Both the export and import will still be
     * successful.
     */
    @Test
    public void testDoExportAndDoImportKeystoresOutsideOfDdfHome() throws IOException {
        // Setup export
        Path exportDir = tempDir.getRoot().toPath().toRealPath();

        // For export, move keystore and truststore into tempDir and reset system properties
        for (Map.Entry<String, Path> entry : KEYSTORES_MAP.entrySet()) {
            Path source = ddfHome.resolve(entry.getValue()).toRealPath();
            Files.move(source, tempDir.getRoot().toPath().toRealPath().resolve(entry.getValue().getFileName()));

            if ("keystore".equals(entry.getKey())) {
                System.setProperty(KEYSTORE_SYSTEM_PROP_KEY,
                        tempDir.getRoot().toPath().resolve(entry.getValue().getFileName()).toRealPath().toString());
            } else if ("truststore".equals(entry.getKey())) {
                System.setProperty(TRUSTSTORE_SYSTEM_PROP_KEY,
                        tempDir.getRoot().toPath().resolve(entry.getValue().getFileName()).toRealPath().toString());
            }
        }

        MigrationReport exportReport = doExport(exportDir);

        // Verify export
        assertThat("The export report has errors.", exportReport.hasErrors(), is(false));
        assertThat("The export report does not have warnings.", exportReport.hasWarnings(), is(true));
        assertThat("Export was not successful.", exportReport.wasSuccessful(), is(true));
        String exportedZipBaseName = String.format("%s-%s.dar", SUPPORTED_BRANDING, SUPPORTED_VERSION);
        Path exportedZip = exportDir.resolve(exportedZipBaseName).toRealPath();
        assertThat(String.format("Export zip [%s] does not exist.", exportedZip), exportedZip.toFile().exists(),
                is(true));
        assertThat(String.format("Exported zip [%s] is empty.", exportedZip), exportedZip.toFile().length(),
                greaterThan(0L));

        // Setup import
        setup(DDF_IMPORTED_HOME, DDF_IMPORTED_TAG, SUPPORTED_VERSION);
        // For import, delete keystore and truststore since they are already in tempDir and reset system
        // properties.
        // Since these are outside of ddf.home, they should not be imported. A checksum should be
        // computed
        // to verify that they are the same as the exported files.
        for (Map.Entry<String, Path> entry : KEYSTORES_MAP.entrySet()) {
            Path keystore = ddfHome.resolve(entry.getValue()).toRealPath();
            Files.delete(ddfHome.resolve(keystore));

            if ("keystore".equals(entry.getKey())) {
                System.setProperty(KEYSTORE_SYSTEM_PROP_KEY,
                        tempDir.getRoot().toPath().resolve(entry.getValue().getFileName()).toRealPath().toString());
            } else if ("truststore".equals(entry.getKey())) {
                System.setProperty(TRUSTSTORE_SYSTEM_PROP_KEY,
                        tempDir.getRoot().toPath().resolve(entry.getValue().getFileName()).toRealPath().toString());
            }
        }

        PlatformMigratable iPlatformMigratable = new PlatformMigratable();
        List<Migratable> iMigratables = Arrays.asList(iPlatformMigratable);
        ConfigurationMigrationManager iConfigurationMigrationManager = new ConfigurationMigrationManager(
                iMigratables, systemService);

        MigrationReport importReport = iConfigurationMigrationManager.doImport(exportDir, this::print);

        // Verify import
        assertThat("The import report has errors.", importReport.hasErrors(), is(false));
        assertThat("The import report does have warnings.", importReport.hasWarnings(), is(false));
        assertThat("Import was not successful.", importReport.wasSuccessful(), is(true));
        verifyRequiredSystemFilesImported();
        verifyOptionalSystemFilesImported();
        verifyWsSecurityFilesImported();
        verifyServiceWrapperImported();
    }

    /**
     * Verify that when the keystore and truststore are located outside of they system home directory,
     * warnings are not recorded on import.
     */
    @Test
    public void testDoVersionUpgradeImportKeystoresOutsideOfDdfHome() throws IOException {
        // Setup export
        Path exportDir = tempDir.getRoot().toPath().toRealPath();

        // For export, move keystore and truststore into tempDir and reset system properties
        for (Map.Entry<String, Path> entry : KEYSTORES_MAP.entrySet()) {
            Path source = ddfHome.resolve(entry.getValue()).toRealPath();
            Files.move(source, tempDir.getRoot().toPath().toRealPath().resolve(entry.getValue().getFileName()));

            if ("keystore".equals(entry.getKey())) {
                System.setProperty(KEYSTORE_SYSTEM_PROP_KEY,
                        tempDir.getRoot().toPath().resolve(entry.getValue().getFileName()).toRealPath().toString());
            } else if ("truststore".equals(entry.getKey())) {
                System.setProperty(TRUSTSTORE_SYSTEM_PROP_KEY,
                        tempDir.getRoot().toPath().resolve(entry.getValue().getFileName()).toRealPath().toString());
            }
        }

        MigrationReport exportReport = doExport(exportDir);

        // Setup import
        setup(DDF_IMPORTED_HOME, DDF_IMPORTED_TAG, IMPORTING_PRODUCT_VERSION);
        // For import, delete keystore and truststore since they are already in tempDir and reset system
        // properties.
        // Since these are outside of ddf.home, they should not be imported. A checksum should be
        // computed
        // to verify that they are the same as the exported files.
        for (Map.Entry<String, Path> entry : KEYSTORES_MAP.entrySet()) {
            Path keystore = ddfHome.resolve(entry.getValue()).toRealPath();
            Files.delete(ddfHome.resolve(keystore));

            if ("keystore".equals(entry.getKey())) {
                System.setProperty(KEYSTORE_SYSTEM_PROP_KEY,
                        tempDir.getRoot().toPath().resolve(entry.getValue().getFileName()).toRealPath().toString());
            } else if ("truststore".equals(entry.getKey())) {
                System.setProperty(TRUSTSTORE_SYSTEM_PROP_KEY,
                        tempDir.getRoot().toPath().resolve(entry.getValue().getFileName()).toRealPath().toString());
            }
        }

        PlatformMigratable iPlatformMigratable = spy(new PlatformMigratable());
        when(iPlatformMigratable.getVersion()).thenReturn("3.0");
        List<Migratable> iMigratables = Arrays.asList(iPlatformMigratable);
        ConfigurationMigrationManager iConfigurationMigrationManager = new ConfigurationMigrationManager(
                iMigratables, systemService);

        MigrationReport importReport = iConfigurationMigrationManager.doImport(exportDir, this::print);

        // Verify import
        assertThat("The import report has errors.", importReport.hasErrors(), is(false));
        assertThat("The import report does have warnings.", importReport.hasWarnings(), is(false));
        assertThat("Import was not successful.", importReport.wasSuccessful(), is(true));
        verifyUpgradeableSystemFilesImported();
        verifyServiceWrapperImported();
    }

    /**
     * Verify that when the exporting system has different system property values than the importing
     * system, the exporting system's system property values win. Also, verify that system properties
     * that only exist in the importing system are preserved, as well as a hard-coded list of system
     * properties to preserve.
     */
    @Test
    public void testDoVersionUpgradeImportWithNewSystemProperties() throws IOException {
        // Setup export
        Path exportDir = tempDir.getRoot().toPath().toRealPath();

        Path exportSystemProps = ddfHome.resolve(CUSTOM_SYSTEM_PROPERTIES_PATH);
        Properties props = new Properties();

        props.put(SYSTEM_PROP_CHANGED_KEY, SYSTEM_PROP_EXPORTED_VALUE);
        props.put(SYSTEM_PROP_TO_PRESERVE_KEY, SYSTEM_PROP_EXPORTED_VALUE);

        try (final Writer writer = new BufferedWriter(new FileWriter(exportSystemProps.toFile()))) {
            props.store(writer, null);
        }

        doExport(exportDir);

        // Setup import
        setup(DDF_IMPORTED_HOME, DDF_IMPORTED_TAG, IMPORTING_PRODUCT_VERSION);

        Path importingSystemProps = ddfHome.resolve(CUSTOM_SYSTEM_PROPERTIES_PATH);
        props = new Properties();

        // Will be overwritten by exported value
        props.put(SYSTEM_PROP_CHANGED_KEY, SYSTEM_PROP_IMPORTING_VALUE);
        // Will be preserved and exported value ignored
        props.put(SYSTEM_PROP_TO_PRESERVE_KEY, SYSTEM_PROP_IMPORTING_VALUE);
        // Will be preserved since this property doesn't exist in the exported properties
        props.put(SYSTEM_PROP_NEW_KEY, SYSTEM_PROP_IMPORTING_VALUE);

        try (final Writer writer = new BufferedWriter(new FileWriter(importingSystemProps.toFile()))) {
            props.store(writer, null);
        }

        PlatformMigratable iPlatformMigratable = spy(new PlatformMigratable());
        when(iPlatformMigratable.getVersion()).thenReturn("3.0");
        List<Migratable> iMigratables = Arrays.asList(iPlatformMigratable);
        ConfigurationMigrationManager iConfigurationMigrationManager = new ConfigurationMigrationManager(
                iMigratables, systemService);

        MigrationReport importReport = iConfigurationMigrationManager.doImport(exportDir, this::print);

        // Verify import
        assertThat("The import report has errors.", importReport.hasErrors(), is(false));
        assertThat("The import report has warnings.", importReport.hasWarnings(), is(false));
        assertThat("Import was not successful.", importReport.wasSuccessful(), is(true));

        File fileOnSystem = importingSystemProps.toRealPath().toFile();
        org.apache.felix.utils.properties.Properties importedProps = new org.apache.felix.utils.properties.Properties(
                fileOnSystem);

        assertThat(importedProps.getProperty(SYSTEM_PROP_CHANGED_KEY), equalTo(SYSTEM_PROP_EXPORTED_VALUE));
        assertThat(importedProps.getProperty(SYSTEM_PROP_TO_PRESERVE_KEY), equalTo(SYSTEM_PROP_IMPORTING_VALUE));
        assertThat(importedProps.getProperty(SYSTEM_PROP_NEW_KEY), equalTo(SYSTEM_PROP_IMPORTING_VALUE));

        verifyUpgradeableSystemFilesImported();
        verifyKeystoresImported();
        verifyServiceWrapperImported();
    }

    /** Verifies that when an optional file is missing the version upgrade import still succeeds. */
    @Test
    public void testDoVersionUpgradeImportWhenSystemPropertiesToPreserveAreMissing() throws IOException {
        // Setup export
        Path exportDir = tempDir.getRoot().toPath().toRealPath();

        doExport(exportDir);

        // Setup import
        setup(DDF_IMPORTED_HOME, DDF_IMPORTED_TAG, IMPORTING_PRODUCT_VERSION);

        // Effectively remove the system.properties.to.preserve property
        Path migrationPropsPath = ddfHome.resolve(MIGRATION_PROPERTIES_PATH);
        Properties props = new Properties();
        props.setProperty("supported.versions", SUPPORTED_VERSION);
        try (final Writer writer = new BufferedWriter(new FileWriter(migrationPropsPath.toFile()))) {
            props.store(writer, null);
        }

        PlatformMigratable iPlatformMigratable = spy(new PlatformMigratable());
        when(iPlatformMigratable.getVersion()).thenReturn("3.0");
        List<Migratable> iMigratables = Arrays.asList(iPlatformMigratable);
        ConfigurationMigrationManager iConfigurationMigrationManager = new ConfigurationMigrationManager(
                iMigratables, systemService);

        MigrationReport importReport = iConfigurationMigrationManager.doImport(exportDir, this::print);

        // Verify import
        assertThat("The import report has no errors.", importReport.hasErrors(), is(false));
        assertThat("The import report has warnings.", importReport.hasWarnings(), is(false));
        assertThat("Import was not successful.", importReport.wasSuccessful(), is(true));
        verifyKeystoresImported();
        verifyServiceWrapperImported();
    }

    private MigrationReport doExport(Path exportDir) {
        PlatformMigratable ePlatformMigratable = new PlatformMigratable();
        List<Migratable> eMigratables = Arrays.asList(ePlatformMigratable);
        ConfigurationMigrationManager eConfigurationMigrationManager = new ConfigurationMigrationManager(
                eMigratables, systemService);

        // Perform export
        return eConfigurationMigrationManager.doExport(exportDir, this::print);
    }

    private void print(MigrationMessage msg) {
        if (msg instanceof MigrationException) {
            ((MigrationException) msg).printStackTrace(System.out);
        } else if (msg instanceof MigrationWarning) {
            OUT.println("Warning: " + msg);
        } else {
            OUT.println("Info: " + msg);
        }
    }

    private void setup(String ddfHomeStr, String tag, String productVersion) throws IOException {
        ddfHome = tempDir.newFolder(ddfHomeStr).toPath().toRealPath();
        Path binDir = ddfHome.resolve("bin");
        Files.createDirectory(binDir);
        System.setProperty(DDF_HOME_SYSTEM_PROP_KEY, ddfHome.toRealPath().toString());
        setupBrandingFile(SUPPORTED_BRANDING);
        setupVersionFile(productVersion);
        setupMigrationProperties(SUPPORTED_VERSION);
        setupKeystores(tag);
        for (Path path : REQUIRED_SYSTEM_FILES) {
            Path p = ddfHome.resolve(path);
            Files.createDirectories(p.getParent());
            Files.createFile(p);
            FileUtils.writeStringToFile(p.toFile(), String.format("#%s&%s", p.toRealPath().toString(), tag),
                    StandardCharsets.UTF_8);
        }
        for (Path path : OPTIONAL_SYSTEM_FILES) {
            Path p = ddfHome.resolve(path);
            Files.createDirectories(p.getParent());
            Files.createFile(p);
            FileUtils.writeStringToFile(p.toFile(), String.format("#%s&%s", p.toRealPath().toString(), tag),
                    StandardCharsets.UTF_8);
        }
        setupWsSecurity(tag);
        setupServiceWrapper(tag);
    }

    private void setupWsSecurity(String tag) throws IOException {
        Path wsSecurity = Files.createDirectories(ddfHome.resolve(WS_SECURITY_DIR_REL_PATH));
        Files.createDirectories(wsSecurity.resolve("issuer"));
        Files.createDirectories(wsSecurity.resolve("server"));

        for (Path securityFile : WS_SECURITY_FILES) {
            Path p = ddfHome.resolve(securityFile);
            Files.createFile(p);
            FileUtils.writeStringToFile(p.toFile(), String.format("#%s&%s", p.toRealPath().toString(), tag),
                    StandardCharsets.UTF_8);
        }
    }

    private void setupServiceWrapper(String tag) throws IOException {
        Path serviceWrapperConfig = ddfHome.resolve(SERVICE_WRAPPER);
        Files.createFile(serviceWrapperConfig);
        FileUtils.writeStringToFile(serviceWrapperConfig.toFile(),
                String.format("#%s&%s", serviceWrapperConfig.toRealPath().toString(), tag), StandardCharsets.UTF_8);
        serviceWrapperConfig = ddfHome.resolve(SERVICE_WRAPPER_2);
        Files.createFile(serviceWrapperConfig);
        FileUtils.writeStringToFile(serviceWrapperConfig.toFile(),
                String.format("#%s&%s", serviceWrapperConfig.toRealPath().toString(), tag), StandardCharsets.UTF_8);
    }

    private void setupKeystores(String tag) throws IOException {
        Path keystores = Files.createDirectories(ddfHome.resolve("etc").resolve("keystores"));
        Files.createDirectories(keystores);

        for (Map.Entry<String, Path> entry : KEYSTORES_MAP.entrySet()) {
            Path keystore = ddfHome.resolve(entry.getValue());
            Files.createFile(keystore);
            FileUtils.writeStringToFile(keystore.toFile(),
                    String.format("#%s&%s", keystore.toRealPath().toString(), tag), StandardCharsets.UTF_8);

            if ("keystore".equals(entry.getKey())) {
                System.setProperty(KEYSTORE_SYSTEM_PROP_KEY, KEYSTORE_PATH_SYSTEM_PROP_VALUE.toString());
            } else if ("truststore".equals(entry.getKey())) {
                System.setProperty(TRUSTSTORE_SYSTEM_PROP_KEY, TRUSTSTORE_PATH_SYSTEM_PROP_VALUE.toString());
            }
        }
    }

    private void setupMigrationProperties(String version) throws IOException {
        Files.createDirectories(ddfHome.resolve(MIGRATION_PROPERTIES_PATH).getParent());
        Files.createFile(ddfHome.resolve(MIGRATION_PROPERTIES_PATH));
        FileUtils.writeStringToFile(ddfHome.resolve(MIGRATION_PROPERTIES_PATH).toRealPath().toFile(),
                String.format("supported.versions=%s\nsystem.properties.to.preserve=%s", version,
                        SYSTEM_PROP_TO_PRESERVE_KEY),
                StandardCharsets.UTF_8);
    }

    private void setupBrandingFile(String branding) throws IOException {
        final Path brandingFile = ddfHome.resolve("Branding.txt");

        Files.createFile(brandingFile);
        FileUtils.writeStringToFile(brandingFile.toFile().getCanonicalFile(), branding, StandardCharsets.UTF_8);
    }

    private void setupVersionFile(String version) throws IOException {
        Path versionFile = ddfHome.resolve("Version.txt");
        Files.createFile(versionFile);
        FileUtils.writeStringToFile(versionFile.toFile().getCanonicalFile(), version, StandardCharsets.UTF_8);
    }

    private void verifyRequiredSystemFilesImported() throws IOException {
        for (Path sysFile : REQUIRED_SYSTEM_FILES) {
            Path p = ddfHome.resolve(sysFile).toRealPath();
            assertThat(String.format("%s does not exist.", p), p.toFile().exists(), is(true));
            assertThat(String.format("%s was not imported.", p), verifyImported(p), is(true));
        }
    }

    private void verifyOptionalSystemFilesImported() throws IOException {
        for (Path sysFile : OPTIONAL_SYSTEM_FILES) {
            Path p = ddfHome.resolve(sysFile).toRealPath();
            assertThat(String.format("%s does not exist.", p), p.toFile().exists(), is(true));
            assertThat(String.format("%s was not imported.", p), verifyImported(p), is(true));
        }
    }

    private void verifyUpgradeableSystemFilesImported() throws IOException {
        for (Path sysFile : UPGRADEABLE_SYSTEM_FILES) {
            Path p = ddfHome.resolve(sysFile).toRealPath();
            assertThat(String.format("%s does not exist.", p), p.toFile().exists(), is(true));
            assertThat(String.format("%s was not imported.", p), verifyImported(p), is(true));
        }
    }

    private void verifyImported(List<Path> files) throws IOException {
        for (Path sysFile : files) {
            Path p = ddfHome.resolve(sysFile).toRealPath();
            assertThat(String.format("%s does not exist.", p), p.toFile().exists(), is(true));
            assertThat(String.format("%s was not imported.", p), verifyImported(p), is(true));
        }
    }

    private void verifyWsSecurityFilesImported() throws IOException {
        for (Path securityFile : WS_SECURITY_FILES) {
            Path p = ddfHome.resolve(securityFile).toRealPath();
            assertThat(String.format("%s does not exist.", p), p.toFile().exists(), is(true));
            assertThat(String.format("%s was not imported.", p), verifyImported(p), is(true));
        }
    }

    private void verifyKeystoresImported() throws IOException {
        for (Map.Entry<String, Path> entry : KEYSTORES_MAP.entrySet()) {
            Path keystore = ddfHome.resolve(entry.getValue()).toRealPath();
            assertThat(String.format("%s does not exist.", keystore), keystore.toFile().exists(), is(true));
            assertThat(String.format("%s was not imported.", keystore), verifyImported(keystore), is(true));
        }
    }

    private void verifyServiceWrapperImported() throws IOException {
        Path p = ddfHome.resolve(SERVICE_WRAPPER).toRealPath();
        assertThat(String.format("%s does not exist.", p), p.toFile().exists(), is(true));
        assertThat(String.format("%s was not imported.", p), verifyImported(p), is(true));
        p = ddfHome.resolve(SERVICE_WRAPPER_2).toRealPath();
        assertThat(String.format("%s does not exist.", p), p.toFile().exists(), is(true));
        assertThat(String.format("%s was not imported.", p), verifyImported(p), is(true));
    }

    private boolean verifyImported(Path p) throws IOException {
        List<String> lines = Files.readAllLines(p, StandardCharsets.UTF_8);
        String tag = lines.get(0).split("&")[1];
        return StringUtils.equals(tag, String.format(DDF_EXPORTED_TAG_TEMPLATE, DDF_EXPORTED_HOME));
    }
}