/*
 * 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.ambari.server.controller.internal;

import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.H2DatabaseCleaner;
import org.apache.ambari.server.Role;
import org.apache.ambari.server.RoleCommand;
import org.apache.ambari.server.actionmanager.ActionManager;
import org.apache.ambari.server.actionmanager.ExecutionCommandWrapper;
import org.apache.ambari.server.actionmanager.ExecutionCommandWrapperFactory;
import org.apache.ambari.server.actionmanager.HostRoleCommand;
import org.apache.ambari.server.actionmanager.HostRoleStatus;
import org.apache.ambari.server.actionmanager.Stage;
import org.apache.ambari.server.agent.ExecutionCommand;
import org.apache.ambari.server.agent.ExecutionCommand.KeyNames;
import org.apache.ambari.server.agent.stomp.AgentConfigsHolder;
import org.apache.ambari.server.audit.AuditLogger;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.controller.AmbariManagementController;
import org.apache.ambari.server.controller.AmbariServer;
import org.apache.ambari.server.controller.KerberosHelper;
import org.apache.ambari.server.controller.KerberosHelperImpl;
import org.apache.ambari.server.controller.ResourceProviderFactory;
import org.apache.ambari.server.controller.UpdateConfigurationPolicy;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.Request;
import org.apache.ambari.server.controller.spi.RequestStatus;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.Resource.Type;
import org.apache.ambari.server.controller.spi.ResourceProvider;
import org.apache.ambari.server.controller.spi.SystemException;
import org.apache.ambari.server.controller.utilities.PredicateBuilder;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
import org.apache.ambari.server.orm.GuiceJpaInitializer;
import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
import org.apache.ambari.server.orm.dao.ExecutionCommandDAO;
import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
import org.apache.ambari.server.orm.dao.RequestDAO;
import org.apache.ambari.server.orm.dao.StackDAO;
import org.apache.ambari.server.orm.dao.StageDAO;
import org.apache.ambari.server.orm.dao.UpgradeDAO;
import org.apache.ambari.server.orm.entities.ExecutionCommandEntity;
import org.apache.ambari.server.orm.entities.HostRoleCommandEntity;
import org.apache.ambari.server.orm.entities.RepoDefinitionEntity;
import org.apache.ambari.server.orm.entities.RepoOsEntity;
import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
import org.apache.ambari.server.orm.entities.RequestEntity;
import org.apache.ambari.server.orm.entities.ServiceConfigEntity;
import org.apache.ambari.server.orm.entities.StackEntity;
import org.apache.ambari.server.orm.entities.StageEntity;
import org.apache.ambari.server.orm.entities.UpgradeEntity;
import org.apache.ambari.server.orm.entities.UpgradeGroupEntity;
import org.apache.ambari.server.orm.entities.UpgradeHistoryEntity;
import org.apache.ambari.server.orm.entities.UpgradeItemEntity;
import org.apache.ambari.server.security.TestAuthenticationFactory;
import org.apache.ambari.server.serveraction.upgrades.AutoSkipFailedSummaryAction;
import org.apache.ambari.server.serveraction.upgrades.ConfigureAction;
import org.apache.ambari.server.stack.upgrade.ConfigureTask;
import org.apache.ambari.server.stack.upgrade.Direction;
import org.apache.ambari.server.stack.upgrade.RegenerateKeytabsTask;
import org.apache.ambari.server.stack.upgrade.orchestrate.UpgradeContext;
import org.apache.ambari.server.stack.upgrade.orchestrate.UpgradeHelper;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.Config;
import org.apache.ambari.server.state.ConfigFactory;
import org.apache.ambari.server.state.ConfigHelper;
import org.apache.ambari.server.state.Host;
import org.apache.ambari.server.state.HostState;
import org.apache.ambari.server.state.SecurityType;
import org.apache.ambari.server.state.Service;
import org.apache.ambari.server.state.ServiceComponent;
import org.apache.ambari.server.state.ServiceComponentHost;
import org.apache.ambari.server.state.StackId;
import org.apache.ambari.server.state.UpgradeState;
import org.apache.ambari.server.topology.TopologyManager;
import org.apache.ambari.server.utils.StageUtils;
import org.apache.ambari.server.view.ViewRegistry;
import org.apache.ambari.spi.RepositoryType;
import org.apache.ambari.spi.upgrade.UpgradeType;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.easymock.EasyMockSupport;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.core.context.SecurityContextHolder;

import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.util.Modules;

/**
 * UpgradeResourceDefinition tests.
 */
