Package com.cybozu.vmbkp.control

Source Code of com.cybozu.vmbkp.control.VmArchiveManager

/**
* @file
* @brief VmArchiveManager, LazyTask, LazyTaskId
*
* Copyright (C) 2009,2010 Cybozu Inc., all rights reserved.
*
* @author Takashi HOSHINO <hoshino@labs.cybozu.co.jp>
*/
package com.cybozu.vmbkp.control;

import java.util.Calendar;
import java.util.List;
import java.util.LinkedList;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import java.io.File;
import java.io.IOException;

import com.cybozu.vmbkp.config.NotNormalFileException;
import com.cybozu.vmbkp.config.ParseException;
import com.cybozu.vmbkp.config.FormatString;
import com.cybozu.vmbkp.config.FormatInt;

import com.cybozu.vmbkp.profile.ConfigGlobal;
import com.cybozu.vmbkp.profile.ProfileVm;
import com.cybozu.vmbkp.profile.ProfileGeneration;

import com.cybozu.vmbkp.util.VmInfo;
import com.cybozu.vmbkp.util.SnapInfo;
import com.cybozu.vmbkp.util.VmdkInfo;
import com.cybozu.vmbkp.util.AdapterType;
import com.cybozu.vmbkp.util.Utility;

import com.cybozu.vmbkp.soap.SnapshotManager;
import com.cybozu.vmbkp.soap.VirtualControllerManager;
import com.cybozu.vmbkp.soap.VirtualDiskManager;

/**
* @brief Lazy task identifier.
*/
enum LazyTaskId
{
    MOVE_PREV_DUMP_AND_DIGEST, DEL_PREV_DUMP, UNKNOWN;
}

/**
* @brief Lazy task.
*/
class LazyTask
{
    private LazyTaskId taskId_;
    private int diskId_;

    public LazyTask(LazyTaskId taskId, int diskId)
    {
        assert taskId != null;
        taskId_ = taskId;

        assert diskId >= 0;
        diskId_ = diskId;
    }

    public LazyTaskId getTaskId() { return taskId_; }
    public int getDiskId() { return diskId_; }
}

/**
* @brief Manage the ProfileVm, realted ProfileGeneration objects,
* and deletion/movement of archives of a virtual machine.
*/
public class VmArchiveManager
{
    /**
     * Logger.
     */
    private static final Logger logger_ =
        Logger.getLogger(VmArchiveManager.class.getName());
   
    /**
     * Global configuration.
     */
    private ConfigGlobal cfgGlobal_;

    /**
     * Vm profile.
     */
    private ProfileVm profVm_;

    /**
     * Current generation.
     */
    private ProfileGeneration currGen_;

    /**
     * List of lazy tasks.
     */
    private LinkedList<LazyTask> lazyTaskList_;
   
    /**
     * Constructor.
     */
    public VmArchiveManager(ConfigGlobal cfgGlobal, VmInfo vmInfo)
        throws Exception
    {
        assert cfgGlobal != null;
        cfgGlobal_ = cfgGlobal;

        /* Initialize generations */
        currGen_ = null;

        /* Initialize profile. */
        profVm_ = initializeProfileVm(vmInfo);
        assert profVm_ != null;

        lazyTaskList_ = new LinkedList<LazyTask>();
    }

    /**
     * Check target archive exists.
     *
     * @return True if archive directory exists.
     */
    public static boolean isExistArchive
        (ConfigGlobal cfgGlobal, VmInfo vmInfo)
    {
        return (isExistArchiveDetail(cfgGlobal, vmInfo) > 0);
    }

    /**
     * Check target archive exists but
     * succeeded generation does not exist.
     *
     * @return True if archive exists but
     *         succeeded generation does not exist.
     */
    public static boolean isEmptyArchive
        (ConfigGlobal cfgGlobal, VmInfo vmInfo)
    {
        return (isExistArchiveDetail(cfgGlobal, vmInfo) == 1);
    }
   
    /**
     * Check succeeded generation exists.
     *
     * @return True if succeeded generation exists, or false.
     */
    public static boolean isExistSucceededGeneration
        (ConfigGlobal cfgGlobal, VmInfo vmInfo)
    {
        return (isExistArchiveDetail(cfgGlobal, vmInfo) == 2);
    }

    /**
     * Check existance of archive and succeeded generation.
     *
     * @return 0: archive does not exist.
     *         1: archive exists but succeeded generation does not exist.
     *         2: succeeded generation exists.
     */
    private static int isExistArchiveDetail
        (ConfigGlobal cfgGlobal, VmInfo vmInfo)
    {
        assert cfgGlobal != null; assert vmInfo != null;

        String backupDirPath =
            cfgGlobal.getDefaultVmDirectory(vmInfo.getMoref());
        String profVmPath =
            cfgGlobal.getDefaultProfileVmPath(vmInfo.getMoref());

        int ret = 0;
        String errMsg = null;
        if ((new File(backupDirPath)).isDirectory() &&
            (new File(profVmPath)).isFile()) {

            ret = 1;
            try {
                ProfileVm profVm = new ProfileVm(profVmPath);
                if (profVm.getLatestSucceededGenerationId() >= 0) {
                    /* Succeeded generation exists. */
                    ret = 2;
                }
            } catch (IOException e) {
                errMsg = String.format("load profile %s failed.\n", profVmPath);
            } catch (NotNormalFileException e) {
                errMsg = String.format("%s is not normal file.\n", profVmPath);
            } catch (ParseException e) {
                errMsg = String.format("parse profile %s failed.\n", profVmPath);
            }
        }
        if (errMsg != null) {
            logger_.warning(errMsg);
        }
        return ret;
    }
   
