/**
 * 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.hadoop.mapreduce.v2.hs;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.v2.api.MRDelegationTokenIdentifier;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.authentication.util.KerberosName;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.delegation.DelegationKey;
import org.junit.Test;

public class TestJHSDelegationTokenSecretManager {

  @Test
  public void testRecovery() throws IOException {
    Configuration conf = new Configuration();
    HistoryServerStateStoreService store =
        new HistoryServerMemStateStoreService();
    store.init(conf);
    store.start();
    JHSDelegationTokenSecretManagerForTest mgr =
        new JHSDelegationTokenSecretManagerForTest(store);
    mgr.startThreads();

    MRDelegationTokenIdentifier tokenId1 = new MRDelegationTokenIdentifier(
        new Text("tokenOwner"), new Text("tokenRenewer"),
        new Text("tokenUser"));
    Token<MRDelegationTokenIdentifier> token1 =
        new Token<MRDelegationTokenIdentifier>(tokenId1, mgr);

    MRDelegationTokenIdentifier tokenId2 = new MRDelegationTokenIdentifier(
        new Text("tokenOwner"), new Text("tokenRenewer"),
        new Text("tokenUser"));
    Token<MRDelegationTokenIdentifier> token2 =
        new Token<MRDelegationTokenIdentifier>(tokenId2, mgr);
    DelegationKey[] keys = mgr.getAllKeys();
    long tokenRenewDate1 = mgr.getAllTokens().get(tokenId1).getRenewDate();
    long tokenRenewDate2 = mgr.getAllTokens().get(tokenId2).getRenewDate();
    mgr.stopThreads();

    mgr = new JHSDelegationTokenSecretManagerForTest(store);
    mgr.recover(store.loadState());
    List<DelegationKey> recoveredKeys = Arrays.asList(mgr.getAllKeys());
    for (DelegationKey key : keys) {
      assertTrue("key missing after recovery", recoveredKeys.contains(key));
    }
    assertTrue("token1 missing", mgr.getAllTokens().containsKey(tokenId1));
    assertEquals("token1 renew date", tokenRenewDate1,
        mgr.getAllTokens().get(tokenId1).getRenewDate());
    assertTrue("token2 missing", mgr.getAllTokens().containsKey(tokenId2));
    assertEquals("token2 renew date", tokenRenewDate2,
        mgr.getAllTokens().get(tokenId2).getRenewDate());

    mgr.startThreads();
    mgr.verifyToken(tokenId1, token1.getPassword());
    mgr.verifyToken(tokenId2, token2.getPassword());
    MRDelegationTokenIdentifier tokenId3 = new MRDelegationTokenIdentifier(
        new Text("tokenOwner"), new Text("tokenRenewer"),
        new Text("tokenUser"));
    Token<MRDelegationTokenIdentifier> token3 =
        new Token<MRDelegationTokenIdentifier>(tokenId3, mgr);
    assertEquals("sequence number restore", tokenId2.getSequenceNumber() + 1,
        tokenId3.getSequenceNumber());
    mgr.cancelToken(token1, "tokenOwner");

    // Testing with full principal name
    MRDelegationTokenIdentifier tokenIdFull = new MRDelegationTokenIdentifier(
        new Text("tokenOwner/localhost@LOCALHOST"), new Text("tokenRenewer"),
        new Text("tokenUser"));
    KerberosName.setRules("RULE:[1:$1]\nRULE:[2:$1]");
    Token<MRDelegationTokenIdentifier> tokenFull = new Token<MRDelegationTokenIdentifier>(
        tokenIdFull, mgr);
    // Negative test
    try {
      mgr.cancelToken(tokenFull, "tokenOwner");
    } catch (AccessControlException ace) {
      assertTrue(ace.getMessage().contains(
          "is not authorized to cancel the token"));
    }
    // Succeed to cancel with full principal
    mgr.cancelToken(tokenFull, tokenIdFull.getOwner().toString());

    long tokenRenewDate3 = mgr.getAllTokens().get(tokenId3).getRenewDate();
    mgr.stopThreads();

    mgr = new JHSDelegationTokenSecretManagerForTest(store);
    mgr.recover(store.loadState());
    assertFalse("token1 should be missing",
        mgr.getAllTokens().containsKey(tokenId1));
    assertTrue("token2 missing", mgr.getAllTokens().containsKey(tokenId2));
    assertEquals("token2 renew date", tokenRenewDate2,
        mgr.getAllTokens().get(tokenId2).getRenewDate());
    assertTrue("token3 missing", mgr.getAllTokens().containsKey(tokenId3));
    assertEquals("token3 renew date", tokenRenewDate3,
        mgr.getAllTokens().get(tokenId3).getRenewDate());

    mgr.startThreads();
    mgr.verifyToken(tokenId2, token2.getPassword());
    mgr.verifyToken(tokenId3, token3.getPassword());
    mgr.stopThreads();
 }

  private static class JHSDelegationTokenSecretManagerForTest
      extends JHSDelegationTokenSecretManager {

    public JHSDelegationTokenSecretManagerForTest(
        HistoryServerStateStoreService store) {
      super(10000, 10000, 10000, 10000, store);
    }

    public Map<MRDelegationTokenIdentifier, DelegationTokenInformation> getAllTokens() {
      return new HashMap<MRDelegationTokenIdentifier, DelegationTokenInformation>(currentTokens);
    }
  }
}
