package org.apache.helix.controller.stages;

/*
 * 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.
 */

import java.util.Map;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.helix.HelixDataAccessor;
import org.apache.helix.HelixProperty;
import org.apache.helix.MockAccessor;
import org.apache.helix.PropertyKey;
import org.apache.helix.controller.dataproviders.ResourceControllerDataProvider;
import org.apache.helix.model.ClusterConfig;
import org.apache.helix.model.IdealState;
import org.apache.helix.model.IdealState.IdealStateProperty;
import org.apache.helix.model.IdealState.RebalanceMode;
import org.apache.helix.model.Resource;
import org.apache.helix.model.StateModelDefinition;
import org.apache.helix.tools.StateModelConfigGenerator;
import org.testng.Assert;
import org.testng.annotations.Test;

public class TestResourceValidationStage {
  private static final String PARTICIPANT = "localhost_1234";
  private static final String STATE = "OFFLINE";

  @Test
  public void testIdealStateValidity() throws Exception {
    MockAccessor accessor = new MockAccessor();
    accessor.setProperty(accessor.keyBuilder().clusterConfig(), new ClusterConfig("TestCluster"));

    // create some ideal states
    String masterSlaveCustomResource = "masterSlaveCustomResource";
    String onlineOfflineFullAutoResource = "onlineOfflineFullAutoResource";
    String masterSlaveSemiAutoInvalidResource = "masterSlaveSemiAutoInvalidResource";
    createIS(accessor, masterSlaveCustomResource, "MasterSlave", RebalanceMode.CUSTOMIZED);
    createIS(accessor, onlineOfflineFullAutoResource, "OnlineOffline", RebalanceMode.FULL_AUTO);
    createIS(accessor, masterSlaveSemiAutoInvalidResource, "MasterSlave", RebalanceMode.SEMI_AUTO);

    // create some ideal state specs
    createISSpec(accessor, masterSlaveCustomResource + "_spec", "MasterSlave",
        RebalanceMode.CUSTOMIZED);
    createISSpec(accessor, onlineOfflineFullAutoResource + "_spec", "OnlineOffline",
        RebalanceMode.FULL_AUTO);

    // add some state models
    addStateModels(accessor);

    // refresh the cache
    ClusterEvent event = new ClusterEvent(ClusterEventType.Unknown);
    ResourceControllerDataProvider cache = new ResourceControllerDataProvider();
    cache.refresh(accessor);
    event.addAttribute(AttributeName.ControllerDataProvider.name(), cache);

    // run resource computation
    new ResourceComputationStage().process(event);
    Map<String, Resource> resourceMap =
        event.getAttribute(AttributeName.RESOURCES_TO_REBALANCE.name());
    Assert.assertTrue(resourceMap.containsKey(masterSlaveCustomResource));
    Assert.assertTrue(resourceMap.containsKey(onlineOfflineFullAutoResource));
    Assert.assertTrue(resourceMap.containsKey(masterSlaveSemiAutoInvalidResource));

    // run resource validation
    new ResourceValidationStage().process(event);
    Map<String, Resource> finalResourceMap =
        event.getAttribute(AttributeName.RESOURCES.name());
    Assert.assertTrue(finalResourceMap.containsKey(masterSlaveCustomResource));
    Assert.assertTrue(finalResourceMap.containsKey(onlineOfflineFullAutoResource));
    Assert.assertFalse(finalResourceMap.containsKey(masterSlaveSemiAutoInvalidResource));
  }

  @Test
  public void testNoSpec() throws Exception {
    MockAccessor accessor = new MockAccessor();
    accessor.setProperty(accessor.keyBuilder().clusterConfig(), new ClusterConfig("TestCluster"));

    // create an ideal state and no spec
    String masterSlaveCustomResource = "masterSlaveCustomResource";
    createIS(accessor, masterSlaveCustomResource, "MasterSlave", RebalanceMode.CUSTOMIZED);

    // add some state models
    addStateModels(accessor);

    // refresh the cache
    ClusterEvent event = new ClusterEvent(ClusterEventType.Unknown);
    ResourceControllerDataProvider cache = new ResourceControllerDataProvider();
    cache.refresh(accessor);
    event.addAttribute(AttributeName.ControllerDataProvider.name(), cache);

    // run resource computation
    new ResourceComputationStage().process(event);
    Map<String, Resource> resourceMap = event.getAttribute(AttributeName.RESOURCES.name());
    Assert.assertTrue(resourceMap.containsKey(masterSlaveCustomResource));

    // run resource validation
    new ResourceValidationStage().process(event);
    Map<String, Resource> finalResourceMap = event.getAttribute(AttributeName.RESOURCES.name());
    Assert.assertTrue(finalResourceMap.containsKey(masterSlaveCustomResource));
  }