    /**
     * Initialize vm profile with the specified virtual machine manager.
     * If the profile file is not found, it tries to create the file.
     *
     * @param vmInfo must not be null.
     * @return vm profile.
     */
    private ProfileVm initializeProfileVm(VmInfo vmInfo)
        throws Exception
    {
        /* Assertions. */
        assert vmInfo != null;
        assert cfgGlobal_ != null;
       
        /* Get backup directory path from global config. */
        String backupDirectory =
            cfgGlobal_.getDefaultVmDirectory(vmInfo.getMoref());

        /* Config file of the virtual machine. */
        String profVmPath = cfgGlobal_.getDefaultProfileVmPath(vmInfo.getMoref());
        if (profVmPath == null) {
            logger_.warning("profVmPath is null."); throw new Exception();
        }
       
        /* Create backup directory if it does not exist. */
        File backupDirectoryFile = new File(backupDirectory);
        if (! backupDirectoryFile.isDirectory()) {
            if (! backupDirectoryFile.mkdir()) {
                logger_.warning
                    (String.format
                     ("mkdir %s failed.", backupDirectory));
                throw new Exception();
            }
        }

        /* Prepare vm profile. */
        ProfileVm profVm;
        if ((new File(profVmPath)).isFile()) {
            /* The file exists so we load and update it. */
            profVm = new ProfileVm(profVmPath);

            /* Check moref of both the profile and vmInfo is same. */
            String moref = profVm.getMoref();
            if (! moref.equals(vmInfo.getMoref())) {
                logger_.warning
                    (String.format
                     ("moref %s != %s.\n", moref, vmInfo.getMoref()));
                throw new Exception();
            }

            /* is_clean must be true to continue backup process. */
            if (profVm.isClean() == false) {
                throw new Exception("profVm is not clean.");
            }
           
            /* Overwrite the name of the virtula machine. */
            profVm.setName(vmInfo.getName());
           
        } else {
            /* Config file does not exist then we create it. */

            profVm = new ProfileVm();
            profVm.initializeMetadata(vmInfo);
            profVm.write(profVmPath);
        }
       
        /* Create backup */
        profVm.makeBackup();

        return profVm;
    }

    /**
     * Get global config.
     */
    public ConfigGlobal getConfigGlobal()
    {
        return cfgGlobal_;
    }
   
    /**
     * Save all profile files.
     */
    public void save()
        throws Exception
    {
        assert profVm_ != null;
        profVm_.write();
       
        if (currGen_ != null) {
            currGen_.write();
        }
    }

    /**
     * Execute lazy tasks and
     * write required data after backup almost finished.
     *
     * @param isSucceeded True if dump of all vmdk files succeeded.
     * @return True if the finalization finished successfully.
     */
    public boolean finalizeBackup(boolean isSucceeded)
        throws Exception
    {
        assert profVm_ != null;
        assert currGen_ != null;

        /* Execute lasy tasks. */
        if (isSucceeded) {
            isSucceeded = execLazyTasks();
        }
       
        currGen_.setIsSucceeded(isSucceeded);
        profVm_.setGenerationInfo(currGen_);

        /* Delete old generations. */
        if (isSucceeded) {
            this.deleteOldGenerations();
        }

        return isSucceeded;
    }
   
    /**
     * Get name of the virtual machine.
     */
    public String getName()
    {
        if (profVm_ == null) { return null; }
        return profVm_.getName();
    }

    /**
     * Get moref of the virtual machine.
     */
    public String getMoref()
    {
        if (profVm_ == null) { return null; }
        return profVm_.getMoref();
    }

    /**
     * Load ProfileVm file.
     *
     * Currently this method is not used.
     *
     * @param vmMoref The moref of target virtual machine.
     * @return Loaded profVm in success, or null in failure.
     */
    public ProfileVm loadProfileVmWithMoref(String vmMoref)
    {
        String path = cfgGlobal_.getDefaultProfileVmPath(vmMoref);
        if (path == null) { return null; }
        if (! (new File(path)).isFile()) { return null; }

        ProfileVm profVm = null;
        try {
            profVm = new ProfileVm(path);
        } catch (Exception e) {
            logger_.warning(Utility.toString(e));
            return null;
        }
        return profVm;
    }

    /**
     * Set target generation.
     */
    public void setTargetGeneration(ProfileGeneration profGen)
        throws Exception
    {
        if (profGen == null) { throw new Exception("profGen is null."); }
        assert profGen.getGenerationId() >= 0;
        currGen_ = profGen;
    }

    /**
     * Get target generation.
     */
    public ProfileGeneration getTargetGeneration()
    {
        return currGen_;
    }

