001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master.cleaner;
019
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.List;
023import org.apache.hadoop.conf.Configuration;
024import org.apache.hadoop.hbase.HBaseTestingUtil;
025import org.apache.hadoop.hbase.Stoppable;
026import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
027import org.apache.hadoop.hbase.testclassification.MasterTests;
028import org.apache.hadoop.hbase.testclassification.SmallTests;
029import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
030import org.junit.jupiter.api.Tag;
031import org.junit.jupiter.api.Test;
032import org.mockito.Mockito;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos;
037
038/**
039 * Tests for SnapshotsCleanerChore
040 */
041@Tag(MasterTests.TAG)
042@Tag(SmallTests.TAG)
043public class TestSnapshotCleanerChore {
044
045  private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotCleanerChore.class);
046
047  private static final HBaseTestingUtil HBASE_TESTING_UTILITY = new HBaseTestingUtil();
048
049  private SnapshotManager snapshotManager;
050
051  private Configuration getSnapshotCleanerConf() {
052    Configuration conf = HBASE_TESTING_UTILITY.getConfiguration();
053    conf.setInt("hbase.master.cleaner.snapshot.interval", 100);
054    return conf;
055  }
056
057  @Test
058  public void testSnapshotCleanerWithoutAnyCompletedSnapshot() throws IOException {
059    snapshotManager = Mockito.mock(SnapshotManager.class);
060    Stoppable stopper = new StoppableImplementation();
061    Configuration conf = getSnapshotCleanerConf();
062    SnapshotCleanerChore snapshotCleanerChore =
063      new SnapshotCleanerChore(stopper, conf, snapshotManager);
064    try {
065      snapshotCleanerChore.chore();
066    } finally {
067      stopper.stop("Stopping Test Stopper");
068    }
069    Mockito.verify(snapshotManager, Mockito.times(0)).deleteSnapshot(Mockito.any());
070  }
071
072  @Test
073  public void testSnapshotCleanerWithNoTtlExpired() throws IOException {
074    snapshotManager = Mockito.mock(SnapshotManager.class);
075    Stoppable stopper = new StoppableImplementation();
076    Configuration conf = getSnapshotCleanerConf();
077    SnapshotCleanerChore snapshotCleanerChore =
078      new SnapshotCleanerChore(stopper, conf, snapshotManager);
079    List<SnapshotProtos.SnapshotDescription> snapshotDescriptionList = new ArrayList<>();
080    snapshotDescriptionList.add(getSnapshotDescription(-2, "snapshot01", "table01",
081      EnvironmentEdgeManager.currentTime() - 100000));
082    snapshotDescriptionList.add(
083      getSnapshotDescription(10, "snapshot02", "table02", EnvironmentEdgeManager.currentTime()));
084    Mockito.when(snapshotManager.getCompletedSnapshots()).thenReturn(snapshotDescriptionList);
085    try {
086      LOG.info("2 Snapshots are completed but TTL is not expired for any of them");
087      snapshotCleanerChore.chore();
088    } finally {
089      stopper.stop("Stopping Test Stopper");
090    }
091    Mockito.verify(snapshotManager, Mockito.times(0)).deleteSnapshot(Mockito.any());
092  }
093
094  @Test
095  public void testSnapshotCleanerWithSomeTtlExpired() throws IOException {
096    snapshotManager = Mockito.mock(SnapshotManager.class);
097    Stoppable stopper = new StoppableImplementation();
098    Configuration conf = getSnapshotCleanerConf();
099    SnapshotCleanerChore snapshotCleanerChore =
100      new SnapshotCleanerChore(stopper, conf, snapshotManager);
101    List<SnapshotProtos.SnapshotDescription> snapshotDescriptionList = new ArrayList<>();
102    snapshotDescriptionList.add(getSnapshotDescription(10, "snapshot01", "table01", 1));
103    snapshotDescriptionList.add(getSnapshotDescription(5, "snapshot02", "table02", 2));
104    snapshotDescriptionList.add(
105      getSnapshotDescription(30, "snapshot01", "table01", EnvironmentEdgeManager.currentTime()));
106    snapshotDescriptionList.add(
107      getSnapshotDescription(0, "snapshot02", "table02", EnvironmentEdgeManager.currentTime()));
108    snapshotDescriptionList.add(
109      getSnapshotDescription(40, "snapshot03", "table03", EnvironmentEdgeManager.currentTime()));
110    Mockito.when(snapshotManager.getCompletedSnapshots()).thenReturn(snapshotDescriptionList);
111    try {
112      LOG.info("5 Snapshots are completed. TTL is expired for 2 them. Going to delete them");
113      snapshotCleanerChore.chore();
114    } finally {
115      stopper.stop("Stopping Test Stopper");
116    }
117    Mockito.verify(snapshotManager, Mockito.times(2)).deleteSnapshot(Mockito.any());
118  }
119
120  @Test
121  public void testSnapshotCleanerWithReadIOE() throws IOException {
122    snapshotManager = Mockito.mock(SnapshotManager.class);
123    Stoppable stopper = new StoppableImplementation();
124    Configuration conf = new HBaseTestingUtil().getConfiguration();
125    SnapshotCleanerChore snapshotCleanerChore =
126      new SnapshotCleanerChore(stopper, conf, snapshotManager);
127    Mockito.when(snapshotManager.getCompletedSnapshots()).thenThrow(IOException.class);
128    try {
129      LOG.info("While getting completed Snapshots, IOException would occur. Hence, No Snapshot"
130        + " should be deleted");
131      snapshotCleanerChore.chore();
132    } finally {
133      stopper.stop("Stopping Test Stopper");
134    }
135    Mockito.verify(snapshotManager, Mockito.times(0)).deleteSnapshot(Mockito.any());
136  }
137
138  @Test
139  public void testSnapshotChoreWithTtlOutOfRange() throws IOException {
140    snapshotManager = Mockito.mock(SnapshotManager.class);
141    Stoppable stopper = new StoppableImplementation();
142    Configuration conf = getSnapshotCleanerConf();
143    List<SnapshotProtos.SnapshotDescription> snapshotDescriptionList = new ArrayList<>();
144    snapshotDescriptionList.add(getSnapshotDescription(Long.MAX_VALUE, "snapshot01", "table01", 1));
145    snapshotDescriptionList.add(getSnapshotDescription(5, "snapshot02", "table02", 2));
146    Mockito.when(snapshotManager.getCompletedSnapshots()).thenReturn(snapshotDescriptionList);
147    SnapshotCleanerChore snapshotCleanerChore =
148      new SnapshotCleanerChore(stopper, conf, snapshotManager);
149    try {
150      LOG.info("Snapshot Chore is disabled. No cleanup performed for Expired Snapshots");
151      snapshotCleanerChore.chore();
152    } finally {
153      stopper.stop("Stopping Test Stopper");
154    }
155    Mockito.verify(snapshotManager, Mockito.times(1)).getCompletedSnapshots();
156  }
157
158  private SnapshotProtos.SnapshotDescription getSnapshotDescription(final long ttl,
159    final String snapshotName, final String tableName, final long snapshotCreationTime) {
160    SnapshotProtos.SnapshotDescription.Builder snapshotDescriptionBuilder =
161      SnapshotProtos.SnapshotDescription.newBuilder();
162    snapshotDescriptionBuilder.setTtl(ttl);
163    snapshotDescriptionBuilder.setName(snapshotName);
164    snapshotDescriptionBuilder.setTable(tableName);
165    snapshotDescriptionBuilder.setType(SnapshotProtos.SnapshotDescription.Type.FLUSH);
166    snapshotDescriptionBuilder.setCreationTime(snapshotCreationTime);
167    return snapshotDescriptionBuilder.build();
168  }
169
170  /**
171   * Simple helper class that just keeps track of whether or not its stopped.
172   */
173  private static class StoppableImplementation implements Stoppable {
174
175    private volatile boolean stop = false;
176
177    @Override
178    public void stop(String why) {
179      this.stop = true;
180    }
181
182    @Override
183    public boolean isStopped() {
184      return this.stop;
185    }
186
187  }
188
189}