/*
* 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 gobblin.aws;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.TimeoutException;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.test.TestingServer;
import org.apache.helix.HelixManager;
import org.apache.helix.HelixManagerFactory;
import org.apache.helix.InstanceType;
import org.apache.helix.model.Message;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.amazonaws.services.autoscaling.model.AutoScalingGroup;
import com.amazonaws.services.autoscaling.model.Tag;
import com.amazonaws.services.autoscaling.model.TagDescription;
import com.amazonaws.services.ec2.model.AvailabilityZone;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.io.Closer;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;
import gobblin.cluster.GobblinClusterConfigurationKeys;
import gobblin.cluster.GobblinHelixConstants;
import gobblin.cluster.HelixMessageSubTypes;
import gobblin.cluster.HelixMessageTestBase;
import gobblin.cluster.HelixUtils;
import gobblin.cluster.TestHelper;
import gobblin.cluster.TestShutdownMessageHandlerFactory;
import gobblin.testing.AssertWithBackoff;
/**
* Unit tests for {@link GobblinAWSClusterLauncher}.
*
* @author Abhishek Tiwari
*/
@Test(groups = { "gobblin.aws" })
@PrepareForTest({ AWSSdkClient.class, GobblinAWSClusterLauncher.class})
@PowerMockIgnore({"javax.*", "org.apache.*", "org.w3c.*", "org.xml.*"})
public class GobblinAWSClusterLauncherTest extends PowerMockTestCase implements HelixMessageTestBase {
public final static Logger LOG = LoggerFactory.getLogger(GobblinAWSClusterLauncherTest.class);
private CuratorFramework curatorFramework;
private Config config;
private GobblinAWSClusterLauncher gobblinAwsClusterLauncher;
private HelixManager helixManager;
private String gobblinClusterName = "testCluster";
private String helixClusterName;
private String clusterId;
private TagDescription clusterNameTag = new TagDescription()
.withKey(GobblinAWSClusterLauncher.CLUSTER_NAME_ASG_TAG).withValue(gobblinClusterName);
private TagDescription clusterIdTag = new TagDescription()
.withKey(GobblinAWSClusterLauncher.CLUSTER_ID_ASG_TAG).withValue("dummy");
private TagDescription masterTypeTag = new TagDescription()
.withKey(GobblinAWSClusterLauncher.ASG_TYPE_ASG_TAG).withValue(GobblinAWSClusterLauncher.ASG_TYPE_MASTER);
private TagDescription workerTypeTag = new TagDescription()
.withKey(GobblinAWSClusterLauncher.ASG_TYPE_ASG_TAG).withValue(GobblinAWSClusterLauncher.ASG_TYPE_WORKERS);
private AutoScalingGroup masterASG = new AutoScalingGroup()
.withAutoScalingGroupName("AutoScalingGroup_master")
.withLaunchConfigurationName("LaunchConfiguration_master")
.withTags(clusterNameTag, clusterIdTag, masterTypeTag);
private AutoScalingGroup workerASG = new AutoScalingGroup()
.withAutoScalingGroupName("AutoScalingGroup_worker")
.withLaunchConfigurationName("LaunchConfiguration_worker")
.withTags(clusterNameTag, clusterIdTag, workerTypeTag);
private AvailabilityZone availabilityZone = new AvailabilityZone().withZoneName("A");
private Instance instance = new Instance().withPublicIpAddress("0.0.0.0");
private final Closer closer = Closer.create();
@Mock
private AWSSdkClient awsSdkClient;
@BeforeClass
public void setUp() throws Exception {
// Mock AWS SDK calls
MockitoAnnotations.initMocks(this);
PowerMockito.whenNew(AWSSdkClient.class).withAnyArguments().thenReturn(awsSdkClient);
Mockito.doNothing()
.when(awsSdkClient)
.createSecurityGroup(Mockito.anyString(), Mockito.anyString());
Mockito.doReturn(Lists.<AvailabilityZone>newArrayList(availabilityZone))
.when(awsSdkClient)
.getAvailabilityZones();
Mockito.doReturn("dummy")
.when(awsSdkClient)
.createKeyValuePair(Mockito.anyString());
Mockito.doReturn(Lists.<AutoScalingGroup>newArrayList(masterASG, workerASG))
.when(awsSdkClient)
.getAutoScalingGroupsWithTag(Mockito.any(Tag.class));
Mockito.doReturn(Lists.<Instance>newArrayList(instance))
.when(awsSdkClient)
.getInstancesForGroup(Mockito.anyString(), Mockito.anyString());
Mockito.doReturn(Lists.<S3ObjectSummary>newArrayList())
.when(awsSdkClient)
.listS3Bucket(Mockito.anyString(), Mockito.anyString());
Mockito.doNothing()
.when(awsSdkClient)
.addPermissionsToSecurityGroup(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(String.class),
Mockito.any(Integer.class), Mockito.any(Integer.class));
Mockito.doNothing()
.when(awsSdkClient)
.createAutoScalingGroup(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(Integer.class),
Mockito.any(Integer.class), Mockito.any(Integer.class), Mockito.any(Optional.class),
Mockito.any(Optional.class), Mockito.any(Optional.class), Mockito.any(Optional.class),
Mockito.any(Optional.class), Mockito.any(Optional.class), Mockito.any(List.class));
Mockito.doNothing()
.when(awsSdkClient)
.createLaunchConfig(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(String.class),
Mockito.any(String.class), Mockito.any(String.class), Mockito.any(Optional.class),
Mockito.any(Optional.class), Mockito.any(Optional.class), Mockito.any(Optional.class),
Mockito.any(Optional.class), Mockito.any(String.class));
Mockito
.doNothing()
.when(awsSdkClient)
.deleteAutoScalingGroup(Mockito.any(String.class), Mockito.any(boolean.class));
Mockito
.doNothing()
.when(awsSdkClient)
.deleteLaunchConfiguration(Mockito.any(String.class));
Mockito.doNothing()
.when(awsSdkClient)
.addPermissionsToSecurityGroup(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(String.class),
Mockito.any(Integer.class), Mockito.any(Integer.class));
// Local test Zookeeper
final TestingServer testingZKServer = this.closer.register(new TestingServer(-1));
LOG.info("Testing ZK Server listening on: " + testingZKServer.getConnectString());
this.curatorFramework = TestHelper.createZkClient(testingZKServer, this.closer);
// Load configuration
final URL url = GobblinAWSClusterLauncherTest.class.getClassLoader().getResource(
GobblinAWSClusterLauncherTest.class.getSimpleName() + ".conf");
Assert.assertNotNull(url, "Could not find resource " + url);
this.config = ConfigFactory.parseURL(url)
.withValue("gobblin.cluster.zk.connection.string",
ConfigValueFactory.fromAnyRef(testingZKServer.getConnectString()))
.resolve();
this.helixClusterName = this.config.getString(GobblinClusterConfigurationKeys.HELIX_CLUSTER_NAME_KEY);
final String zkConnectionString = this.config.getString(GobblinClusterConfigurationKeys.ZK_CONNECTION_STRING_KEY);
this.helixManager = HelixManagerFactory
.getZKHelixManager(this.config.getString(GobblinClusterConfigurationKeys.HELIX_CLUSTER_NAME_KEY),
TestHelper.TEST_HELIX_INSTANCE_NAME, InstanceType.CONTROLLER, zkConnectionString);
// Gobblin AWS Cluster Launcher to test
this.gobblinAwsClusterLauncher = new GobblinAWSClusterLauncher(this.config);
}
@Test
public void testCreateHelixCluster() throws Exception {
// This is tested here instead of in HelixUtilsTest to avoid setting up yet another testing ZooKeeper server.
HelixUtils
.createGobblinHelixCluster(this.config.getString(GobblinClusterConfigurationKeys.ZK_CONNECTION_STRING_KEY),
this.config.getString(GobblinClusterConfigurationKeys.HELIX_CLUSTER_NAME_KEY));
// Assert to check if there is no pre-existing cluster
Assert.assertEquals(this.curatorFramework.checkExists().forPath(String.format("/%s",
this.helixClusterName)).getVersion(), 0);
Assert.assertEquals(this.curatorFramework.checkExists().forPath(String.format("/%s/CONTROLLER",
this.helixClusterName)).getVersion(), 0);
}
@Test(dependsOnMethods = "testCreateHelixCluster")
public void testSetupAndSubmitApplication() throws Exception {
// Setup new cluster
this.clusterId = this.gobblinAwsClusterLauncher.setupGobblinCluster();
this.clusterIdTag.setValue(this.clusterId);
}
@Test(dependsOnMethods = "testSetupAndSubmitApplication")
public void testGetReconnectableApplicationId() throws Exception {
// Assert to check if cluster was created correctly by trying to reconnect to it
Assert.assertEquals(this.gobblinAwsClusterLauncher.getReconnectableClusterId().get(), this.clusterId);
}
@Test(dependsOnMethods = "testGetReconnectableApplicationId")
public void testSendShutdownRequest() throws Exception {
// Connect to Helix as Controller and register a shutdown request handler
this.helixManager.connect();
this.helixManager.getMessagingService().registerMessageHandlerFactory(GobblinHelixConstants.SHUTDOWN_MESSAGE_TYPE,
new TestShutdownMessageHandlerFactory(this));
// Make Gobblin AWS Cluster launcher start a shutdown
this.gobblinAwsClusterLauncher.connectHelixManager();
this.gobblinAwsClusterLauncher.sendShutdownRequest();
Assert.assertEquals(this.curatorFramework.checkExists()
.forPath(String.format("/%s/CONTROLLER/MESSAGES", this.helixClusterName)).getVersion(), 0);
GetControllerMessageNumFunc getCtrlMessageNum =
new GetControllerMessageNumFunc(this.helixClusterName, this.curatorFramework);
// Assert to check if shutdown message was issued
AssertWithBackoff assertWithBackoff =
AssertWithBackoff.create().logger(LoggerFactory.getLogger("testSendShutdownRequest")).timeoutMs(20000);
assertWithBackoff.assertEquals(getCtrlMessageNum, 1, "1 controller message queued");
// Assert to check if shutdown message was processed
// Give Helix sometime to handle the message
assertWithBackoff.assertEquals(getCtrlMessageNum, 0, "all controller messages processed");
}
@AfterClass
public void tearDown() throws IOException, TimeoutException {
try {
this.gobblinAwsClusterLauncher.stop();
if (this.helixManager.isConnected()) {
this.helixManager.disconnect();
}
this.gobblinAwsClusterLauncher.disconnectHelixManager();
} finally {
this.closer.close();
}
}
@Test(enabled = false)
@Override
public void assertMessageReception(Message message) {
Assert.assertEquals(message.getMsgType(), GobblinHelixConstants.SHUTDOWN_MESSAGE_TYPE);
Assert.assertEquals(message.getMsgSubType(), HelixMessageSubTypes.APPLICATION_MASTER_SHUTDOWN.toString());
}
static class GetControllerMessageNumFunc implements Function<Void, Integer> {
private final CuratorFramework curatorFramework;
private final String testName;
public GetControllerMessageNumFunc(String testName, CuratorFramework curatorFramework) {
this.curatorFramework = curatorFramework;
this.testName = testName;
}
@Override
public Integer apply(Void input) {
try {
return this.curatorFramework.getChildren().forPath(String.format("/%s/CONTROLLER/MESSAGES",
this.testName)).size();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}