    /**
     * Setup generation config file.
     *
     * @param vmInfo Vm information.
     * @param snapInfo Snapshot information.
     * @param vmdkInfoList The list of vmdk information.
     * @param calendar timestamp.
     * @param isGzip True when you use gziped archives.
     * @return Initialized ProfileGeneration object in success.
     */
    public ProfileGeneration prepareNewGeneration
        (VmInfo vmInfo, SnapInfo snapInfo,
         List<VmdkInfo> vmdkInfoList, Calendar calendar,
         boolean isGzip)
        throws Exception
    {
        ProfileVm profVm = profVm_;
       
        /*
         * Create new generation id.
         */
        int currGenId = profVm.getLatestSucceededGenerationId();
        String timestampMs = Long.toString(calendar.getTimeInMillis());
        int newGenId = profVm.createNewGenerationId(timestampMs);
        profVm.write();
       
        /*
         * Make generation directory if it does not exist.
         */
        String genDir = profVm.getDefaultGenerationDirectory(newGenId);
        if ((new File(genDir)).mkdir() == false) {
            logger_.warning(String.format("mkdir %s failed.", genDir));
            throw new Exception();
        }

        /*
         * Make new ProfileGeneration and initialize it.
         */
        String profGenFn = genDir + "/" + ProfileGeneration.FILE_NAME;
        ProfileGeneration profGen = new ProfileGeneration();
        profGen.initializeGeneration
            (newGenId, currGenId,
             vmInfo, snapInfo,
             vmdkInfoList, calendar, isGzip);
        profGen.write(profGenFn);

        return profGen;
    }

    /**
     * Load profile of the specified generation.
     *
     * @param genId Specify -1 to select the latest generation.
     * @return ProfileGeneration object in success, or null.
     */
    public ProfileGeneration loadProfileGeneration(int genId)
        throws IOException, ParseException, NotNormalFileException,
               BackupFailedException
    {
        assert profVm_ != null;
        ProfileVm profVm = profVm_;
       
        if (genId < 0) {
            genId = profVm.getLatestSucceededGenerationId();
            if (genId < 0) { return null; }
        }

        if (profVm.isGenerationSucceeded(genId) == false) {
            logger_.warning
                (String.format
                 ("status of the generation  %d is not succeeded.", genId));
            throw new BackupFailedException();
        }
        String path = profVm.getDefaultProfileGenerationPath(genId);
        if (path == null) { return null; }

        return new ProfileGeneration(path);
    }

    /**
     * Delete backup directoriy of old generations
     * if the number of generations is more than "keep_generations" value.
     */
    public void deleteOldGenerations()
        throws Exception
    {
        /* Used member variables. */
        ConfigGlobal cfgGlobal = cfgGlobal_;
        ProfileVm profVm = profVm_;
       
        /* get keep_generations value */
        int keep = cfgGlobal.getKeepGenerations();
       
        /* get set of id of old generations */
        Set<Integer> oldGenIdSet = profVm.getOldGenerationSet(keep);

        /* debug */
        if (oldGenIdSet.isEmpty()) {
            logger_.info("No generation was deleted.");
            return;
        }
       
        /* debug */
        Set<String> oldGenStrSet = new TreeSet<String>();
        for (Integer gnIdI : oldGenIdSet) {
            oldGenStrSet.add(gnIdI.toString());
        }
        logger_.info(Utility.concat
                     (oldGenStrSet, "\n",
                      "Old generations to be deleted: ", "\n"));
                                   

        for (Integer genIdI : oldGenIdSet) {
            assert genIdI != null;
            int genId = genIdI.intValue();

            deleteGeneration(genId);
        }
    }

    /**
     * Delete backup directory of failed generations.
     */
    public void deleteFailedGenerations()
        throws Exception
    {
        ProfileVm profVm = profVm_;
        List<Integer> list = profVm.getFailedGenerationList();

        for (Integer genIdI : list) {
            assert genIdI != null;
            deleteGeneration(genIdI.intValue());
        }
    }

    /**
     * Get list of failed generations.
     */
    public List<Integer> getFailedGenerationList()
    {
        return profVm_.getFailedGenerationList();
    }

    /**
     * Delete the specified generation directory and
     * the corresponding entry in the vm profile.
     *
     * @param genId
     * @return True if succeeded.
     */
    private boolean deleteGeneration(int genId)
    {
        ProfileVm profVm = profVm_;
       
        String tgtPath =
            profVm.getDefaultGenerationDirectory(genId);
           
        /* delete generation directory */
        boolean ret = Utility.deleteDirectoryRecursive(new File(tgtPath));
        logger_.info
            (String.format
             ("delete generation %d directory %s.\n",
              genId, (ret ? "succeeded" : "failed")));

        /* delete information of the generation from profile vm */
        profVm.delGenerationInfo(genId);

        return ret;
    }

    /**
     * Get previous succeeded generation.
     *
     * @return null if not found.
     */
    private ProfileGeneration getPrevGeneration()
    {
        int currGenId = currGen_.getGenerationId();
        return getPrevGeneration(currGenId);
    }

