/** * VMware Continuent Tungsten Replicator * Copyright (C) 2015 VMware, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Initial developer(s): Robert Hodges * Contributor(s): */ package com.continuent.tungsten.replicator.backup; import java.io.File; import org.apache.log4j.Logger; import com.continuent.tungsten.replicator.util.ProcessHelper; /** * Implements a helper for running LVM commands across full life-cycle from * creating a snapshot to mounting it to unmounting and discarding. * * @author <a href="mailto:robert.hodges@continuent.com">Robert Hodges</a> * @version 1.0 */ public class LvmHelper { private static Logger logger = Logger .getLogger(LvmHelper.class); // Plugin parameters. private String volumeGroup = "VolGroup00"; private String logicalVolume = "LogGroup00"; private String logicalVolumeMount = "/"; private String snapshotName = "mysqlsnap"; private String snapshotSize = "10G"; private String snapshotMount = "/mnt/snapshots"; private String dataDir = "/database-storage-directory"; private String lvcreate = "/usr/sbin/lvcreate"; private String lvremove = "/usr/sbin/lvremove"; // Root command prefix and various generated commands in array form. private String[] lvcreateCmd; private String[] lvremoveCmd; private String[] mountCmd; private String[] umountCmd; private String[] rmCmd; File snapshotDataDirSpec; File dataDirSpec; // Helper to execute OS processes. ProcessHelper processHelper; /** * Creates a new LVM helper. */ public LvmHelper(ProcessHelper processHelper) { this.processHelper = processHelper; } public String getDataDir() { return dataDir; } public void setDataDir(String dataDir) { this.dataDir = dataDir; } public String getVolumeGroup() { return volumeGroup; } public void setVolumeGroup(String volumeGroup) { this.volumeGroup = volumeGroup; } public String getLogicalVolume() { return logicalVolume; } public void setLogicalVolume(String logicalVolume) { this.logicalVolume = logicalVolume; } public String getLogicalVolumeMount() { return logicalVolumeMount; } public void setLogicalVolumeMount(String logicalVolumeMount) { this.logicalVolumeMount = logicalVolumeMount; } public String getSnapshotName() { return snapshotName; } public void setSnapshotName(String snapshotName) { this.snapshotName = snapshotName; } public String getSnapshotSize() { return snapshotSize; } public void setSnapshotSize(String snapshotSize) { this.snapshotSize = snapshotSize; } public String getSnapshotMount() { return snapshotMount; } public void setSnapshotMount(String snapshotMount) { this.snapshotMount = snapshotMount; } public String getLvcreate() { return lvcreate; } public void setLvcreate(String lvcreate) { this.lvcreate = lvcreate; } public String getLvremove() { return lvremove; } public void setLvremove(String lvremove) { this.lvremove = lvremove; } public File getSnapshotDataDir() { return snapshotDataDirSpec; } /** * Configure the commands and locations to manage snapshots. Must be called * before executing any other command. */ public void configure() throws BackupException { // Generate and validate database storage locations. File logicalVolumeSpec = new File("/dev/" + volumeGroup + "/" + logicalVolume); File logicalVolumeMountSpec = new File(logicalVolumeMount); validateStorage("logical volume mount point", logicalVolumeMountSpec); // Generate and validate database storage locations. logicalVolumeSpec = new File("/dev/" + volumeGroup + "/" + logicalVolume); dataDirSpec = new File(dataDir); logicalVolumeMountSpec = new File(logicalVolumeMount); validateStorage("Database storage directory", dataDirSpec); validateStorage("logical volume mount point", logicalVolumeMountSpec); // Compute and validate path from mount point to data directory. String dataDirLocalSpec; if (!dataDir.startsWith(logicalVolumeMount)) { throw new BackupException( "Database data directory does not match mount point for logical volume: " + " dataDir=" + dataDir + "logicalVolumeMount=" + logicalVolumeMount); } if (dataDir.length() == logicalVolumeMount.length()) dataDirLocalSpec = ""; else dataDirLocalSpec = dataDir.substring(logicalVolumeMount.length()); // Compute snapshot volume specification and mounted position. File snapshotSpec = new File("/dev/" + volumeGroup + "/" + snapshotName); File snapshotMountSpec = new File(snapshotMount); snapshotDataDirSpec = new File(snapshotMount + "/" + dataDirLocalSpec); validateStorage("snapshot volume mount point", snapshotMountSpec); // Create lvcreate command like the following example: // lvcreate -s -n rootsnapshot /dev/VolGroup00/LogVol00 String[] cmdArray1 = {lvcreate, "-L" + snapshotSize, "-s", "-n", snapshotName, logicalVolumeSpec.getAbsolutePath()}; lvcreateCmd = cmdArray1; // Create lvremove command like the following: // lvremove /dev/VolGroup00/rootsnapshot String[] cmdArray2 = {lvremove, "-f", snapshotSpec.getAbsolutePath()}; lvremoveCmd = cmdArray2; // Create mount command as in: mount /dev/VolGroup00/rootsnapshot // /mnt/rootsnapshot String[] cmdArray3 = {"mount", snapshotSpec.getAbsolutePath(), snapshotMountSpec.getAbsolutePath()}; mountCmd = cmdArray3; // Create umount command, as in: umount /mnt/rootsnapshot String[] cmdArray4 = {"umount", snapshotMountSpec.getAbsolutePath()}; umountCmd = cmdArray4; // Create rm command used to clear storage directory for restore. // Note: We use 'sh -c' to ensure file name expansion. String[] cmdArray5 = {"sh", "-c", "rm -rf " + this.dataDirSpec.getAbsolutePath() + "/*"}; rmCmd = cmdArray5; } /** * Creates a new snapshot, which is assumed not to exist already. * * @throws BackupException If the snapshot creation fails, for example * because the snapshot already exists. */ public void createSnapshot() throws BackupException { logger.debug("Creating snapshot: " + this.snapshotName); processHelper.exec("Creating file system snapshot", lvcreateCmd); } /** * Removes an existing snapshot. * * @throws BackupException If the snapshot removal fails, for example * because the snapshot does not exist. */ public void removeSnapshot() throws BackupException { logger.debug("Removing snapshot: " + this.snapshotName); processHelper.exec("Remove the snapshot", lvremoveCmd); } /** * Mounts a snapshot and validates that the storage is readable. * * @throws BackupException Thrown if the mount command is unsuccessful for * any reason */ public void mountSnapShot() throws BackupException { logger.debug("Mounting snapshot: " + this.snapshotName); processHelper.exec("Mounting the snapshot", mountCmd); validateStorage("database storage directory from snapshot", snapshotDataDirSpec); } /** * Unmounts a snapshot. * * @throws BackupException Thrown if the unmount command is unsuccessful for * any reason */ public void unmountSnapshot() throws BackupException { logger.debug("Unmounting snapshot: " + this.snapshotName); processHelper.exec("Unmounting snapshot", umountCmd); } /** * Clears storage in preparation for restore operation. * * @throws BackupException Thrown if rm command fails. */ public void removeStorage() throws BackupException { logger.debug("Removing contents of storage directory: " + dataDirSpec.getAbsolutePath()); processHelper.exec("Removing contents of storage directory", rmCmd); } /** * Ensure that indicated storage is a readable directory. */ public void validateStorage(String name, File storageDir) throws BackupException { String suffix = " name=" + name + " location=" + storageDir; if (!storageDir.isDirectory()) throw new BackupException("Storage location is not a directory:" + suffix); else if (!storageDir.canRead()) throw new BackupException("Storage location is not readable:" + suffix); } }