/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.lens.server.query; import static org.apache.lens.server.api.LensConfConstants.*; import java.io.IOException; import java.util.Calendar; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.apache.lens.cube.metadata.DateUtil; import org.apache.lens.server.LensServices; import org.apache.lens.server.api.error.LensException; import org.apache.lens.server.api.metrics.MetricsService; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import lombok.extern.slf4j.Slf4j; /** * The Class QueryResultPurger - Purges old files in query resultset directory and hdfs output directory. */ @Slf4j public class QueryResultPurger implements Runnable { /** * The resultset retention */ private DateUtil.TimeDiff resultsetRetention; /** * The hdfs output retention */ private DateUtil.TimeDiff hdfsOutputRetention; /** * The query result purger executor */ private ScheduledExecutorService queryResultPurgerExecutor; private Path resultsetPath; private Path hdfsOutputPath; private Configuration conf; /** * The Constant QUERY_RESULT_PURGER_COUNTER. */ public static final String QUERY_RESULT_PURGER_ERROR_COUNTER = "query-result-purger-errors"; /** * The metrics service. */ private MetricsService metricsService; public void init(Configuration configuration) { this.conf = configuration; this.resultsetPath = new Path(conf.get(RESULT_SET_PARENT_DIR, RESULT_SET_PARENT_DIR_DEFAULT)); this.hdfsOutputPath = new Path(resultsetPath.toString(), conf.get(QUERY_HDFS_OUTPUT_PATH, DEFAULT_HDFS_OUTPUT_PATH)); int purgeDelay = conf.getInt(RESULTSET_PURGE_INTERVAL_IN_SECONDS, DEFAULT_RESULTSET_PURGE_INTERVAL_IN_SECONDS); try { String resultSetDiffStr = conf.get(QUERY_RESULTSET_RETENTION, DEFAULT_QUERY_RESULTSET_RETENTION); String hdfsOutputDiffStr = conf.get(QUERY_RESULTSET_RETENTION, DEFAULT_QUERY_RESULTSET_RETENTION); this.resultsetRetention = DateUtil.TimeDiff.parseFrom(resultSetDiffStr); this.hdfsOutputRetention = DateUtil.TimeDiff.parseFrom(hdfsOutputDiffStr); queryResultPurgerExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "QueryResultPurger"); } }); queryResultPurgerExecutor.scheduleWithFixedDelay(this, purgeDelay, purgeDelay, TimeUnit.SECONDS); log.info( "Initialized query result purger with lens resultset retention of {} and hdfs output retention of {}, " + "scheduled to run every {} seconds", resultSetDiffStr, hdfsOutputDiffStr, purgeDelay); } catch (LensException e) { log.error("Error occurred while initializing query result purger", e); } } public void purgePaths(Path path, DateUtil.TimeDiff retention, boolean purgeDirectory) throws IOException { int counter = 0; FileSystem fs = path.getFileSystem(conf); FileStatus[] fileList = fs.listStatus(path); for (FileStatus f : fileList) { if ((f.isFile() || (f.isDirectory() && purgeDirectory)) && canBePurged(f, retention)) { try { if (fs.delete(f.getPath(), true)) { counter++; } else { getMetrics().incrCounter(this.getClass(), QUERY_RESULT_PURGER_ERROR_COUNTER); } } catch (IOException e) { getMetrics().incrCounter(this.getClass(), QUERY_RESULT_PURGER_ERROR_COUNTER); } } } log.info("Purged {} files/directories in {}", counter, path.toString()); } @Override public void run() { try { purgePaths(resultsetPath, resultsetRetention, false); purgePaths(hdfsOutputPath, hdfsOutputRetention, true); } catch (Exception e) { log.error("Error occurred in Query result purger", e); getMetrics().incrCounter(this.getClass(), QUERY_RESULT_PURGER_ERROR_COUNTER); } } private boolean canBePurged(FileStatus f, DateUtil.TimeDiff retention) { return f.getModificationTime() < retention.negativeOffsetFrom(Calendar.getInstance().getTime()).getTime(); } /** * Stops query result purger */ public void shutdown() { if (null != queryResultPurgerExecutor) { queryResultPurgerExecutor.shutdown(); log.info("Stopped query result purger."); } } /** * Checks the status of executor service * * @return */ public boolean isHealthy() { if (null == queryResultPurgerExecutor || queryResultPurgerExecutor.isShutdown() || queryResultPurgerExecutor.isTerminated()) { return false; } return true; } private MetricsService getMetrics() { if (metricsService == null) { metricsService = LensServices.get().getService(MetricsService.NAME); if (metricsService == null) { throw new NullPointerException("Could not get metrics service"); } } return metricsService; } public void awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { if (null != queryResultPurgerExecutor) { queryResultPurgerExecutor.awaitTermination(timeout, unit); } } }