    /**
     * Get previous succeeded generation of the specified genId.
     */
    private ProfileGeneration getPrevGeneration(int genId)
    {
        if (genId < 0) { return null; }
        int prevGenId = profVm_.getPrevSucceededGenerationId(genId);
        logger_.fine("prevGenId: " + prevGenId); /* debug */
        if (prevGenId < 0) { return null; }
        assert profVm_.isGenerationSucceeded(prevGenId);

        ProfileGeneration ret = null;
        try {
            ret = loadProfileGeneration(prevGenId);
        } catch (Exception e) {
            logger_.warning(Utility.toString(e));
        }
        return ret;
    }

    /**
     * Get next succeeded generation.
     *
     * @return null if not found.
     */
    private ProfileGeneration getNextGeneration()
    {
        int currGenId = currGen_.getGenerationId();
        return getNextGeneration(currGenId);
    }

    /**
     * Get next succeeded generation of the specified genId.
     */
    private ProfileGeneration getNextGeneration(int genId)
    {
        if (genId < 0) { return null; }
        int nextGenId = profVm_.getNextSucceededGenerationId(genId);
        logger_.fine("nextGenId: " + nextGenId); /* debug */
        if (nextGenId < 0) { return null; }
        assert profVm_.isGenerationSucceeded(nextGenId);

        ProfileGeneration ret = null;
        try {
            ret = loadProfileGeneration(nextGenId);
        } catch (Exception e) {
            logger_.warning(Utility.toString(e));
        }
        return ret;
    }
   
    /**
     * Get disk id of corresponding vmdk of previous generation.
     *
     * @return >=0 in success, or -1 in failure.
     */
    public int getPrevDiskId(int diskId)
    {
        ProfileVm profVm = profVm_;
        ProfileGeneration currGen = currGen_;
       
        String uuid = currGen.getUuid(diskId);

        /* get previous generation and check. */
        ProfileGeneration prevGen = getPrevGeneration();
        if (prevGen == null) { return -1; }

        /* search and check disk id. */
        int prevDiskId = prevGen.getDiskIdWithUuid(uuid);
        if (prevDiskId < 0) { return -1; }

        return prevDiskId;
    }
   
    /**
     * Get change id of corresponding vmdk of previous generation.
     *
     * @return change id in success, or null in failure.
     */
    public String getPrevChangeId(int diskId)
    {
        /* get previous generation and check. */
        ProfileGeneration prevGen = getPrevGeneration();
        if (prevGen == null) { return null; }

        int prevDiskId = getPrevDiskId(diskId);
        if (prevDiskId < 0) { return null; }
       
        /* check that changed block information is available */
        String prevChangeId = prevGen.getChangeId(prevDiskId);

        if (prevChangeId != null && prevChangeId.equals("*") == false) {
            return prevChangeId;
        } else {
            return null;
        }
    }

    /**
     * Get previous dump path
     * using diskId of the current generation.
     */
    public String getPrevDumpPath(int diskId)
    {
        ProfileGeneration prevGen = getPrevGeneration();
        if (prevGen == null) { return null; }
        int prevDiskId = getPrevDiskId(diskId);
        if (prevDiskId < 0) { return null; }

        return prevGen.getDumpOutPath(prevDiskId);
    }

    /**
     * Get previous digest path
     * using diskId of the current generation.
     */
    public String getPrevDigestPath(int diskId)
    {
        ProfileGeneration prevGen = getPrevGeneration();
        if (prevGen == null) { return null; }
        int prevDiskId = getPrevDiskId(diskId);
        if (prevDiskId < 0) { return null; }

        return prevGen.getDigestOutPath(prevDiskId);
    }

    /**
     * Check if the differential backup can be executed.
     */
    public boolean canExecDiffBackup(String uuid)
    {
        /* Get current diskId */
        int diskId = currGen_.getDiskIdWithUuid(uuid);
        logger_.info("diskId: " + diskId); /* debug */
        if (diskId < 0) { return false; };

        /* Check the previous dump succeeded */
        ProfileGeneration prevGen = this.getPrevGeneration();
        if (prevGen == null) {
            logger_.warning("prevGen is null.");
            return false;
        }
        int prevDiskId = prevGen.getDiskIdWithUuid(uuid);
        if (prevDiskId < 0) {
            logger_.warning("prevDiskId < 0");
            return false;
        }
        if (prevGen.isVmdkdumpSucceeded(prevDiskId) == false) {
            logger_.warning("vmdkbkp did not succeed.");
            return false;
        }
       
        /* Capacity check. */
        long capacity = currGen_.getCapacity(diskId);
        long prevCapacity = prevGen.getCapacity(prevDiskId);
        if (capacity < 0 || capacity != prevCapacity) {
            logger_.info("capacity is invalid or different.");
            return false;
        }
       
        /* Check full dump file exists in previous generation. */
        if (prevGen.isDumpOutExist(prevDiskId) == false ||
            prevGen.isDigestOutExist(prevDiskId) == false) {
            logger_.info("dump or digest does not exist.");
            return false;
        }

        return true;
    }