public class UpgradeResourceProviderTest extends EasyMockSupport {

  private UpgradeDAO upgradeDao = null;
  private RequestDAO requestDao = null;
  private RepositoryVersionDAO repoVersionDao = null;
  private Injector injector;
  private Clusters clusters;
  private Cluster cluster;
  private AmbariManagementController amc;
  private ConfigHelper configHelper;
  private AgentConfigsHolder agentConfigsHolder = createNiceMock(AgentConfigsHolder.class);
  private StackDAO stackDAO;
  private TopologyManager topologyManager;
  private ConfigFactory configFactory;
  private HostRoleCommandDAO hrcDAO;
  private KerberosHelper kerberosHelperMock = createNiceMock(KerberosHelper.class);

  RepositoryVersionEntity repoVersionEntity2110;
  RepositoryVersionEntity repoVersionEntity2111;
  RepositoryVersionEntity repoVersionEntity2112;
  RepositoryVersionEntity repoVersionEntity2200;

  /**
   * Creates a single host cluster with ZOOKEEPER_SERVER and ZOOKEEPER_CLIENT on
   * {@link #repoVersionEntity2110}.
   *
   * @throws Exception
   */
  @Before
  public void before() throws Exception {
    SecurityContextHolder.getContext().setAuthentication(
        TestAuthenticationFactory.createAdministrator());

    // setup the config helper for placeholder resolution
    configHelper = EasyMock.createNiceMock(ConfigHelper.class);

    expect(
        configHelper.getPlaceholderValueFromDesiredConfigurations(
            EasyMock.anyObject(Cluster.class), EasyMock.eq("{{foo/bar}}"))).andReturn(
        "placeholder-rendered-properly").anyTimes();

    expect(
        configHelper.getDefaultProperties(EasyMock.anyObject(StackId.class),
            EasyMock.anyString())).andReturn(
        new HashMap<>()).anyTimes();

    expect(
        configHelper.getChangedConfigTypes(EasyMock.anyObject(Cluster.class), EasyMock.anyObject(ServiceConfigEntity.class),
            EasyMock.anyLong(), EasyMock.anyLong(), EasyMock.anyString())).andReturn(
        Collections.emptyMap()).anyTimes();
    replay(configHelper);

    InMemoryDefaultTestModule module = new InMemoryDefaultTestModule();

    // create an injector which will inject the mocks
    injector = Guice.createInjector(
        Modules.override(module).with(new MockModule()));

    H2DatabaseCleaner.resetSequences(injector);
    injector.getInstance(GuiceJpaInitializer.class);

    amc = injector.getInstance(AmbariManagementController.class);
    configFactory = injector.getInstance(ConfigFactory.class);

    Field field = AmbariServer.class.getDeclaredField("clusterController");
    field.setAccessible(true);
    field.set(null, amc);

    stackDAO = injector.getInstance(StackDAO.class);
    upgradeDao = injector.getInstance(UpgradeDAO.class);
    requestDao = injector.getInstance(RequestDAO.class);
    repoVersionDao = injector.getInstance(RepositoryVersionDAO.class);
    hrcDAO = injector.getInstance(HostRoleCommandDAO.class);

    AmbariEventPublisher publisher = EasyMock.createNiceMock(AmbariEventPublisher.class);
    replay(publisher);
    ViewRegistry.initInstance(new ViewRegistry(publisher));

    // TODO AMARI-12698, this file is attempting to check RU on version 2.1.1, which doesn't support it
    // because it has no upgrade packs. We should use correct versions that have stacks.
    // For now, Ignore the tests that fail.
    StackEntity stackEntity211 = stackDAO.find("HDP", "2.1.1");
    StackEntity stackEntity220 = stackDAO.find("HDP", "2.2.0");
    StackId stack211 = new StackId(stackEntity211);

    repoVersionEntity2110 = new RepositoryVersionEntity();
    repoVersionEntity2110.setDisplayName("My New Version 1");
    repoVersionEntity2110.addRepoOsEntities(createTestOperatingSystems());
    repoVersionEntity2110.setStack(stackEntity211);
    repoVersionEntity2110.setVersion("2.1.1.0");
    repoVersionDao.create(repoVersionEntity2110);

    repoVersionEntity2111 = new RepositoryVersionEntity();
    repoVersionEntity2111.setDisplayName("My New Version 2 for minor upgrade");
    repoVersionEntity2111.addRepoOsEntities(createTestOperatingSystems());
    repoVersionEntity2111.setStack(stackEntity211);
    repoVersionEntity2111.setVersion("2.1.1.1");
    repoVersionDao.create(repoVersionEntity2111);

    repoVersionEntity2112 = new RepositoryVersionEntity();
    repoVersionEntity2112.setDisplayName("My New Version 3 for patch upgrade");
    repoVersionEntity2112.addRepoOsEntities(createTestOperatingSystems());
    repoVersionEntity2112.setStack(stackEntity211);
    repoVersionEntity2112.setVersion("2.1.1.2");
    repoVersionEntity2112.setType(RepositoryType.PATCH);
    repoVersionEntity2112.setVersionXml("");
    repoVersionDao.create(repoVersionEntity2112);

    repoVersionEntity2200 = new RepositoryVersionEntity();
    repoVersionEntity2200.setDisplayName("My New Version 4 for major upgrade");
    repoVersionEntity2200.addRepoOsEntities(createTestOperatingSystems());
    repoVersionEntity2200.setStack(stackEntity220);
    repoVersionEntity2200.setVersion("2.2.0.0");
    repoVersionDao.create(repoVersionEntity2200);

    clusters = injector.getInstance(Clusters.class);

    clusters.addCluster("c1", stack211);
    cluster = clusters.getCluster("c1");

    clusters.addHost("h1");
    Host host = clusters.getHost("h1");
    Map<String, String> hostAttributes = new HashMap<>();
    hostAttributes.put("os_family", "redhat");
    hostAttributes.put("os_release_version", "6.3");
    host.setHostAttributes(hostAttributes);
    host.setState(HostState.HEALTHY);

    clusters.mapHostToCluster("h1", "c1");

    // add a single ZK server and client on 2.1.1.0
    Service service = cluster.addService("ZOOKEEPER", repoVersionEntity2110);
    ServiceComponent component = service.addServiceComponent("ZOOKEEPER_SERVER");
    ServiceComponentHost sch = component.addServiceComponentHost("h1");
    sch.setVersion("2.1.1.0");

    component = service.addServiceComponent("ZOOKEEPER_CLIENT");
    sch = component.addServiceComponentHost("h1");
    sch.setVersion("2.1.1.0");

    Configuration configuration = injector.getInstance(Configuration.class);
    configuration.setProperty("upgrade.parameter.zk-server.timeout", "824");

    topologyManager = injector.getInstance(TopologyManager.class);
    StageUtils.setTopologyManager(topologyManager);
    StageUtils.setConfiguration(configuration);
    ActionManager.setTopologyManager(topologyManager);
    EasyMock.replay(injector.getInstance(AuditLogger.class));
  }