  @Test
  public void testMissingStateModel() throws Exception {
    MockAccessor accessor = new MockAccessor();
    accessor.setProperty(accessor.keyBuilder().clusterConfig(), new ClusterConfig("TestCluster"));
    // create an ideal state and no spec
    String masterSlaveCustomResource = "masterSlaveCustomResource";
    String leaderStandbyCustomResource = "leaderStandbyCustomResource";
    createIS(accessor, masterSlaveCustomResource, "MasterSlave", RebalanceMode.CUSTOMIZED);
    createIS(accessor, leaderStandbyCustomResource, "LeaderStandby", RebalanceMode.CUSTOMIZED);

    // add some state models (but not leader standby)
    addStateModels(accessor);

    // refresh the cache
    ClusterEvent event = new ClusterEvent(ClusterEventType.Unknown);
    ResourceControllerDataProvider cache = new ResourceControllerDataProvider();
    cache.refresh(accessor);
    event.addAttribute(AttributeName.ControllerDataProvider.name(), cache);

    // run resource computation
    new ResourceComputationStage().process(event);
    Map<String, Resource> resourceMap = event.getAttribute(AttributeName.RESOURCES.name());
    Assert.assertTrue(resourceMap.containsKey(masterSlaveCustomResource));
    Assert.assertTrue(resourceMap.containsKey(leaderStandbyCustomResource));

    // run resource validation
    new ResourceValidationStage().process(event);
    Map<String, Resource> finalResourceMap = event.getAttribute(AttributeName.RESOURCES.name());
    Assert.assertTrue(finalResourceMap.containsKey(masterSlaveCustomResource));
    Assert.assertFalse(finalResourceMap.containsKey(leaderStandbyCustomResource));
  }

  private void createIS(HelixDataAccessor accessor, String resourceId, String stateModelDefRef,
      RebalanceMode rebalanceMode) {
    IdealState idealState = new IdealState(resourceId);
    idealState.setRebalanceMode(rebalanceMode);
    idealState.setStateModelDefRef(stateModelDefRef);
    idealState.setNumPartitions(1);
    idealState.setReplicas("1");
    idealState.getRecord().setListField(resourceId + "_0", ImmutableList.of(PARTICIPANT));
    idealState.getRecord().setMapField(resourceId + "_0", ImmutableMap.of(PARTICIPANT, STATE));
    accessor.setProperty(accessor.keyBuilder().idealStates(resourceId), idealState);
  }

  private void createISSpec(HelixDataAccessor accessor, String specId, String stateModelDefRef,
      RebalanceMode rebalanceMode) {
    PropertyKey propertyKey = accessor.keyBuilder().clusterConfig();
    HelixProperty property = accessor.getProperty(propertyKey);
    if (property == null) {
      property = new HelixProperty("sampleClusterConfig");
    }
    String key = "IdealStateRule!" + specId;
    String value =
        IdealStateProperty.REBALANCE_MODE.toString() + "=" + rebalanceMode.toString() + ","
            + IdealStateProperty.STATE_MODEL_DEF_REF.toString() + "=" + stateModelDefRef;
    property.getRecord().setSimpleField(key, value);
    accessor.setProperty(propertyKey, property);
  }

  private void addStateModels(HelixDataAccessor accessor) {
    StateModelDefinition masterSlave =
        new StateModelDefinition(StateModelConfigGenerator.generateConfigForMasterSlave());
    accessor.setProperty(accessor.keyBuilder().stateModelDef(masterSlave.getId()), masterSlave);
    StateModelDefinition onlineOffline =
        new StateModelDefinition(StateModelConfigGenerator.generateConfigForOnlineOffline());
    accessor.setProperty(accessor.keyBuilder().stateModelDef(onlineOffline.getId()), onlineOffline);
  }
}