    /**
     * Check if incremental backup can be executed.
     */
    public boolean canExecIncrBackup(String uuid)
    {
        /* Check diff backup is capable. */
        if (this.canExecDiffBackup(uuid) == false) {
            return false;
        }

        /* Get the latest dump and digest available generation */
        int diskId = currGen_.getDiskIdWithUuid(uuid);
        if (diskId < 0) {
            logger_.warning("diskId < 0");
            return false;
        };
        ProfileGeneration prevGen = this.getPrevGeneration();
        if (prevGen == null) {
            logger_.warning("prevGen is null.");
            return false;
        }
        int prevDiskId = prevGen.getDiskIdWithUuid(uuid);
        if (prevDiskId < 0) {
            logger_.warning("prevDiskId < 0");
            return false;
        }
       
        /* Check that changed block information of both
           previous and current generation is available */
        String currChangeId = currGen_.getChangeId(diskId);
        if (currChangeId == null || currChangeId.equals("*")) {
            logger_.info("currChangeId is null or \"*\"");
            return false;
        }
        String prevChangeId = prevGen.getChangeId(prevDiskId);
        logger_.info("prevChangeId: " + prevChangeId);
        if (prevChangeId == null || prevChangeId.equals("*")) {
            logger_.info("prevChangeId is null or \"*\"");
            return false;
        }

        /* ChangeId of both generations exists. */
        return true;
    }

    /**
     * Get a list of full dump file and rdiff files to restore
     * vmdk specified with diskId.
     *
     * @param diskId Disk id of the !!!current!!! generation.
     * @return The list of file path of the dump and rdiff.
     *         the first element must not be rdiff but dump.
     *         Return null in failure.
     */
    public List<String> getDumpPathListForRestore(int diskId)
    {
        assert currGen_ != null;
        ProfileGeneration currGen = currGen_;
       
        logger_.info
            (String.format
             ("------------------------------------------------------\n" +
              "getDumpPathListForRestore start for diskID %d.\n" +
              "------------------------------------------------------\n",
              diskId));
       
        /* Initialize */
        LinkedList<String> ret = new LinkedList<String>();
        String uuid = currGen.getUuid(diskId);

        /* Check the generation backup succeeded. */
        if (currGen.isVmdkdumpSucceeded(diskId) == false) {
            logger_.warning
                (String.format
                 ("Skipping: " +
                  "generation %d disk %d (%s) is marked FAILED.\n",
                  currGen.getGenerationId(), diskId, currGen.getUuid(diskId)));
            return null;
        }

        /* Does this generation have the full dump */
        String dumpPath;
        dumpPath = currGen.getDumpOutPath(diskId);
        logger_.info(String.format("dumpPath: %s\n", dumpPath));
        if (currGen.isDumpOutExist(diskId)) {
            ret.add(dumpPath);
            return ret;
        }

        /* There is no dump of the current generation,
           so search corresponding dump in newer generations. */
        assert ret.isEmpty();
        String rdiffPath;

        boolean isFirstElementDump = false;
        ProfileGeneration next = this.getNextGeneration();
        while (next != null) {

            /* Get nextDiskId */
            int nextDiskId = next.getDiskIdWithUuid(uuid);
            if (nextDiskId < 0) {
                logger_.warning("diskId is invalid."); /* debug */
                return null;
            }

            /* Check the vmdk backup succeeded. */
            if (next.isVmdkdumpSucceeded(nextDiskId) == false) {
                logger_.warning
                    (String.format
                     ("backup of vmdk with diskId %d is failed.",
                      nextDiskId));
                return null;
            }
           
            dumpPath = next.getDumpOutPath(nextDiskId);
            rdiffPath = next.getRdiffOutPath(nextDiskId);

            logger_.info
                (String.format
                 ("dumpPath: %s\n" + "rdiffPath: %s\n",
                  dumpPath, rdiffPath));

            boolean notFoundFile = true;
            if (next.isRdiffOutExist(nextDiskId)) {
                /* Rdiff file is found. */
                ret.addFirst(FormatString.toQuatedString(rdiffPath));
                notFoundFile = false;
            }
            if (next.isDumpOutExist(nextDiskId)) {
                /* Dump file is found. */
                ret.addFirst(FormatString.toQuatedString(dumpPath));
                isFirstElementDump = true;
                break;
            }
            if (next.isChanged(nextDiskId) == false) {
                /* The vmdk is not changed at all in This generation.
                   This generation may have none of dump, digest, and rdiff.
                   Just skip. */
                notFoundFile = false;
            }
            if (notFoundFile) {
                /* Error. */
                logger_.warning
                    (String.format
                     ("Error: neither dump nor rdiff is available in " +
                      " generation %d\n", next.getGenerationId()));
                return null;
            }
               
            next = this.getNextGeneration(next.getGenerationId());
        }
           
        if (isFirstElementDump == false) {
            logger_.warning
                (String.format("isFirstElementDump is false."));
            return null;
        }

        /* debug */
        StringBuffer sb = new StringBuffer();
        sb.append("---getDumpPathListForRestore() result---\n");
        for (String path : ret) {
            sb.append(path);
            sb.append("\n");
        }
        sb.append("----------------------------------------\n");
        logger_.info(sb.toString());

        logger_.info("getDumpPathListForRestore end.");
        return (List<String>) ret;
    }