  private List<RepoOsEntity> createTestOperatingSystems() {
    List<RepoOsEntity> operatingSystems = new ArrayList<>();
    RepoDefinitionEntity repoDefinitionEntity1 = new RepoDefinitionEntity();
    repoDefinitionEntity1.setRepoID("HDP-UTILS");
    repoDefinitionEntity1.setBaseUrl("");
    repoDefinitionEntity1.setRepoName("HDP-UTILS");
    RepoDefinitionEntity repoDefinitionEntity2 = new RepoDefinitionEntity();
    repoDefinitionEntity2.setRepoID("HDP");
    repoDefinitionEntity2.setBaseUrl("");
    repoDefinitionEntity2.setRepoName("HDP");
    RepoOsEntity repoOsEntity = new RepoOsEntity();
    repoOsEntity.setFamily("redhat6");
    repoOsEntity.setAmbariManaged(true);
    repoOsEntity.addRepoDefinition(repoDefinitionEntity1);
    repoOsEntity.addRepoDefinition(repoDefinitionEntity2);
    operatingSystems.add(repoOsEntity);
    return operatingSystems;
  }

  @After
  public void after() throws AmbariException, SQLException {
    H2DatabaseCleaner.clearDatabaseAndStopPersistenceService(injector);
    EasyMock.reset(injector.getInstance(AuditLogger.class));
    injector = null;
  }

