/*-
 * -\-\-
 * Helios System Tests
 * --
 * Copyright (C) 2016 Spotify AB
 * --
 * Licensed 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 com.spotify.helios.system;

import static com.spotify.helios.common.descriptors.HostStatus.Status.UP;
import static com.spotify.helios.common.descriptors.TaskStatus.State.RUNNING;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.messages.Container;
import com.spotify.helios.client.HeliosClient;
import com.spotify.helios.common.descriptors.JobId;
import com.spotify.helios.common.descriptors.TaskStatus;
import com.spotify.helios.servicescommon.coordination.DefaultZooKeeperClient;
import com.spotify.helios.servicescommon.coordination.Paths;
import com.spotify.helios.servicescommon.coordination.ZooKeeperClient;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.hamcrest.CustomTypeSafeMatcher;
import org.junit.Test;

public class ZooKeeperClusterIdTest extends SystemTestBase {

  @Override
  public void baseSetup() throws Exception {
    super.baseSetup();
  }

  @Test
  public void testZooKeeperClient() throws Exception {
    // Create the cluster ID node
    zk().curatorWithSuperAuth().newNamespaceAwareEnsurePath(Paths.configId(zkClusterId))
        .ensure(zk().curatorWithSuperAuth().getZookeeperClient());

    // We need to create a new curator because ZooKeeperClient will try to start it,
    // and zk().curator() has already been started.
    final ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3);
    final CuratorFramework curator = CuratorFrameworkFactory.builder()
        .retryPolicy(retryPolicy)
        .connectString(zk().connectString())
        .build();

    final ZooKeeperClient client = new DefaultZooKeeperClient(curator, zkClusterId);
    client.start();

    // This should work since the cluster ID exists
    client.create("/test");

    // Now let's remove the cluster ID
    client.delete(Paths.configId(zkClusterId));

    // Sleep so the watcher thread in ZooKeeperClient has a chance to update state
    Thread.sleep(500);

    // Try the same operation again, and it should fail this time
    try {
      client.ensurePath(Paths.configJobs());
      fail("ZooKeeper operation should have failed because cluster ID was removed");
    } catch (IllegalStateException ignore) {
      // ignored
    }
  }

  @Test
  public void testMaster() throws Exception {
    startDefaultMaster("--zk-cluster-id=" + zkClusterId);

    final HeliosClient client = defaultClient();

    // This should succeed since the cluster ID was created by SystemTestBase
    client.jobs().get();

    // Delete the cluster ID
    zk().curatorWithSuperAuth().delete().forPath(Paths.configId(zkClusterId));

    // Call jobs again, and this time it should throw an exception because the cluster ID is gone
    try {
      client.jobs().get();
    } catch (ExecutionException e) {
      assertThat(e.getMessage(), containsString("500"));
    }
  }

  @Test
  public void testAgent() throws Exception {
    startDefaultMaster("--zk-cluster-id=" + zkClusterId);
    startDefaultAgent(testHost(), "--zk-cluster-id=" + zkClusterId);
    awaitHostStatus(testHost(), UP, LONG_WAIT_SECONDS, SECONDS);

    // Create job and deploy it
    final JobId jobId = createJob(testJobName, testJobVersion, BUSYBOX, IDLE_COMMAND);
    deployJob(jobId, testHost());
    final TaskStatus runningStatus = awaitTaskState(jobId, testHost(), RUNNING);
    final String containerId = runningStatus.getContainerId();

    // Delete the config node which contains the cluster ID and all the job definitions
    zk().curatorWithSuperAuth().delete().deletingChildrenIfNeeded().forPath("/config");

    // Sleep for a second so agent has a chance to react to deletion
    Thread.sleep(1000);

    // Make sure the agent didn't stop the job
    try (final DockerClient docker = getNewDockerClient()) {
      final List<Container> containers = docker.listContainers();
      final CustomTypeSafeMatcher<Container> containerIdMatcher =
          new CustomTypeSafeMatcher<Container>("Container with id " + containerId) {
            @Override
            protected boolean matchesSafely(Container container) {
              return container.id().equals(containerId);
            }
          };

      assertContainersMatch(containers, containerIdMatcher);
    }
  }

  @SuppressWarnings("unchecked")
  private void assertContainersMatch(final List<Container> containers,
                                     final CustomTypeSafeMatcher<Container> containerIdMatcher) {
    assertThat(containers, hasItems(new CustomTypeSafeMatcher[]{ containerIdMatcher }));
  }

}