    /**
     * Get digest path of the disk of the current generation.
     * (The digest file may be moved to a future generation.)
     *
     * @param diskId disk id of current generation.
     * @return Full path of the target digest file.
     */
    public String getDigestPathForCheck(int diskId)
    {
        assert currGen_ != null;
        ProfileGeneration currGen = currGen_;

        String uuid = currGen.getUuid(diskId);

        /* There is no digest file in the current generation.
           Search newer generations. */
        boolean isFound = false;
        ProfileGeneration next = currGen;
        while (next != null) {

            int nextDiskId = next.getDiskIdWithUuid(uuid);
            if (nextDiskId < 0) {
                logger_.warning("diskId is invalid.");
                return null;
            }

            if (next.isVmdkdumpSucceeded(nextDiskId) == false) {
                logger_.warning
                    (String.format
                     ("Geneartion %d disk %d (%s) is marked FAILED.\n",
                      next.getGenerationId(), nextDiskId, uuid));
                return null;
            }

            String digestPath;
            digestPath = next.getDigestOutPath(nextDiskId);
            if (next.isDigestOutExist(nextDiskId)) {
                logger_.info(String.format("digestPath: %s\n", digestPath));
                return digestPath;
            }

            next = this.getNextGeneration(next.getGenerationId());
        }

        /* not found */
        logger_.warning
            (String.format
             ("digestPath not found for genetarion %d disk %d (%s).",
              currGen.getGenerationId(), diskId, uuid));
        return null;
    }

    /**
     * @param filename Given string to test.
     * @return True if the extension is ".gz".
     */
    private boolean isGzipFileName(String filename)
    {
        int len = filename.length();
        return (len > 3 && filename.substring(len - 3).equals(".gz"));
    }

    /**
     * @param filename String to be converted.
     * @param isGzip The name will be gzip file name if true.
     * @return converted string.
     */
    private String convertFileName(String filename, boolean isGzip)
    {
        String tmp = eliminateGzipedExtension(filename);
        if (isGzip) {
            return addGzipedExtension(tmp);
        } else {
            return tmp;
        }
    }

    /**
     * @param filename Given string to be converted.
     * @return non-gzip file name.
     */
    private String eliminateGzipedExtension(String filename)
    {
        int len = filename.length();
        if (isGzipFileName(filename)) {
            assert len > 3;
            return filename.substring(0, len - 3);
        } else {
            return filename;
        }
    }

    /**
     * @param filename Given string to be converted.
     *        must not be gzip file name.
     * @return gzip file name.
     */
    private String addGzipedExtension(String filename) {

        assert ! isGzipFileName(filename);
        return filename + ".gz";
    }
   
    /**
     * Move previous dump and digest files
     * to current generation directory.
     */
    public boolean moveDumpAndDigestFromPrev(int diskId)
    {
        assert profVm_ != null;
        assert currGen_ != null;

        {
            /* Set the same file type (gziped or not). */
            String fromDump = this.getPrevDumpPath(diskId);
            String toDump = currGen_.getDumpOutFileName(diskId);
            boolean isGzipFromDump = isGzipFileName(fromDump);
            boolean isGzipToDump = isGzipFileName(toDump);
            if (isGzipFromDump != isGzipToDump) {
                currGen_.setDumpOutFileName
                    (diskId, convertFileName(toDump, isGzipFromDump));
            }
            logger_.fine
                (String.format
                 ("%s %s %s\n",
                  isGzipFromDump,
                  isGzipToDump,
                  currGen_.getDumpOutFileName(diskId)));

            String fromDigest = this.getPrevDigestPath(diskId);
            String toDigest = currGen_.getDigestOutFileName(diskId);
            boolean isGzipFromDigest = isGzipFileName(fromDigest);
            boolean isGzipToDigest = isGzipFileName(toDigest);
            if (isGzipFromDigest != isGzipToDigest) {
                currGen_.setDigestOutFileName
                    (diskId, convertFileName(toDigest, isGzipFromDigest));
            }
            logger_.fine
                (String.format
                 ("%s %s %s\n",
                  isGzipFromDigest,
                  isGzipToDigest,
                  currGen_.getDigestOutFileName(diskId)));
        }

       
        String fromDumpStr = this.getPrevDumpPath(diskId);
        String toDumpStr = currGen_.getDumpOutPath(diskId);
        assert fromDumpStr != null && toDumpStr != null;

        String fromDigestStr = this.getPrevDigestPath(diskId);
        String toDigestStr = currGen_.getDigestOutPath(diskId);
        assert fromDigestStr != null && toDigestStr != null;

        logger_.info
            (String.format
             ("move dump %s to %s. digest %s to %s.",
              fromDumpStr, toDumpStr,
              fromDigestStr, toDigestStr));
       
        File fromDump = new File(fromDumpStr);
        File toDump = new File(toDumpStr);
        File fromDigest = new File(fromDigestStr);
        File toDigest = new File(toDigestStr);

       
        if (fromDump.isFile() == false) {
            logger_.warning
                (String.format
                 ("File %s not found.", fromDumpStr));
            return false;
        }
        if (fromDigest.isFile() == false) {
            logger_.warning
                (String.format
                 ("File %s not found.", fromDigestStr));
            return false;
        }

        return fromDump.renameTo(toDump) && fromDigest.renameTo(toDigest);
    }