  /**
   * Obtain request id from the {@code RequestStatus}
   *
   * @param requestStatus reqult of the {@code createResources}
   * @return id of the request
   */
  private long getRequestId(RequestStatus requestStatus) {
    assertEquals(1, requestStatus.getAssociatedResources().size());
    Resource r = requestStatus.getAssociatedResources().iterator().next();
    String id = r.getPropertyValue("Upgrade/request_id").toString();
    return Long.parseLong(id);
  }

  @Test
  public void testCreateResourcesWithAutoSkipFailures() throws Exception {
    Cluster cluster = clusters.getCluster("c1");

    Map<String, Object> requestProps = new HashMap<>();
    requestProps.put(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME, "c1");
    requestProps.put(UpgradeResourceProvider.UPGRADE_REPO_VERSION_ID, String.valueOf(repoVersionEntity2200.getId()));
    requestProps.put(UpgradeResourceProvider.UPGRADE_PACK, "upgrade_test");
    requestProps.put(UpgradeResourceProvider.UPGRADE_TYPE, UpgradeType.ROLLING.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_FAILURES, Boolean.TRUE.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_SC_FAILURES, Boolean.TRUE.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_MANUAL_VERIFICATION, Boolean.FALSE.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_PREREQUISITE_CHECKS, Boolean.TRUE.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_DIRECTION, Direction.UPGRADE.name());

    ResourceProvider upgradeResourceProvider = createProvider(amc);
    Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
    upgradeResourceProvider.createResources(request);

    List<UpgradeEntity> upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
    assertEquals(1, upgrades.size());

    UpgradeEntity entity = upgrades.get(0);
    assertEquals(cluster.getClusterId(), entity.getClusterId().longValue());

    List<UpgradeGroupEntity> upgradeGroups = entity.getUpgradeGroups();
    assertEquals(3, upgradeGroups.size());

    UpgradeGroupEntity preClusterGroup = upgradeGroups.get(0);
    assertEquals("PRE_CLUSTER", preClusterGroup.getName());

    List<UpgradeItemEntity> preClusterUpgradeItems = preClusterGroup.getItems();
    assertEquals(2, preClusterUpgradeItems.size());
    assertEquals("Foo", parseSingleMessage(preClusterUpgradeItems.get(0).getText()));
    assertEquals("Foo", parseSingleMessage(preClusterUpgradeItems.get(1).getText()));

    UpgradeGroupEntity zookeeperGroup = upgradeGroups.get(1);
    assertEquals("ZOOKEEPER", zookeeperGroup.getName());

    List<UpgradeItemEntity> zookeeperUpgradeItems = zookeeperGroup.getItems();
    assertEquals(5, zookeeperUpgradeItems.size());

    assertEquals("This is a manual task with a placeholder of placeholder-rendered-properly",
        parseSingleMessage(zookeeperUpgradeItems.get(0).getText()));
    assertEquals("Restarting ZooKeeper Server on h1", zookeeperUpgradeItems.get(1).getText());
    assertEquals("Updating configuration zookeeper-newconfig",
        zookeeperUpgradeItems.get(2).getText());
    assertEquals("Service Check ZooKeeper", zookeeperUpgradeItems.get(3).getText());
    assertTrue(zookeeperUpgradeItems.get(4).getText().contains("There are failures that were automatically skipped"));

    // the last upgrade item is the skipped failure check
    UpgradeItemEntity skippedFailureCheck = zookeeperUpgradeItems.get(zookeeperUpgradeItems.size() - 1);
    skippedFailureCheck.getTasks().contains(AutoSkipFailedSummaryAction.class.getName());

    UpgradeGroupEntity postClusterGroup = upgradeGroups.get(2);
    assertEquals("POST_CLUSTER", postClusterGroup.getName());

    List<UpgradeItemEntity> postClusterUpgradeItems = postClusterGroup.getItems();
    assertEquals(2, postClusterUpgradeItems.size());
    assertEquals("Please confirm you are ready to finalize", parseSingleMessage(postClusterUpgradeItems.get(0).getText()));
    assertEquals("Save Cluster State", postClusterUpgradeItems.get(1).getText());
  }

