-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[14] Configuration backup/restore in online mode
- Loading branch information
Showing
4 changed files
with
293 additions
and
1 deletion.
There are no files selected for viewing
122 changes: 122 additions & 0 deletions
122
...s/src/main/java/org/wildfly/extras/creaper/commands/foundation/online/SnapshotBackup.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package org.wildfly.extras.creaper.commands.foundation.online; | ||
|
||
import java.io.IOException; | ||
import java.util.concurrent.TimeoutException; | ||
|
||
import org.jboss.logging.Logger; | ||
import org.wildfly.extras.creaper.core.CommandFailedException; | ||
import org.wildfly.extras.creaper.core.online.CliException; | ||
import org.wildfly.extras.creaper.core.online.ModelNodeResult; | ||
import org.wildfly.extras.creaper.core.online.OnlineCommand; | ||
import org.wildfly.extras.creaper.core.online.OnlineCommandContext; | ||
import org.wildfly.extras.creaper.core.online.operations.admin.ReloadToSnapshot; | ||
|
||
import com.google.common.base.Strings; | ||
|
||
/** | ||
* Provides a pair of online commands to backup and then restore application | ||
* server configuration while the server is running. Backup consists of taking a | ||
* snapshot, restore then means reloading the server from the snapshot. | ||
* | ||
* <p> | ||
* The {@code backup} command must be applied before {@code restore}, | ||
* {@code backup} can be applied once, and then {@code restore} can be applied | ||
* many times. If any one of these rules is violated, an exception is thrown. | ||
* For special circumstances, when the backup that was already acquired is no | ||
* longer needed and is not going to be restored, a {@code destroy} command is | ||
* provided. If there was no backup acquired, the destroy command does nothing. | ||
* </p> | ||
*/ | ||
public final class SnapshotBackup { | ||
private static final Logger log = Logger.getLogger(SnapshotBackup.class); | ||
|
||
private String snapshotName; // null <=> backup wasn't acquired, can't restore | ||
|
||
private final OnlineCommand backupPart = new OnlineCommand() { | ||
@Override | ||
public void apply(OnlineCommandContext occ) throws CommandFailedException, IOException, CliException { | ||
if (SnapshotBackup.this.snapshotName != null) { | ||
throw new CommandFailedException("Snapshot was already taken: " | ||
+ SnapshotBackup.this.snapshotName); | ||
} | ||
ModelNodeResult modelNodeResult = occ.client.execute(":take-snapshot"); | ||
// Ensure operation succeeded and value is returned | ||
modelNodeResult.assertDefinedValue("Taking snapshot failed."); | ||
String snaphotAbsolutePath = modelNodeResult.stringValue(); | ||
SnapshotBackup.this.snapshotName = getSnapshotName(snaphotAbsolutePath); | ||
} | ||
|
||
/** | ||
* Extract file name of absolute path to snapshot | ||
* | ||
* <p> | ||
* Also handles situation when server and client are running on different platforms. | ||
* </p> | ||
* | ||
* @param snapshotAbsolutePath Absolute path to snapshot | ||
* @return name of snapshot or empty string if snapshotAbsolutePath is null or empty. | ||
*/ | ||
private String getSnapshotName(String snapshotAbsolutePath) { | ||
if (Strings.isNullOrEmpty(snapshotAbsolutePath)) { | ||
return ""; | ||
} | ||
// Windows file names can't contain "/", so if there's a "/" in the full path, it's not Windows. | ||
String fileSeparator = "/"; | ||
if (!snapshotAbsolutePath.contains("/")) { | ||
// absolute path is from windows environment | ||
fileSeparator = "\\"; | ||
} | ||
return snapshotAbsolutePath.substring(snapshotAbsolutePath.lastIndexOf(fileSeparator) + 1); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "SnapshotBackup.backup"; | ||
} | ||
}; | ||
|
||
private final OnlineCommand restorePart = new OnlineCommand() { | ||
@Override | ||
public void apply(OnlineCommandContext occ) throws CommandFailedException, IOException, InterruptedException, | ||
TimeoutException { | ||
if (SnapshotBackup.this.snapshotName == null) { | ||
throw new CommandFailedException("There's no snapshot to restore"); | ||
} | ||
ReloadToSnapshot reloadToSnapshot = new ReloadToSnapshot(occ.client, SnapshotBackup.this.snapshotName); | ||
reloadToSnapshot.perform(); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "SnapshotBackup.restore"; | ||
} | ||
}; | ||
|
||
private final OnlineCommand destroyPart = new OnlineCommand() { | ||
@Override | ||
public void apply(OnlineCommandContext occ) throws CliException, IOException { | ||
if (SnapshotBackup.this.snapshotName == null) { | ||
return; | ||
} | ||
occ.client.executeCli(":delete-snapshot(name=" + SnapshotBackup.this.snapshotName + ")"); | ||
SnapshotBackup.this.snapshotName = null; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "SnapshotBackup.destroy"; | ||
} | ||
}; | ||
|
||
public OnlineCommand backup() { | ||
return backupPart; | ||
} | ||
|
||
public OnlineCommand restore() { | ||
return restorePart; | ||
} | ||
|
||
public OnlineCommand destroy() { | ||
return destroyPart; | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
testsuite/common/src/main/java/org/wildfly/extras/creaper/test/WildFly11Tests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package org.wildfly.extras.creaper.test; | ||
|
||
|
||
/** | ||
* Category marker for tests that require WildFly 11 and can't run with prior | ||
* versions of server. | ||
* | ||
* <p> | ||
* WildFly11Tests category is meant as a "subset" of WildFlyTests. If test is in | ||
* category WildFly11Tests, it also has to be in category WildFlyTests. | ||
* </p> | ||
*/ | ||
public interface WildFly11Tests { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
...c/test/java/org/wildfly/extras/creaper/commands/foundation/online/SnapshotBackupTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package org.wildfly.extras.creaper.commands.foundation.online; | ||
|
||
import static org.junit.Assert.assertFalse; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
import java.io.IOException; | ||
|
||
import org.jboss.arquillian.junit.Arquillian; | ||
import org.junit.After; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.junit.experimental.categories.Category; | ||
import org.junit.runner.RunWith; | ||
import org.wildfly.extras.creaper.commands.logging.AddConsoleLogHandler; | ||
import org.wildfly.extras.creaper.core.CommandFailedException; | ||
import org.wildfly.extras.creaper.core.ManagementClient; | ||
import org.wildfly.extras.creaper.core.online.OnlineManagementClient; | ||
import org.wildfly.extras.creaper.core.online.OnlineOptions; | ||
import org.wildfly.extras.creaper.core.online.operations.Address; | ||
import org.wildfly.extras.creaper.core.online.operations.OperationException; | ||
import org.wildfly.extras.creaper.core.online.operations.Operations; | ||
import org.wildfly.extras.creaper.test.WildFly11Tests; | ||
import org.wildfly.extras.creaper.test.WildFlyTests; | ||
import org.xml.sax.SAXException; | ||
|
||
@Category({WildFlyTests.class, WildFly11Tests.class}) | ||
@RunWith(Arquillian.class) | ||
public class SnapshotBackupTest { | ||
|
||
private static final String TEST_RESOURCE_NAME = "TEST-CONSOLE-HANDLER"; | ||
private static final Address TEST_RESOURCE_ADDRESS = Address.subsystem("logging") | ||
.and("console-handler", TEST_RESOURCE_NAME); | ||
|
||
private static OnlineManagementClient client; | ||
private static Operations ops; | ||
|
||
@Before | ||
public void connect() throws IOException { | ||
client = ManagementClient.online(OnlineOptions | ||
.standalone() | ||
.localDefault() | ||
.build()); | ||
ops = new Operations(client); | ||
} | ||
|
||
@After | ||
public void cleanup() throws IOException { | ||
if (client != null) { | ||
client.close(); | ||
} | ||
} | ||
|
||
@Test(expected = CommandFailedException.class) | ||
public void restoreBeforeBackup() throws CommandFailedException, IOException { | ||
SnapshotBackup snapshotBackup = new SnapshotBackup(); | ||
|
||
client.apply(snapshotBackup.restore()); // fail | ||
} | ||
|
||
@Test(expected = CommandFailedException.class) | ||
public void backupTwice() throws CommandFailedException, IOException, SAXException { | ||
SnapshotBackup snapshotBackup = new SnapshotBackup(); | ||
|
||
client.apply(snapshotBackup.backup()); | ||
client.apply(snapshotBackup.backup()); // fail | ||
|
||
} | ||
|
||
@Test | ||
public void restoreTwice() throws CommandFailedException, IOException, SAXException, InterruptedException { | ||
SnapshotBackup snapshotBackup = new SnapshotBackup(); | ||
|
||
client.apply(snapshotBackup.backup()); | ||
|
||
client.apply(snapshotBackup.restore()); | ||
client.apply(snapshotBackup.restore()); | ||
} | ||
|
||
@Test | ||
public void backupRestore() throws CommandFailedException, IOException, OperationException, InterruptedException { | ||
SnapshotBackup snapshotBackup = new SnapshotBackup(); | ||
|
||
client.apply(snapshotBackup.backup()); | ||
|
||
assertFalse("Resource should not exists", ops.exists(TEST_RESOURCE_ADDRESS)); | ||
client.apply(new AddConsoleLogHandler.Builder(TEST_RESOURCE_NAME).build()); | ||
assertTrue("Resource should exists", ops.exists(TEST_RESOURCE_ADDRESS)); | ||
|
||
client.apply(snapshotBackup.restore()); | ||
assertFalse("Resource should not exists", ops.exists(TEST_RESOURCE_ADDRESS)); | ||
} | ||
|
||
} |