    /**
     * Delete dump file of previous succeeded generation.
     */
    public boolean deletePrevDump(int diskId)
    {
        ProfileGeneration prevGen = this.getPrevGeneration();
        if (prevGen == null) {
            logger_.warning("prevGen is null.");
            return false;
        }
        String prevDumpPath = this.getPrevDumpPath(diskId);
        if (prevDumpPath == null) {
            logger_.warning("prevDumpPath is null.");
            return false;
        }
        File prevDumpFile = new File(prevDumpPath);
        if (prevDumpFile.isFile() == false) {
            logger_.warning("prevDumpFile is not normal file.");
            return false;
        }

        /* delete previoud dump file! */
        boolean isDeleted = prevDumpFile.delete();
        currGen_.setDeletedPreviousDump(diskId, isDeleted);
       
        logger_.info
            (String.format
             ("Deleting previous dump %s %s.",
              prevDumpPath, (isDeleted ? "succeeded" : "failed")));

        return isDeleted;
    }

    /**
     * Register lazy task of moveDumpAndDigestFromPrev().
     *
     * @param diskId disk id of the current generation.
     */
    public void registerLazyTaskMovePrevDumpAndDigest(int diskId)
    {
        logger_.info
            (String.format
             ("register %s %d.",
              LazyTaskId.MOVE_PREV_DUMP_AND_DIGEST.toString(),
              diskId));

        assert lazyTaskList_ != null;
        LazyTask task =
            new LazyTask(LazyTaskId.MOVE_PREV_DUMP_AND_DIGEST, diskId);
        lazyTaskList_.offer(task);
    }

    /**
     * Register lazy task of deletePrevDump().
     *
     * @param diskId disk id of the current generation.
     */
    public void registerLazyTaskDelPrevDump(int diskId)
    {
        logger_.info
            (String.format
             ("register %s %d.",
              LazyTaskId.DEL_PREV_DUMP.toString(),
              diskId));
       
        assert lazyTaskList_ != null;
        LazyTask task =
            new LazyTask(LazyTaskId.DEL_PREV_DUMP, diskId);
        lazyTaskList_.offer(task);
    }

    /**
     * Execute lazy tasks registered.
     *
     * @return True if all tasks finished successfully, or false.
     */
    private boolean execLazyTasks()
    {
        logger_.info("execLazyTasks() begin.");
        assert lazyTaskList_ != null;
        boolean ret = true;

        while (lazyTaskList_.isEmpty() == false) {
            LazyTask task = lazyTaskList_.poll();

            switch (task.getTaskId()) {
            case MOVE_PREV_DUMP_AND_DIGEST:
                ret &= moveDumpAndDigestFromPrev(task.getDiskId());
                break;
            case DEL_PREV_DUMP:
                ret &= deletePrevDump(task.getDiskId());
                break;
            default:
                ret = false;
                break;
            }
        }
        logger_.info("execLazyTasks() end.");
        return ret;
    }

    /**
     * Generate controller-disk map from config data.
     *
     * @param datastoreName datastore name.
     * @return a list of virtual controllers manager which have
     *         a list of information of its virtual disks.
     */
    public List<VirtualControllerManager>
        generateVirtualControllerManagerList(String datastoreName)
    {
        assert datastoreName != null;
        assert currGen_ != null;
       
        ProfileGeneration profGen = currGen_;
       
        /* Set to make sorted and deduplicated list later. */
        TreeSet<VirtualControllerManager> vcmSet =
            new TreeSet<VirtualControllerManager>();

        List<Integer> diskIdList = profGen.getDiskIdList();
        for (Integer diskIdI : diskIdList) {

            int diskId = diskIdI.intValue();
            assert diskId >= 0;

            /* Skip independent disk. */
            if (profGen.isIndependentDisk(diskId)) { continue; }
           
            /* Get required parameters */
            int key = profGen.getDiskDeviceKey(diskId);
            int unitNumber = profGen.getUnitNumber(diskId);
            long capacity = profGen.getCapacity(diskId);
            assert capacity % 1024L == 0;
            long capacityInKb = capacity / 1024L;

            AdapterType type = profGen.getAdapterType(diskId);
            assert type != AdapterType.UNKNOWN;
            int ckey = profGen.getControllerDeviceKey(diskId);
            int busNumber = profGen.getBusNumber(ckey);

            /* Create virtual disk manager and virtual controller manager. */
            VirtualDiskManager vdm = new VirtualDiskManager
                (key, unitNumber, capacityInKb, datastoreName);
            VirtualControllerManager vcm =
                new VirtualControllerManager(type, ckey, busNumber);

            /* Get the corresponding virtual controller manager
               in the set, or the created one is added to the set. */
            VirtualControllerManager vcmInSet = vcmSet.ceiling(vcm);
            if (vcmInSet == null || vcmInSet.compareTo(vcm) != 0) {
                vcmSet.add(vcm);
                vcmInSet = vcm;
            } else {
                assert vcmInSet.compareTo(vcm) == 0;
            }
            /* Assign relationship of the disk and the controller  */
            vcmInSet.add(vdm);
        } /* for */

        /* Set -> List */
        List<VirtualControllerManager> ret =
            new LinkedList<VirtualControllerManager>();
        ret.addAll(vcmSet);
        return ret;
    }