  @Test
  public void testCreateResourcesWithAutoSkipManualVerification() throws Exception {
    Cluster cluster = clusters.getCluster("c1");

    Map<String, Object> requestProps = new HashMap<>();
    requestProps.put(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME, "c1");
    requestProps.put(UpgradeResourceProvider.UPGRADE_REPO_VERSION_ID, String.valueOf(repoVersionEntity2200.getId()));
    requestProps.put(UpgradeResourceProvider.UPGRADE_PACK, "upgrade_test");
    requestProps.put(UpgradeResourceProvider.UPGRADE_TYPE, UpgradeType.ROLLING.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_MANUAL_VERIFICATION, Boolean.TRUE.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_PREREQUISITE_CHECKS, Boolean.TRUE.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_DIRECTION, Direction.UPGRADE.name());

    ResourceProvider upgradeResourceProvider = createProvider(amc);
    Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
    upgradeResourceProvider.createResources(request);

    List<UpgradeEntity> upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
    assertEquals(1, upgrades.size());

    UpgradeEntity entity = upgrades.get(0);
    assertEquals(cluster.getClusterId(), entity.getClusterId().longValue());

    List<UpgradeGroupEntity> upgradeGroups = entity.getUpgradeGroups();
    assertEquals(2, upgradeGroups.size());

    UpgradeGroupEntity zookeeperGroup = upgradeGroups.get(0);
    assertEquals("ZOOKEEPER", zookeeperGroup.getName());

    List<UpgradeItemEntity> zookeeperUpgradeItems = zookeeperGroup.getItems();
    assertEquals(3, zookeeperUpgradeItems.size());
    assertEquals("Restarting ZooKeeper Server on h1", zookeeperUpgradeItems.get(0).getText());
    assertEquals("Updating configuration zookeeper-newconfig",
        zookeeperUpgradeItems.get(1).getText());
    assertEquals("Service Check ZooKeeper", zookeeperUpgradeItems.get(2).getText());

    UpgradeGroupEntity postClusterGroup = upgradeGroups.get(1);
    assertEquals("POST_CLUSTER", postClusterGroup.getName());

    List<UpgradeItemEntity> postClusterUpgradeItems = postClusterGroup.getItems();
    assertEquals(1, postClusterUpgradeItems.size());
    assertEquals("Save Cluster State", postClusterUpgradeItems.get(0).getText());
  }

