org.zenoss.zep.index.impl.EventIndexRebuilderImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.zenoss.zep.index.impl.EventIndexRebuilderImpl.java

Source

/*****************************************************************************
 * 
 * Copyright (C) Zenoss, Inc. 2011, all rights reserved.
 * 
 * This content is made available according to terms specified in
 * License.zenoss under the directory where your Zenoss product is installed.
 * 
 ****************************************************************************/

package org.zenoss.zep.index.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.zenoss.protobufs.zep.Zep.EventDetailItem;
import org.zenoss.protobufs.zep.Zep.EventSummary;
import org.zenoss.protobufs.zep.Zep.ZepConfig;
import org.zenoss.zep.ZepException;
import org.zenoss.zep.dao.*;
import org.zenoss.zep.dao.impl.DaoUtils;
import org.zenoss.zep.events.IndexRebuildRequiredEvent;
import org.zenoss.zep.impl.ThreadRenamingRunnable;
import org.zenoss.zep.index.EventIndexDao;
import org.zenoss.zep.index.EventIndexRebuilder;
import org.zenoss.zep.index.EventIndexer;
import org.zenoss.zep.index.IndexedDetailsConfiguration;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * Utility used to rebuild the event index.
 */
public class EventIndexRebuilderImpl
        implements EventIndexRebuilder, ApplicationListener<IndexRebuildRequiredEvent> {
    private static final Logger logger = LoggerFactory.getLogger(EventIndexRebuilderImpl.class);

    private final boolean enableIndexing;

    private EventIndexer eventIndexer;
    private ConfigDao configDao;
    private EventSummaryBaseDao summaryBaseDao;
    private EventIndexDao indexDao;
    private IndexMetadataDao indexMetadataDao;
    private IndexedDetailsConfiguration indexedDetailsConfiguration;

    private final ExecutorService executorService = Executors.newSingleThreadExecutor();
    private Future<?> rebuildFuture = null;
    private volatile boolean shutdown = false;
    private volatile boolean configurationChanged = false;
    private final Object lock = new Object();
    private byte[] indexVersionHash;
    private File indexStateFile;

    public EventIndexRebuilderImpl(boolean enableIndexing) {
        this.enableIndexing = enableIndexing;
    }

    @Override
    public void init() {
        if (!enableIndexing) {
            return;
        }
        rebuildFuture = executorService.submit(new ThreadRenamingRunnable(new Runnable() {
            @Override
            public void run() {
                logger.info("Index rebuilding thread started for: {}", indexDao.getName());
                while (!shutdown) {
                    try {
                        configurationChanged = false;
                        Map<String, EventDetailItem> detailItems = indexedDetailsConfiguration
                                .getEventDetailItemsByName();
                        indexVersionHash = calculateIndexVersionHash(detailItems);
                        recreateIndexIfNeeded();

                        // Wait to be interrupted if the configuration changes for the index
                        synchronized (lock) {
                            if (!shutdown && !configurationChanged) {
                                lock.wait();
                            }
                        }
                    } catch (InterruptedException e) {
                        logger.info("Interrupted index rebuilding thread");
                    } catch (Exception e) {
                        logger.warn("Failed to rebuild event index", e);
                        /*
                         * If we got an error indexing or accessing the database, back off a bit and wait for any
                         * transient errors to be resolved.
                         */
                        synchronized (lock) {
                            try {
                                lock.wait(30000L);
                            } catch (InterruptedException ie) {
                                // Ignored
                            }
                        }
                    }
                }
                logger.info("Index rebuilding thread stopped for: {}", indexDao.getName());
            }
        }, "INDEX_REBUILDER_" + this.indexDao.getName().toUpperCase()));
    }

    public void setIndexDir(File indexDir) {
        this.indexStateFile = new File(indexDir, ".index_state_" + indexDao.getName() + ".properties");
        if (this.enableIndexing) {
            File parentDir = this.indexStateFile.getParentFile();
            if (!parentDir.isDirectory() && !parentDir.mkdirs()) {
                logger.warn("Failed to create parent directory: {}", parentDir.getAbsolutePath());
            }
        }
    }

    public void setEventIndexer(EventIndexer eventIndexer) {
        this.eventIndexer = eventIndexer;
    }

    public void setConfigDao(final ConfigDao configDao) {
        this.configDao = configDao;
    }

    public void setSummaryBaseDao(EventSummaryBaseDao summaryBaseDao) {
        this.summaryBaseDao = summaryBaseDao;
    }

    public void setIndexDao(EventIndexDao indexDao) {
        this.indexDao = indexDao;
    }

    public void setIndexMetadataDao(IndexMetadataDao indexMetadataDao) {
        this.indexMetadataDao = indexMetadataDao;
    }

    public void setIndexedDetailsConfiguration(IndexedDetailsConfiguration indexedDetailsConfiguration) {
        this.indexedDetailsConfiguration = indexedDetailsConfiguration;
    }

    private void deleteStateFile() {
        if (this.indexStateFile.isFile() && !this.indexStateFile.delete()) {
            logger.info("Failed to remove index rebuild state file");
        }
    }

    private static byte[] calculateIndexVersionHash(Map<String, EventDetailItem> detailItems) throws ZepException {
        TreeMap<String, EventDetailItem> sorted = new TreeMap<String, EventDetailItem>(detailItems);
        StringBuilder indexConfigStr = new StringBuilder();
        for (EventDetailItem item : sorted.values()) {
            // Only key and type affect the indexing behavior - ignore changes to display name
            indexConfigStr.append('|');
            indexConfigStr.append(item.getKey());
            indexConfigStr.append('|');
            indexConfigStr.append(item.getType().name());
            indexConfigStr.append('|');
        }
        if (indexConfigStr.length() == 0) {
            return null;
        }
        return DaoUtils.sha1(indexConfigStr.toString());
    }

    public void shutdown() throws InterruptedException {
        this.shutdown = true;
        synchronized (this.lock) {
            this.lock.notifyAll();
        }
        if (rebuildFuture != null) {
            try {
                rebuildFuture.get();
            } catch (ExecutionException e) {
                logger.warn("Error rebuilding event index", e);
            }
        }
        this.executorService.shutdown();
        this.executorService.awaitTermination(0, TimeUnit.SECONDS);
    }

    private void recreateIndexIfNeeded() throws ZepException, InterruptedException {
        final IndexMetadata indexMetadata = indexMetadataDao.findIndexMetadata(indexDao.getName());
        final int numDocs = indexDao.getNumDocs();

        boolean recreateIndex = false;
        IndexRebuildState indexRebuildState = null;

        // Stop the event indexer if it is currently running.
        eventIndexer.stop();

        // Recreate index if we detect an empty index (could have been wiped from disk) - This check only happens once
        if (numDocs == 0) {
            logger.info("Empty index detected.");
            deleteStateFile();
            recreateIndex = true;
        }
        // Recreate index if we detect that the schema was cleared (wiped record of indexing state)
        else if (indexMetadata == null) {
            if (numDocs > 0) {
                logger.info("Inconsistent state between index and database. Clearing index.");
                indexDao.clear();
            }
            deleteStateFile();
            recreateIndex = true;
        }
        // Recreate index if the version number or indexed details changed
        else {
            recreateIndex = false;
            try {
                indexRebuildState = IndexRebuildState.loadState(this.indexStateFile);
            } catch (Exception e) {
                logger.warn("Failed to restore index rebuild state from: " + this.indexStateFile.getAbsolutePath(),
                        e);
            }
            if (indexRebuildState != null) {
                recreateIndex = true;
                if (indexVersionChanged(indexMetadata)) {
                    if (indexRebuildState.getIndexVersion() != IndexConstants.INDEX_VERSION
                            || !Arrays.equals(indexRebuildState.getIndexVersionHash(), this.indexVersionHash)) {
                        // We have state from an previous version / hash - ignore it
                        indexRebuildState = null;
                        deleteStateFile();
                    }
                }
            }
        }

        if (recreateIndex) {
            /*
             * We add dummy index metadata here to ensure that if ZEP is stopped while rebuilding the index, we will be
             * able to restart the indexing process.
             */
            byte[] checksum = new byte[20];
            Arrays.fill(checksum, (byte) 0);
            this.indexMetadataDao.updateIndexVersion(this.indexDao.getName(), 0, checksum);

            // We want to start the event indexer before we start the rebuild (we want both to run in parallel).
            eventIndexer.start(this.configDao.getConfig());

            recreateIndexFromDatabase(indexRebuildState);
        } else {
            // Start the event indexer - we have done all the necessary initialization.
            eventIndexer.start(this.configDao.getConfig());
        }
    }

    private boolean indexVersionChanged(IndexMetadata indexMetadata) {
        boolean changed = false;
        if (IndexConstants.INDEX_VERSION != indexMetadata.getIndexVersion()) {
            logger.info("Index version changed: previous={}, new={}", indexMetadata.getIndexVersion(),
                    IndexConstants.INDEX_VERSION);
            changed = true;
        } else if (!Arrays.equals(this.indexVersionHash, indexMetadata.getIndexVersionHash())) {
            logger.info("Index configuration changed.");
            changed = true;
        }
        return changed;
    }

    private void recreateIndexFromDatabase(IndexRebuildState indexRebuildState) throws ZepException {
        logger.info("Recreating index for table {}", indexDao.getName());
        EventBatchParams nextBatch = null;
        final long throughTime;

        if (indexRebuildState == null) {
            throughTime = System.currentTimeMillis();
            indexRebuildState = new IndexRebuildState(IndexConstants.INDEX_VERSION, this.indexVersionHash,
                    throughTime, null, null);
        } else {
            Long startingLastSeen = indexRebuildState.getStartingLastSeen();
            String startingUuid = indexRebuildState.getStartingUuid();
            if (startingLastSeen != null || startingUuid != null) {
                nextBatch = new EventBatchParams(startingLastSeen, startingUuid);
            }
            throughTime = indexRebuildState.getThroughTime();
            logger.info("Resuming event indexing from: {}", nextBatch);
        }

        int i = 0;
        int numIndexed = 0;
        List<EventSummary> events;
        do {
            ZepConfig config = configDao.getConfig();
            logger.debug("About to configDao.listBatch");
            EventBatch batch = summaryBaseDao.listBatch(nextBatch, throughTime, config.getIndexLimit());
            events = batch.events;
            nextBatch = batch.nextParams;
            i++;
            logger.debug("About to indexDao.indexMany");
            indexDao.indexMany(events);
            logger.debug("Finished indexDao.indexMany");
            numIndexed += events.size();
            if (this.configurationChanged || this.shutdown) {
                break;
            }
            indexDao.commit(); //TODO: perhaps remove this
            indexRebuildState.setStartingUuid(nextBatch.nextUuid);
            indexRebuildState.setStartingLastSeen(nextBatch.nextLastSeen);
            if (i % 25 == 0) {
                logger.info("Indexed {} events on table {}", numIndexed, indexDao.getName());
            }
            try {
                indexRebuildState.save(this.indexStateFile);
            } catch (Exception e) {
                logger.warn(
                        "Failed to save state of index rebuild to file: " + this.indexStateFile.getAbsolutePath(),
                        e);
            }
        } while (nextBatch.nextUuid != null && !this.configurationChanged && !this.shutdown);

        if (this.configurationChanged || this.shutdown) {
            logger.info("Index rebuild aborted");
            return;
        }

        if (numIndexed > 0) {
            indexDao.commit();
            indexMetadataDao.updateIndexVersion(indexDao.getName(), IndexConstants.INDEX_VERSION, indexVersionHash);
        }
        logger.info("Finished recreating index for {} events on table: {}", numIndexed, indexDao.getName());
        deleteStateFile();
    }

    @Override
    public void onApplicationEvent(IndexRebuildRequiredEvent event) {
        this.configurationChanged = true;
        synchronized (this.lock) {
            this.lock.notifyAll();
        }
    }
}