    /**
     * Print the specified controller-disk mapping for debug.
     *
     * @param vcmList
     */
    public void printVirtualControllerManagerList
        (List<VirtualControllerManager> vcmList)
    {
        for (VirtualControllerManager vcm : vcmList) {
            vcm.print();
        }
    }

    /**
     * Convert List<VirtualControllerManager>
     * to String as human-readable format.
     *
     * @param vcmList
     * @return Human readable information of
     *  the list of VirtualControllerManager.
     */
    public String toStringVirtualControllerManagerList
        (List<VirtualControllerManager> vcmList)
    {
        StringBuffer sb = new StringBuffer();
        for (VirtualControllerManager vcm : vcmList) {
            sb.append(vcm.toString());
        }
        return sb.toString();
    }
   
    /**
     * Get target disk id of the corresponding vmdk with the given vmdkInfo.
     */
    public int getTargetDiskId(VmdkInfo vmdkInfo)
    {
        assert currGen_ != null;
        ProfileGeneration profGen = currGen_;
       
        List<Integer> diskIdList = profGen.getDiskIdList();
        for (Integer diskIdI : diskIdList) {
            int diskId = diskIdI.intValue();

            /* Comparing key is enough, however,
               The following parameters should be the same. */
            int ckey = profGen.getControllerDeviceKey(diskId);
            if (vmdkInfo.key_ == profGen.getDiskDeviceKey(diskId) &&
                vmdkInfo.capacityInKB_ * 1024L == profGen.getCapacity(diskId) &&
                vmdkInfo.unitNumber_ == profGen.getUnitNumber(diskId) &&
                vmdkInfo.ckey_ == ckey &&
                vmdkInfo.busNumber_ == profGen.getBusNumber(ckey) &&
                vmdkInfo.type_ == profGen.getAdapterType(diskId)) {

                return diskId;
            }
        }
       
        /* not found */
        return -1;
    }

    /**
     * Lock wrapper.
     */
    public void lock(int timeoutSec)
        throws Exception
    {
        assert profVm_ != null;
        profVm_.lock(timeoutSec);
    }

    /**
     * Unlock wrapper.
     */
    public void unlock()
    {
        assert profVm_ != null;
        profVm_.unlock();
    }

    /**
     * Reload wrapper.
     */
    public void reload()
        throws Exception
    {
        assert profVm_ != null;
        profVm_.reload();
    }

    /**
     * Get status string of generation of the genId.
     */
    private String getGenerationStatusString(int genId)
    {
        StringBuffer sb = new StringBuffer();
        sb.append
            (String.format
             ("[Gen %d \"%s\"]", genId, profVm_.getTimestampStr(genId)));

        if (profVm_.isGenerationSucceeded(genId)) {
            try {
                ProfileGeneration profGen =
                    loadProfileGeneration(genId);

                List<Integer> diskIdList = profGen.getDiskIdList();

                for (Integer diskId: diskIdList) {

                    sb.append
                        (String.format
                         ("[%d %s%s:%s %sB %s %ds]",
                          diskId,
                          profGen.getAdapterType(diskId).toTypeString(),
                          profGen.getBusNumber
                          (profGen.getControllerDeviceKey(diskId)),
                          profGen.getUnitNumber(diskId),
                          FormatInt.toString(profGen.getCapacity(diskId)),
                          profGen.getBackupMode(diskId).toString(),
                          profGen.getDumpElapsedTimeMs(diskId) / 1000L
                          ));
                }

            } catch (Exception e) {
                logger_.warning
                    (String.format("failed with generation %d.", genId));
                return String.format("%d ##########_ERROR_##########");
            }
        } else {
            sb.append(" ----------_FAILED_----------");
        }
        return sb.toString();
    }
       
    /**
     * Get status string.
     */
    public String getStatusString(StatusInfo statusInfo, boolean isAvailable)
    {
        assert profVm_ != null;

        StringBuffer sb = new StringBuffer();
       
        sb.append(profVm_.getStatusString(isAvailable));

        if (statusInfo.isDetail) {
            for (Integer genId: profVm_.getGenerationIdList()) {
                assert genId != null;
                assert genId >= 0;
               
                sb.append("\n\t");
                sb.append(this.getGenerationStatusString(genId));
            }
        }
        return sb.toString();
    }

    /**
     * Get timestampMs of latest generation.
     *
     * @return timestampMs. -1 means no available generation.
     */
    public long getTimestampMsOfLatestGeneration()
    {
        if (profVm_ == null) { return -1L; }
        int genId = profVm_.getLatestGenerationId();

        if (genId < 0) {
            return -1L;
        } else {
            return Long.valueOf(profVm_.getTimestampMs(genId)).longValue();
        }
    }
}
TOP

Related Classes of com.cybozu.vmbkp.control.VmArchiveManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.