  @Test
  public void testCreateResourcesWithAutoSkipAll() throws Exception {
    Cluster cluster = clusters.getCluster("c1");

    Map<String, Object> requestProps = new HashMap<>();
    requestProps.put(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME, "c1");
    requestProps.put(UpgradeResourceProvider.UPGRADE_REPO_VERSION_ID, String.valueOf(repoVersionEntity2200.getId()));
    requestProps.put(UpgradeResourceProvider.UPGRADE_PACK, "upgrade_test");
    requestProps.put(UpgradeResourceProvider.UPGRADE_TYPE, UpgradeType.ROLLING.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_FAILURES, Boolean.TRUE.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_SC_FAILURES, Boolean.TRUE.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_MANUAL_VERIFICATION, Boolean.TRUE.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_PREREQUISITE_CHECKS, Boolean.TRUE.toString());
    requestProps.put(UpgradeResourceProvider.UPGRADE_DIRECTION, Direction.UPGRADE.name());

    ResourceProvider upgradeResourceProvider = createProvider(amc);
    Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
    upgradeResourceProvider.createResources(request);

    List<UpgradeEntity> upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
    assertEquals(1, upgrades.size());

    UpgradeEntity entity = upgrades.get(0);
    assertEquals(cluster.getClusterId(), entity.getClusterId().longValue());

    List<UpgradeGroupEntity> upgradeGroups = entity.getUpgradeGroups();
    assertEquals(2, upgradeGroups.size());

    UpgradeGroupEntity zookeeperGroup = upgradeGroups.get(0);
    assertEquals("ZOOKEEPER", zookeeperGroup.getName());

    List<UpgradeItemEntity> zookeeperUpgradeItems = zookeeperGroup.getItems();
    assertEquals(4, zookeeperUpgradeItems.size());

    assertEquals("Restarting ZooKeeper Server on h1", zookeeperUpgradeItems.get(0).getText());
    assertEquals("Updating configuration zookeeper-newconfig",
        zookeeperUpgradeItems.get(1).getText());
    assertEquals("Service Check ZooKeeper", zookeeperUpgradeItems.get(2).getText());
    assertTrue(zookeeperUpgradeItems.get(3).getText().contains("There are failures that were automatically skipped"));

    // the last upgrade item is the skipped failure check
    UpgradeItemEntity skippedFailureCheck = zookeeperUpgradeItems.get(zookeeperUpgradeItems.size() - 1);
    skippedFailureCheck.getTasks().contains(AutoSkipFailedSummaryAction.class.getName());

    UpgradeGroupEntity postClusterGroup = upgradeGroups.get(1);
    assertEquals("POST_CLUSTER", postClusterGroup.getName());

    List<UpgradeItemEntity> postClusterUpgradeItems = postClusterGroup.getItems();
    assertEquals(1, postClusterUpgradeItems.size());
    assertEquals("Save Cluster State", postClusterUpgradeItems.get(0).getText());
  }

