com.netflix.priam.backup.IncrementalRestore.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.priam.backup.IncrementalRestore.java

Source

/**
 * Copyright 2013 Netflix, Inc.
 *
 * 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.
 */
package com.netflix.priam.backup;

import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.cassandra.io.sstable.SSTableLoaderWrapper;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.streaming.PendingFile;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.netflix.priam.IConfiguration;
import com.netflix.priam.PriamServer;
import com.netflix.priam.backup.AbstractBackupPath.BackupFileType;
import com.netflix.priam.backup.IMessageObserver.RESTORE_MESSAGE_STATUS;
import com.netflix.priam.backup.IMessageObserver.RESTORE_MESSAGE_TYPE;
import com.netflix.priam.scheduler.SimpleTimer;
import com.netflix.priam.scheduler.TaskTimer;
import com.netflix.priam.utils.Sleeper;

/*
 * Incremental SSTable Restore using SSTable Loader
 */
@Singleton
public class IncrementalRestore extends AbstractRestore {
    private static final Logger logger = LoggerFactory.getLogger(IncrementalRestore.class);
    public static final String JOBNAME = "INCR_RESTORE_THREAD";
    private static final String SYSTEM_KEYSPACE = "system";
    private final List<String> streamedIncrementalRestorePaths = new ArrayList<String>();
    static List<IMessageObserver> observers = new ArrayList<IMessageObserver>();

    /* Marked public for testing */
    public static final Pattern SECONDRY_INDEX_PATTERN = Pattern
            .compile("^[a-zA-Z_0-9-]+\\.[a-zA-Z_0-9-]+\\.[a-z1-9]{2,4}$");

    private final File restoreDir;

    @Inject
    private SSTableLoaderWrapper loader;

    @Inject
    private PriamServer priamServer;

    @Inject
    public IncrementalRestore(IConfiguration config, @Named("incr_restore") IBackupFileSystem fs, Sleeper sleeper) {
        super(config, fs, JOBNAME, sleeper);
        this.restoreDir = new File(config.getDataFileLocation(), "restore_incremental");
    }

    @Override
    public void execute() throws Exception {
        String prefix = config.getRestorePrefix();
        if (Strings.isNullOrEmpty(prefix)) {
            logger.error(
                    "Restore prefix is not set, skipping incremental restore to avoid looping over the incremental backups. Plz check the configurations");
            return; // No point in restoring the files which was just backedup.
        }

        if (config.isRestoreClosestToken()) {
            priamServer.getId().getInstance().setToken(restoreToken.toString());
        }

        Date start = tracker.first().time;
        Iterator<AbstractBackupPath> incrementals = fs.list(prefix, start, Calendar.getInstance().getTime());
        FileUtils.createDirectory(restoreDir); // create restore dir.
        while (incrementals.hasNext()) {
            AbstractBackupPath temp = incrementals.next();
            if (tracker.contains(temp) || start.compareTo(temp.time) >= 0)
                continue; // ignore the ones which where already downloaded.
            if (temp.getType() != BackupFileType.SST)
                continue; // download SST's only.
            // skip System Keyspace, else you will run into concurrent schema issues.
            if (temp.getKeyspace().equalsIgnoreCase("System"))
                continue;
            /* Cassandra will rebuild Secondary index's after streaming is complete so we can ignore those */
            if (SECONDRY_INDEX_PATTERN.matcher(temp.fileName).matches()) // Make this use the constant from 1.1
                continue;

            // Create Directory for Individual Token respective to each incoming file
            File tokenDir = new File(restoreDir, temp.getToken());
            FileUtils.createDirectory(tokenDir);
            File keyspaceDir = config.getTargetKSName() == null ? new File(tokenDir, temp.keyspace)
                    : new File(tokenDir, config.getTargetKSName());
            FileUtils.createDirectory(keyspaceDir);
            File columnFamilyDir = config.getTargetCFName() == null ? new File(keyspaceDir, temp.columnFamily)
                    : new File(tokenDir, config.getTargetCFName());
            FileUtils.createDirectory(columnFamilyDir);
            logger.debug("*** Keyspace = " + keyspaceDir.getAbsolutePath() + " Column Family = "
                    + columnFamilyDir.getAbsolutePath() + " File = " + temp.getRemotePath());
            if (config.getTargetKSName() != null || config.getTargetCFName() != null)
                temp.fileName = renameIncrementalRestoreFile(temp.fileName);
            download(temp, new File(columnFamilyDir, temp.fileName));
        }
        // wait for all the downloads in this batch to complete.
        waitToComplete();
        // stream the SST's in the dir
        for (File tokenDir : restoreDir.listFiles()) {
            for (File keyspaceDir : tokenDir.listFiles()) {
                for (File columnFamilyDir : keyspaceDir.listFiles()) {
                    Collection<PendingFile> streamedSSTs = loader.stream(columnFamilyDir);
                    addToStreamedIncrementalRestorePaths(streamedSSTs);
                    if (streamedIncrementalRestorePaths.size() > 0) {
                        logger.debug("streamedIncrementalRestorePaths > 0, hence notifying observers");
                        notifyStreamedDataObservers();
                    }
                    // cleanup the dir which where streamed.
                    loader.deleteCompleted(streamedSSTs);
                }
            }
        }
    }

    /**
     * Run every 20 Sec
     */
    public static TaskTimer getTimer() {
        return new SimpleTimer(JOBNAME, 20L * 1000);
    }

    @Override
    public String getName() {
        return JOBNAME;
    }

    public static void addObserver(IMessageObserver observer) {
        observers.add(observer);
    }

    public static void removeObserver(IMessageObserver observer) {
        observers.remove(observer);
    }

    public void addToStreamedIncrementalRestorePaths(Collection<PendingFile> streamedSSTs) {
        streamedIncrementalRestorePaths.clear();
        for (PendingFile streamedSST : streamedSSTs)
            streamedIncrementalRestorePaths.add(streamedSST.getFilename());
    }

    public void notifyStreamedDataObservers() {
        for (IMessageObserver observer : observers) {
            if (observer != null) {
                observer.update(RESTORE_MESSAGE_TYPE.INCREMENTAL, streamedIncrementalRestorePaths,
                        RESTORE_MESSAGE_STATUS.STREAMED);
            } else
                logger.error("Streamed Observer is Null, hence can not notify ...");
        }
    }

    public String renameIncrementalRestoreFile(String fileToBeRenamed) {
        String[] splitToBeRenamed = StringUtils.split(fileToBeRenamed, '-');

        // Rename KS
        if (config.getTargetKSName() != null) {
            logger.info("Old Keyspace = [" + splitToBeRenamed[0] + "] --> New Keyspace = ["
                    + config.getTargetKSName() + "]");
            splitToBeRenamed[0] = config.getTargetKSName();
        }

        // Rename CF
        if (config.getTargetCFName() != null) {
            logger.info("Old Column Family = [" + splitToBeRenamed[1] + "] --> New Column Family = ["
                    + config.getTargetCFName() + "]");
            splitToBeRenamed[1] = config.getTargetCFName();
        }

        logger.info("Orig Filename = [" + fileToBeRenamed + "] --> New Filename = ["
                + StringUtils.join(splitToBeRenamed, '-') + "]");

        return StringUtils.join(splitToBeRenamed, '-');

    }

}