/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2013 ForgeRock AS
*/
package org.opends.server.tools.upgrade;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.tools.upgrade.FormattedNotificationCallback.*;
import static org.opends.server.tools.upgrade.UpgradeTasks.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.callback.ConfirmationCallback;
import org.opends.messages.Message;
import org.opends.server.core.LockFileManager;
import org.opends.server.tools.ClientException;
import org.opends.server.util.BuildVersion;
import org.opends.server.util.StaticUtils;
/**
* This class contains the table of upgrade tasks that need performing when
* upgrading from one version to another.
*/
public final class Upgrade
{
/**
* Upgrade's logger.
*/
private final static Logger LOG = Logger
.getLogger(UpgradeCli.class.getName());
/**
* Upgrade supports version from 2.4.5.
*/
private final static BuildVersion UPGRADESUPPORTSVERSIONFROM = BuildVersion
.valueOf("2.4.5.0000");
/**
* The success exit code value.
*/
public static final int EXIT_CODE_SUCCESS = 0;
/**
* The error exit code value.
*/
public static final int EXIT_CODE_ERROR = 1;
/**
* The exit code value that will be used if upgrade requires manual
* intervention.
*/
public static final int EXIT_CODE_MANUAL_INTERVENTION = 2;
/**
* Developers should register upgrade tasks below.
*/
private static final NavigableMap<BuildVersion, List<UpgradeTask>> TASKS =
new TreeMap<BuildVersion, List<UpgradeTask>>();
private static final List<UpgradeTask> MANDATORY_TASKS =
new LinkedList<UpgradeTask>();
static
{
// @formatter:off
register("2.5.0.6869",
modifyConfigEntry(INFO_UPGRADE_TASK_6869_SUMMARY.get(),
"(objectClass= ds-cfg-collation-matching-rule)",
"add: ds-cfg-collation",
"ds-cfg-collation: de:1.3.6.1.4.1.42.2.27.9.4.28.1",
"ds-cfg-collation: de-DE:1.3.6.1.4.1.42.2.27.9.4.28.1",
"-",
"delete: ds-cfg-collation",
"ds-cfg-collation: de:1.3.6.1.4.1.142.2.27.9.4.28.1",
"ds-cfg-collation: de-DE:1.3.6.1.4.1.142.2.27.9.4.28.1"));
register("2.5.0.7192",
modifyConfigEntry(INFO_UPGRADE_TASK_7192_SUMMARY.get(),
"(objectClass= ds-cfg-password-policy)",
"add: objectClass",
"objectClass: ds-cfg-authentication-policy",
"-",
"add: ds-cfg-java-class",
"ds-cfg-java-class: org.opends.server.core.PasswordPolicyFactory"));
register("2.5.0.7364",
modifyConfigEntry(INFO_UPGRADE_TASK_7364_SUMMARY.get(),
"(ds-cfg-java-class= org.opends.server.loggers.TextAuditLogPublisher)",
"add: objectClass",
"objectClass: ds-cfg-file-based-audit-log-publisher",
"-",
"delete: objectClass",
"objectClass: ds-cfg-file-based-access-log-publisher"));
register ("2.5.0.7466",
renameSnmpSecurityConfig(INFO_UPGRADE_TASK_7466_SUMMARY.get()));
register("2.5.0.7748",
newAttributeTypes(INFO_UPGRADE_TASK_7748_1_SUMMARY.get(),
"00-core.ldif", "etag"),
addConfigEntry(INFO_UPGRADE_TASK_7748_2_SUMMARY.get(),
"dn: cn=etag,cn=Virtual Attributes,cn=config",
"changetype: add",
"objectClass: top",
"objectClass: ds-cfg-virtual-attribute",
"objectClass: ds-cfg-entity-tag-virtual-attribute",
"cn: etag",
"ds-cfg-java-class: org.opends.server.extensions."
+ "EntityTagVirtualAttributeProvider",
"ds-cfg-enabled: true",
"ds-cfg-attribute-type: etag",
"ds-cfg-conflict-behavior: real-overrides-virtual",
"ds-cfg-checksum-algorithm: adler-32",
"ds-cfg-excluded-attribute: ds-sync-hist"));
register("2.5.0.7834",
addConfigEntry(INFO_UPGRADE_TASK_7834_SUMMARY.get(),
"dn: cn=Password Expiration Time,cn=Virtual Attributes,cn=config",
"changetype: add",
"objectClass: top",
"objectClass: ds-cfg-virtual-attribute",
"objectClass: ds-cfg-password-expiration-time-virtual-attribute",
"cn: Password Expiration Time",
"ds-cfg-java-class: org.opends.server.extensions."
+ "PasswordExpirationTimeVirtualAttributeProvider",
"ds-cfg-enabled: true",
"ds-cfg-attribute-type: ds-pwp-password-expiration-time",
"ds-cfg-conflict-behavior: virtual-overrides-real"));
register("2.5.0.7979",
modifyConfigEntry(INFO_UPGRADE_TASK_7979_SUMMARY.get(),
"(ds-cfg-java-class= org.opends.server.schema.CertificateSyntax)",
"add: objectClass",
"objectClass: ds-cfg-certificate-attribute-syntax",
"-",
"add: ds-cfg-strict-format",
"ds-cfg-strict-format: false"));
register("2.5.0.8124",
modifyConfigEntry(INFO_UPGRADE_TASK_8124_SUMMARY.get(),
"(ds-cfg-java-class= org.opends.server.schema.JPEGSyntax)",
"add: objectClass",
"objectClass: ds-cfg-jpeg-attribute-syntax",
"-",
"add: ds-cfg-strict-format",
"ds-cfg-strict-format: false"));
register("2.5.0.8133",
modifyConfigEntry(INFO_UPGRADE_TASK_8133_SUMMARY.get(),
"(ds-cfg-java-class= org.opends.server.schema.CountryStringSyntax)",
"add: objectClass",
"objectClass: ds-cfg-country-string-attribute-syntax",
"-",
"add: ds-cfg-strict-format",
"ds-cfg-strict-format: false"));
register("2.5.0.8214",
modifyConfigEntryOptional(INFO_UPGRADE_TASK_8214_SUMMARY.get(),
INFO_UPGRADE_TASK_8214_DESCRIPTION.get(),
"(ds-cfg-java-class=org.opends.server.extensions."
+ "IsMemberOfVirtualAttributeProvider)",
"add: ds-cfg-filter",
"ds-cfg-filter: (|(objectClass=person)(objectClass=groupOfNames)"
+ "(objectClass=groupOfUniqueNames)(objectClass=groupOfEntries))",
"-",
"delete: ds-cfg-filter",
"ds-cfg-filter: (objectClass=person)"));
register("2.5.0.8387",
modifyConfigEntry(INFO_UPGRADE_TASK_8387_SUMMARY.get(),
"(objectClass= ds-cfg-dictionary-password-validator)",
"add: ds-cfg-check-substrings",
"ds-cfg-check-substrings: false"));
register("2.5.0.8389",
modifyConfigEntry(INFO_UPGRADE_TASK_8389_SUMMARY.get(),
"(objectClass= ds-cfg-attribute-value-password-validator)",
"add: ds-cfg-check-substrings",
"ds-cfg-check-substrings: false"));
register("2.5.0.8487",
addConfigEntry(INFO_UPGRADE_TASK_8487_SUMMARY.get(),
"dn: cn=PBKDF2,cn=Password Storage Schemes,cn=config",
"changetype: add",
"objectClass: top",
"objectClass: ds-cfg-password-storage-scheme",
"objectClass: ds-cfg-pbkdf2-password-storage-scheme",
"cn: PBKDF2",
"ds-cfg-java-class: org.opends.server.extensions."
+ "PBKDF2PasswordStorageScheme",
"ds-cfg-enabled: true"));
register("2.5.0.8613",
addConfigFile("http-config.json"),
addConfigEntry(INFO_UPGRADE_TASK_8613_SUMMARY.get(),
"dn: cn=HTTP Connection Handler,cn=Connection Handlers,cn=config",
"changetype: add",
"objectClass: ds-cfg-http-connection-handler",
"objectClass: ds-cfg-connection-handler",
"objectClass: top",
"ds-cfg-listen-port: 8080",
"cn: HTTP Connection Handler",
"ds-cfg-max-blocked-write-time-limit: 2 minutes",
"ds-cfg-ssl-client-auth-policy: optional",
"ds-cfg-use-tcp-keep-alive: true",
"ds-cfg-max-request-size: 5 megabytes",
"ds-cfg-use-tcp-no-delay: true",
"ds-cfg-allow-tcp-reuse-address: true",
"ds-cfg-accept-backlog: 128",
"ds-cfg-authentication-required: true",
"ds-cfg-buffer-size: 4096 bytes",
"ds-cfg-config-file: config/http-config.json",
"ds-cfg-listen-address: 0.0.0.0",
"ds-cfg-java-class: " +
"org.opends.server.protocols.http.HTTPConnectionHandler",
"ds-cfg-keep-stats: true",
"ds-cfg-ssl-cert-nickname: server-cert",
"ds-cfg-use-ssl: false",
"ds-cfg-enabled: false"));
register("2.5.0.8832", addConfigEntry(INFO_UPGRADE_TASK_8832_SUMMARY.get(),
"dn: cn=File-Based HTTP Access Logger,cn=Loggers,cn=config",
"changetype: add",
"objectClass: ds-cfg-file-based-http-access-log-publisher",
"objectClass: top",
"objectClass: ds-cfg-http-access-log-publisher",
"objectClass: ds-cfg-log-publisher",
"cn: File-Based HTTP Access Logger",
"ds-cfg-java-class: " +
"org.opends.server.loggers.TextHTTPAccessLogPublisher",
"ds-cfg-asynchronous: true",
"ds-cfg-log-file: logs/http-access",
"ds-cfg-rotation-policy: " +
"cn=24 Hours Time Limit Rotation Policy," +
"cn=Log Rotation Policies,cn=config",
"ds-cfg-rotation-policy: " +
"cn=Size Limit Rotation Policy,cn=Log Rotation Policies,cn=config",
"ds-cfg-retention-policy: " +
"cn=File Count Retention Policy,cn=Log Retention Policies,cn=config",
"ds-cfg-log-file-permissions: 640",
"ds-cfg-enabled: false"));
register("2.5.0.8985",
newAttributeTypes(INFO_UPGRADE_TASK_8985_1_SUMMARY.get(),
"00-core.ldif", "emailAddress"),
modifyConfigEntry(INFO_UPGRADE_TASK_8985_2_SUMMARY.get(),
"&(ds-cfg-java-class=org.opends.server.extensions." +
"SubjectAttributeToUserAttributeCertificateMapper)" +
"(ds-cfg-subject-attribute-mapping=e:mail)",
"delete:ds-cfg-subject-attribute-mapping",
"ds-cfg-subject-attribute-mapping: e:mail",
"-",
"add:ds-cfg-subject-attribute-mapping",
"ds-cfg-subject-attribute-mapping: emailAddress:mail"));
/* See OPENDJ-992 */
register("2.5.0.9013",
regressionInVersion("2.5.0.7640",
rebuildSingleIndex(INFO_UPGRADE_TASK_9013_DESCRIPTION.get())));
/*
* All upgrades will refresh the server configuration schema and generate
* a new upgrade folder.
*/
registerLast(
copySchemaFile("02-config.ldif"),
updateConfigUpgradeFolder());
// TODO for tests.
/*register("2.5.0.8657",
rebuildAllIndexes(Message.raw("This is fake Rebuild Task")));*/
// @formatter:on
}
/**
* Returns a list containing all the tasks which are required in order to
* upgrade from {@code fromVersion} to {@code toVersion}.
*
* @param fromVersion
* The old version.
* @param toVersion
* The new version.
* @return A list containing all the tasks which are required in order to
* upgrade from {@code fromVersion} to {@code toVersion}.
*/
public static List<UpgradeTask> getUpgradeTasks(
final BuildVersion fromVersion, final BuildVersion toVersion)
{
final List<UpgradeTask> tasks = new LinkedList<UpgradeTask>();
for (final List<UpgradeTask> subList : TASKS.subMap(fromVersion, false,
toVersion, true).values())
{
tasks.addAll(subList);
}
tasks.addAll(MANDATORY_TASKS);
return tasks;
}
/**
* Upgrades the server from {@code fromVersion} to {@code toVersion} located
* in the upgrade context.
*
* @param context
* The context of the upgrade.
* @throws ClientException
* If an error occurred while performing the upgrade.
*/
public static void upgrade(final UpgradeContext context)
throws ClientException
{
// Checks and validate the version number.
isVersionCanBeUpdated(context);
// Server offline ?
checkIfServerIsRunning();
context.notify( INFO_UPGRADE_TITLE.get(), TITLE_CALLBACK);
context.notify( INFO_UPGRADE_SUMMARY.get(context.getFromVersion()
.toString(), context.getToVersion().toString()), NOTICE_CALLBACK);
context.notify( INFO_UPGRADE_GENERAL_SEE_FOR_DETAILS
.get(UpgradeUtils.getInstallationPath() + File.separator
+ UpgradeLog.UPGRADELOGNAME), NOTICE_CALLBACK);
// Checks License.
checkLicence(context);
/*
* Get the list of required upgrade tasks.
*/
final List<UpgradeTask> tasks = getUpgradeTasks(context.getFromVersion(),
context.getToVersion());
if (tasks.isEmpty())
{
changeBuildInfoVersion(context);
return;
}
/*
* Verify tasks requirements.
* Eg. if a task requires mandatory user interaction, like rebuild index,
* and the application is non-interactive then, the process
* may abort immediately.
*/
verify(context, tasks);
/*
* Asking upgrade requirements if needed to user.
*/
context.notify(INFO_UPGRADE_REQUIREMENTS.get(), TITLE_CALLBACK);
interact(context, tasks);
// Starts upgrade.
final int userResponse = context.confirmYN(
INFO_UPGRADE_DISPLAY_CONFIRM_START.get(), ConfirmationCallback.YES);
if (userResponse == ConfirmationCallback.NO)
{
throw new ClientException(EXIT_CODE_ERROR,
INFO_UPGRADE_ABORTED_BY_USER.get());
}
try
{
/*
* Perform the upgrade tasks.
*/
context.notify(INFO_UPGRADE_PERFORMING_TASKS.get(),
TITLE_CALLBACK);
perform(context, tasks);
if (UpgradeTasks.countErrors == 0)
{
// At the end, and if only if succeed, we need to change the buildInfo
// file with the version number updated.
changeBuildInfoVersion(context);
// Writes the license if needed.
LicenseFile.createFileLicenseApproved();
}
else
{
context.notify(
ERR_UPGRADE_FAILS.get(UpgradeTasks.countErrors), TITLE_CALLBACK);
}
}
catch (final ClientException e)
{
LOG.log(Level.SEVERE, e.getMessage());
context.notify( e.getMessageObject());
throw e;
}
catch (final Exception e)
{
LOG.log(Level.SEVERE, e.getMessage());
context.notify(ERR_UPGRADE_TASKS_FAIL.get(e.getMessage()));
throw new ClientException(EXIT_CODE_ERROR, Message.raw(e.getMessage()));
}
finally
{
context.notify(INFO_UPGRADE_GENERAL_SEE_FOR_DETAILS
.get(UpgradeUtils.getInstallationPath() + File.separator
+ UpgradeLog.UPGRADELOGNAME), NOTICE_CALLBACK);
}
}
private static void perform(final UpgradeContext context,
final List<UpgradeTask> tasks)
throws ClientException
{
/*
* Notify each task that the upgrade is about to be started.
*/
for (final UpgradeTask task : tasks)
{
task.start(context);
}
/*
* Perform each task.
*/
for (final UpgradeTask task : tasks)
{
task.perform(context);
}
/*
* Notify each task that the upgrade has completed. Tasks may do cleanup
* work here, such as removing files.
*/
for (final UpgradeTask task : tasks)
{
task.end(context);
}
}
private static void register(final String versionString,
final UpgradeTask... tasks)
{
final BuildVersion version = BuildVersion.valueOf(versionString);
List<UpgradeTask> taskList = TASKS.get(version);
if (taskList == null)
{
TASKS.put(version, (taskList = new LinkedList<UpgradeTask>()));
}
taskList.addAll(Arrays.asList(tasks));
}
private static void registerLast(final UpgradeTask... tasks)
{
MANDATORY_TASKS.addAll(Arrays.asList(tasks));
}
private static void interact(final UpgradeContext context,
final List<UpgradeTask> tasks)
throws ClientException
{
/*
* Let tasks interact with the user in order to obtain user's selection.
*/
for (final UpgradeTask task : tasks)
{
task.interact(context);
}
}
private static void verify(final UpgradeContext context,
final List<UpgradeTask> tasks)
throws ClientException
{
/*
* Let tasks interact with CLI to check if command line is correct.
*/
for (final UpgradeTask task : tasks)
{
task.verify(context);
}
}
/**
* The server must be offline during the upgrade.
*
* @throws ClientException
* An exception is thrown if the server is currently running.
*/
private final static void checkIfServerIsRunning() throws ClientException
{
final String lockFile = LockFileManager.getServerLockFileName();
final Message message = ERR_UPGRADE_REQUIRES_SERVER_OFFLINE.get();
try
{
final StringBuilder failureReason = new StringBuilder();
if (!LockFileManager.acquireExclusiveLock(lockFile, failureReason))
{
throw new ClientException(EXIT_CODE_ERROR, message);
}
}
catch (Exception e)
{
// Assume that if we cannot acquire the lock file the server is
// running.
throw new ClientException(EXIT_CODE_ERROR, message);
}
}
/**
* Checks if the version can be updated.
*
* @param context
* The current context which running the upgrade.
* @throws ClientException
* If an exception occurs - stops the process.
*/
private static void isVersionCanBeUpdated(final UpgradeContext context)
throws ClientException
{
if (context.getFromVersion().equals(context.getToVersion()))
{
/*
* If the server is already up to date then treat it as a successful
* upgrade so that upgrade is idempotent.
*/
final Message message = ERR_UPGRADE_VERSION_UP_TO_DATE.get(context
.getToVersion().toString());
throw new ClientException(EXIT_CODE_SUCCESS, message);
}
// The upgrade only supports version >= 2.4.5.
if (context.getFromVersion().compareTo(UPGRADESUPPORTSVERSIONFROM) < 0)
{
throw new ClientException(EXIT_CODE_ERROR,
INFO_UPGRADE_VERSION_IS_NOT_SUPPORTED.get(UPGRADESUPPORTSVERSIONFROM
.toString(), UPGRADESUPPORTSVERSIONFROM.toString()));
}
}
/**
* Writes the up to date's version number within the build info file.
*
* @param context
* The current context which running the upgrade.
* @throws ClientException
* If an exception occurs when displaying the message.
* @throws IOException
* If an exception occurs when trying to write the file.
*/
private static void changeBuildInfoVersion(final UpgradeContext context)
throws ClientException
{
FileWriter buildInfo = null;
try
{
buildInfo = new FileWriter(new File(UpgradeUtils.configDirectory,
Installation.BUILDINFO_RELATIVE_PATH), false);
// Write the new version
buildInfo.write(context.getToVersion().toString());
context.notify(INFO_UPGRADE_SUCCESSFUL.get(context
.getFromVersion().toString(), context.getToVersion().toString()),
TITLE_CALLBACK);
}
catch (IOException e)
{
throw new ClientException(EXIT_CODE_ERROR, Message.raw(e.getMessage()));
}
finally
{
StaticUtils.close(buildInfo);
}
}
private static void checkLicence(final UpgradeContext context)
throws ClientException
{
// Check license
if (!LicenseFile.isAlreadyApproved())
{
if (LicenseFile.exists())
{
context.notify(Message.raw("\n" + LicenseFile.getText()));
context.notify(INFO_LICENSE_DETAILS_CLI_LABEL.get());
if (!context.isAcceptLicenseMode())
{
final int answer;
// The force cannot answer yes to the license's question,
// which is not a task even if it requires a user interaction OR
// -an accept license mode to continue the process.
if (context.isForceUpgradeMode())
{
answer = ConfirmationCallback.NO;
context.notify(Message.raw(INFO_LICENSE_ACCEPT.get() + " "
+ INFO_PROMPT_NO_COMPLETE_ANSWER.get()));
}
else
{
answer =
context.confirmYN(INFO_LICENSE_ACCEPT.get(),
ConfirmationCallback.NO);
}
if (answer == ConfirmationCallback.NO)
{
System.exit(EXIT_CODE_SUCCESS);
}
else if (answer == ConfirmationCallback.YES)
{
LicenseFile.setApproval(true);
}
}
else
{
// We automatically accept the license with this option.
context.notify(Message.raw(INFO_LICENSE_ACCEPT.get() + " "
+ INFO_PROMPT_YES_COMPLETE_ANSWER.get()));
LicenseFile.setApproval(true);
}
}
}
}
// Prevent instantiation.
private Upgrade()
{
// Nothing to do.
}
}