  @Test
  public void testGetResources() throws Exception {
    RequestStatus status = testCreateResources();

    Set<Resource> createdResources = status.getAssociatedResources();
    assertEquals(1, createdResources.size());
    Resource res = createdResources.iterator().next();
    Long id = (Long) res.getPropertyValue("Upgrade/request_id");
    assertNotNull(id);
    assertEquals(Long.valueOf(1), id);

    // upgrade
    Set<String> propertyIds = new HashSet<>();
    propertyIds.add("Upgrade");

    Predicate predicate = new PredicateBuilder()
        .property(UpgradeResourceProvider.UPGRADE_REQUEST_ID).equals("1").and()
        .property(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME).equals("c1")
        .toPredicate();
    Request request = PropertyHelper.getReadRequest(propertyIds);

    ResourceProvider upgradeResourceProvider = createProvider(amc);
    Set<Resource> resources = upgradeResourceProvider.getResources(request, predicate);

    assertEquals(1, resources.size());
    res = resources.iterator().next();
    assertNotNull(res.getPropertyValue("Upgrade/progress_percent"));
    assertNotNull(res.getPropertyValue(UpgradeResourceProvider.UPGRADE_DIRECTION));
    assertEquals(Direction.UPGRADE, res.getPropertyValue(UpgradeResourceProvider.UPGRADE_DIRECTION));
    assertEquals(false, res.getPropertyValue(UpgradeResourceProvider.UPGRADE_SKIP_FAILURES));
    assertEquals(false, res.getPropertyValue(UpgradeResourceProvider.UPGRADE_SKIP_SC_FAILURES));
    assertEquals(UpgradeType.ROLLING, res.getPropertyValue(UpgradeResourceProvider.UPGRADE_TYPE));

    // upgrade groups
    propertyIds.clear();
    propertyIds.add("UpgradeGroup");

    predicate = new PredicateBuilder()
        .property(UpgradeGroupResourceProvider.UPGRADE_REQUEST_ID).equals("1").and()
        .property(UpgradeGroupResourceProvider.UPGRADE_CLUSTER_NAME).equals("c1")
        .toPredicate();
    request = PropertyHelper.getReadRequest(propertyIds);

    ResourceProvider upgradeGroupResourceProvider = new UpgradeGroupResourceProvider(amc);
    resources = upgradeGroupResourceProvider.getResources(request, predicate);

    assertEquals(3, resources.size());
    res = resources.iterator().next();
    assertNotNull(res.getPropertyValue("UpgradeGroup/status"));
    assertNotNull(res.getPropertyValue("UpgradeGroup/group_id"));
    assertNotNull(res.getPropertyValue("UpgradeGroup/total_task_count"));
    assertNotNull(res.getPropertyValue("UpgradeGroup/in_progress_task_count"));
    assertNotNull(res.getPropertyValue("UpgradeGroup/completed_task_count"));

    // upgrade items
    propertyIds.clear();
    propertyIds.add("UpgradeItem");

    predicate = new PredicateBuilder()
        .property(UpgradeItemResourceProvider.UPGRADE_GROUP_ID).equals("1").and()
        .property(UpgradeItemResourceProvider.UPGRADE_REQUEST_ID).equals("1").and()
        .property(UpgradeItemResourceProvider.UPGRADE_CLUSTER_NAME).equals("c1")
        .toPredicate();
    request = PropertyHelper.getReadRequest(propertyIds);

    ResourceProvider upgradeItemResourceProvider = new UpgradeItemResourceProvider(amc);
    resources = upgradeItemResourceProvider.getResources(request, predicate);

    assertEquals(2, resources.size());

    res = resources.iterator().next();
    assertNotNull(res.getPropertyValue("UpgradeItem/status"));

    // !!! check for manual stage vs item text
    propertyIds.clear();
    propertyIds.add("UpgradeItem");

    predicate = new PredicateBuilder()
        .property(UpgradeItemResourceProvider.UPGRADE_GROUP_ID).equals("3").and()
        .property(UpgradeItemResourceProvider.UPGRADE_REQUEST_ID).equals("1").and()
        .property(UpgradeItemResourceProvider.UPGRADE_CLUSTER_NAME).equals("c1")
        .toPredicate();
    request = PropertyHelper.getReadRequest(propertyIds);

    upgradeItemResourceProvider = new UpgradeItemResourceProvider(amc);
    resources = upgradeItemResourceProvider.getResources(request, predicate);
    assertEquals(2, resources.size());
    res = resources.iterator().next();

    assertEquals("Confirm Finalize", res.getPropertyValue("UpgradeItem/context"));
    String msgStr = res.getPropertyValue("UpgradeItem/text").toString();
    JsonParser parser = new JsonParser();
    JsonArray msgArray = (JsonArray) parser.parse(msgStr);
    JsonObject msg = (JsonObject) msgArray.get(0);

    assertTrue(msg.get("message").getAsString().startsWith("Please confirm"));
  }

  /**
   * Tests that retrieving an upgrade correctly populates less common upgrade
   * options correctly.
   */
  @Test
  public void testGetResourcesWithSpecialOptions() throws Exception {
    Cluster cluster = clusters.getCluster("c1");

    List<UpgradeEntity> upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
    assertEquals(0, upgrades.size());

    Map<String, Object> requestProps = new HashMap<>();
    requestProps.put(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME, "c1");
    requestProps.put(UpgradeResourceProvider.UPGRADE_REPO_VERSION_ID, String.valueOf(repoVersionEntity2111.getId()));
    requestProps.put(UpgradeResourceProvider.UPGRADE_PACK, "upgrade_test");
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_PREREQUISITE_CHECKS, "true");
    requestProps.put(UpgradeResourceProvider.UPGRADE_DIRECTION, Direction.UPGRADE.name());

