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.client;
019
020import static org.junit.Assert.assertTrue;
021import static org.junit.Assert.fail;
022
023import java.io.IOException;
024import org.apache.hadoop.conf.Configuration;
025import org.apache.hadoop.hbase.HBaseClassTestRule;
026import org.apache.hadoop.hbase.HBaseConfiguration;
027import org.apache.hadoop.hbase.HConstants;
028import org.apache.hadoop.hbase.TableName;
029import org.apache.hadoop.hbase.ipc.HBaseRpcController;
030import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
031import org.apache.hadoop.hbase.testclassification.ClientTests;
032import org.apache.hadoop.hbase.testclassification.SmallTests;
033import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
034import org.junit.ClassRule;
035import org.junit.Rule;
036import org.junit.Test;
037import org.junit.experimental.categories.Category;
038import org.junit.rules.TestName;
039import org.mockito.Mockito;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
044
045import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneResponse;
046import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SnapshotResponse;
047
048/**
049 * Test snapshot logic from the client
050 */
051@Category({ SmallTests.class, ClientTests.class })
052public class TestSnapshotFromAdmin {
053
054  @ClassRule
055  public static final HBaseClassTestRule CLASS_RULE =
056    HBaseClassTestRule.forClass(TestSnapshotFromAdmin.class);
057
058  private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotFromAdmin.class);
059
060  @Rule
061  public TestName name = new TestName();
062
063  /**
064   * Test that the logic for doing 'correct' back-off based on exponential increase and the max-time
065   * passed from the server ensures the correct overall waiting for the snapshot to finish. n
066   */
067  @Test
068  public void testBackoffLogic() throws Exception {
069    final int pauseTime = 100;
070    final int maxWaitTime =
071      HConstants.RETRY_BACKOFF[HConstants.RETRY_BACKOFF.length - 1] * pauseTime;
072    final int numRetries = HConstants.RETRY_BACKOFF.length;
073    // calculate the wait time, if we just do straight backoff (ignoring the expected time from
074    // master)
075    long ignoreExpectedTime = 0;
076    for (int i = 0; i < HConstants.RETRY_BACKOFF.length; i++) {
077      ignoreExpectedTime += HConstants.RETRY_BACKOFF[i] * pauseTime;
078    }
079    // the correct wait time, capping at the maxTime/tries + fudge room
080    final long time = pauseTime * 3L + ((maxWaitTime / numRetries) * 3) + 300L;
081    assertTrue("Capped snapshot wait time isn't less that the uncapped backoff time "
082      + "- further testing won't prove anything.", time < ignoreExpectedTime);
083
084    // setup the mocks
085    ConnectionImplementation mockConnection = Mockito.mock(ConnectionImplementation.class);
086    Configuration conf = HBaseConfiguration.create();
087    // setup the conf to match the expected properties
088    conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, numRetries);
089    conf.setLong("hbase.client.pause", pauseTime);
090
091    // mock the master admin to our mock
092    MasterKeepAliveConnection mockMaster = Mockito.mock(MasterKeepAliveConnection.class);
093    Mockito.when(mockConnection.getConfiguration()).thenReturn(conf);
094    Mockito.when(mockConnection.getMaster()).thenReturn(mockMaster);
095    // we need a real retrying caller
096    RpcRetryingCallerFactory callerFactory = new RpcRetryingCallerFactory(conf);
097    RpcControllerFactory controllerFactory = Mockito.mock(RpcControllerFactory.class);
098    Mockito.when(controllerFactory.newController())
099      .thenReturn(Mockito.mock(HBaseRpcController.class));
100    Mockito.when(mockConnection.getRpcRetryingCallerFactory()).thenReturn(callerFactory);
101    Mockito.when(mockConnection.getRpcControllerFactory()).thenReturn(controllerFactory);
102    // set the max wait time for the snapshot to complete
103    SnapshotResponse response =
104      SnapshotResponse.newBuilder().setExpectedTimeout(maxWaitTime).build();
105    Mockito.when(mockMaster.snapshot((RpcController) Mockito.any(), Mockito.any()))
106      .thenReturn(response);
107    // setup the response
108    IsSnapshotDoneResponse.Builder builder = IsSnapshotDoneResponse.newBuilder();
109    builder.setDone(false);
110    // first five times, we return false, last we get success
111    Mockito.when(mockMaster.isSnapshotDone((RpcController) Mockito.any(), Mockito.any()))
112      .thenReturn(builder.build(), builder.build(), builder.build(), builder.build(),
113        builder.build(), builder.setDone(true).build());
114
115    // setup the admin and run the test
116    Admin admin = new HBaseAdmin(mockConnection);
117    String snapshot = "snapshot";
118    final TableName table = TableName.valueOf(name.getMethodName());
119    // get start time
120    long start = EnvironmentEdgeManager.currentTime();
121    admin.snapshot(snapshot, table);
122    long finish = EnvironmentEdgeManager.currentTime();
123    long elapsed = (finish - start);
124    assertTrue("Elapsed time:" + elapsed + " is more than expected max:" + time, elapsed <= time);
125    admin.close();
126  }
127
128  /**
129   * Make sure that we validate the snapshot name and the table name before we pass anything across
130   * the wire
131   * @throws Exception on failure
132   */
133  @Test
134  public void testValidateSnapshotName() throws Exception {
135    ConnectionImplementation mockConnection = Mockito.mock(ConnectionImplementation.class);
136    Configuration conf = HBaseConfiguration.create();
137    Mockito.when(mockConnection.getConfiguration()).thenReturn(conf);
138    // we need a real retrying caller
139    RpcRetryingCallerFactory callerFactory = new RpcRetryingCallerFactory(conf);
140    RpcControllerFactory controllerFactory = Mockito.mock(RpcControllerFactory.class);
141    Mockito.when(controllerFactory.newController())
142      .thenReturn(Mockito.mock(HBaseRpcController.class));
143    Mockito.when(mockConnection.getRpcRetryingCallerFactory()).thenReturn(callerFactory);
144    Mockito.when(mockConnection.getRpcControllerFactory()).thenReturn(controllerFactory);
145    Admin admin = new HBaseAdmin(mockConnection);
146    // check that invalid snapshot names fail
147    failSnapshotStart(admin, new SnapshotDescription(HConstants.SNAPSHOT_DIR_NAME));
148    failSnapshotStart(admin, new SnapshotDescription("-snapshot"));
149    failSnapshotStart(admin, new SnapshotDescription("snapshot fails"));
150    failSnapshotStart(admin, new SnapshotDescription("snap$hot"));
151    failSnapshotStart(admin, new SnapshotDescription("snap:hot"));
152    // check the table name also get verified
153    failSnapshotDescriptorCreation("snapshot", ".table");
154    failSnapshotDescriptorCreation("snapshot", "-table");
155    failSnapshotDescriptorCreation("snapshot", "table fails");
156    failSnapshotDescriptorCreation("snapshot", "tab%le");
157
158    // mock the master connection
159    MasterKeepAliveConnection master = Mockito.mock(MasterKeepAliveConnection.class);
160    Mockito.when(mockConnection.getMaster()).thenReturn(master);
161    SnapshotResponse response = SnapshotResponse.newBuilder().setExpectedTimeout(0).build();
162    Mockito.when(master.snapshot((RpcController) Mockito.any(), Mockito.any()))
163      .thenReturn(response);
164    IsSnapshotDoneResponse doneResponse = IsSnapshotDoneResponse.newBuilder().setDone(true).build();
165    Mockito.when(master.isSnapshotDone((RpcController) Mockito.any(), Mockito.any()))
166      .thenReturn(doneResponse);
167
168    // make sure that we can use valid names
169    admin.snapshot(new SnapshotDescription("snapshot", TableName.valueOf(name.getMethodName())));
170  }
171
172  private void failSnapshotStart(Admin admin, SnapshotDescription snapshot) throws IOException {
173    try {
174      admin.snapshot(snapshot);
175      fail("Snapshot should not have succeed with name:" + snapshot.getName());
176    } catch (IllegalArgumentException e) {
177      LOG.debug("Correctly failed to start snapshot:" + e.getMessage());
178    }
179  }
180
181  private void failSnapshotDescriptorCreation(final String snapshotName, final String tableName) {
182    try {
183      new SnapshotDescription(snapshotName, tableName);
184      fail("SnapshotDescription should not have succeed with name:" + snapshotName);
185    } catch (IllegalArgumentException e) {
186      LOG.debug("Correctly failed to create SnapshotDescription:" + e.getMessage());
187    }
188  }
189}