    // tests skipping SC failure options
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_FAILURES, "true");
    requestProps.put(UpgradeResourceProvider.UPGRADE_SKIP_SC_FAILURES, "true");

    ResourceProvider upgradeResourceProvider = createProvider(amc);

    Request request = PropertyHelper.getCreateRequest(Collections.singleton(requestProps), null);
    RequestStatus status = upgradeResourceProvider.createResources(request);
    assertNotNull(status);

    // upgrade
    Set<String> propertyIds = new HashSet<>();
    propertyIds.add("Upgrade");

    Predicate predicate = new PredicateBuilder()
        .property(UpgradeResourceProvider.UPGRADE_REQUEST_ID).equals("1").and()
        .property(UpgradeResourceProvider.UPGRADE_CLUSTER_NAME).equals("c1")
        .toPredicate();

    request = PropertyHelper.getReadRequest(propertyIds);
    Set<Resource> resources = upgradeResourceProvider.getResources(request, predicate);

    assertEquals(1, resources.size());
    Resource resource = resources.iterator().next();

    assertEquals(true, resource.getPropertyValue(UpgradeResourceProvider.UPGRADE_SKIP_FAILURES));
    assertEquals(true, resource.getPropertyValue(UpgradeResourceProvider.UPGRADE_SKIP_SC_FAILURES));
  }


  @Test
  public void testCreatePartialDowngrade() throws Exception {
    clusters.addHost("h2");
    Host host = clusters.getHost("h2");
    Map<String, String> hostAttributes = new HashMap<>();
    hostAttributes.put("os_family", "redhat");
    hostAttributes.put("os_release_version", "6.3");
    host.setHostAttributes(hostAttributes);

    clusters.mapHostToCluster("h2", "c1");
    Cluster cluster = clusters.getCluster("c1");
    Service service = cluster.getService("ZOOKEEPER");

    // start out with 0 (sanity check)
    List<UpgradeEntity> upgrades = upgradeDao.findUpgrades(cluster.getClusterId());
    assertEquals(0, upgrades.size());

    // a downgrade MUST have an upgrade to come from, so populate an upgrade in
    // the DB
    RequestEntity requestEntity = new RequestEntity();
    requestEntity.setRequestId(2L);
    requestEntity.setClusterId(cluster.getClusterId());
    requestEntity.setStages(new ArrayList<>());
    requestDao.create(requestEntity);

    UpgradeEntity upgradeEntity = new UpgradeEntity();
    upgradeEntity.setClusterId(cluster.getClusterId());
    upgradeEntity.setDirection(Direction.UPGRADE);
    upgradeEntity.setRepositoryVersion(repoVersionEntity2200);
    upgradeEntity.setUpgradePackage("upgrade_test");
    upgradeEntity.setUpgradePackStackId(repoVersionEntity2200.getStackId());
    upgradeEntity.setUpgradeType(UpgradeType.ROLLING);
    upgradeEntity.setRequestEntity(requestEntity);

    UpgradeHistoryEntity history = new UpgradeHistoryEntity();
    history.setUpgrade(upgradeEntity);
    history.setFromRepositoryVersion(service.getDesiredRepositoryVersion());
    history.setTargetRepositoryVersion(repoVersionEntity2200);
    history.setServiceName(service.getName());
    history.setComponentName("ZOKKEEPER_SERVER");
    upgradeEntity.addHistory(history);

    history = new UpgradeHistoryEntity();
    history.setUpgrade(upgradeEntity);
    history.setFromRepositoryVersion(service.getDesiredRepositoryVersion());
    history.setTargetRepositoryVersion(repoVersionEntity2200);
    history.setServiceName(service.getName());
    history.setComponentName("ZOKKEEPER_CLIENT");
    upgradeEntity.addHistory(history);

    upgradeDao.create(upgradeEntity);
