/**
* 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.falcon.regression.core.util;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.falcon.entity.v0.EntityType;
import org.apache.falcon.regression.core.bundle.Bundle;
import org.apache.falcon.regression.core.enumsAndConstants.ResponseErrors;
import org.apache.falcon.regression.core.helpers.entity.AbstractEntityHelper;
import org.apache.falcon.request.BaseRequest;
import org.apache.falcon.resource.APIResult;
import org.apache.falcon.resource.FeedInstanceResult;
import org.apache.falcon.resource.InstanceDependencyResult;
import org.apache.falcon.resource.InstancesResult;
import org.apache.falcon.resource.InstancesSummaryResult;
import org.apache.falcon.resource.SchedulableEntityInstance;
import org.apache.falcon.resource.TriageResult;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.http.HttpResponse;
import org.apache.log4j.Logger;
import org.apache.oozie.client.BundleJob;
import org.apache.oozie.client.CoordinatorAction;
import org.apache.oozie.client.CoordinatorJob;
import org.apache.oozie.client.Job.Status;
import org.apache.oozie.client.OozieClient;
import org.apache.oozie.client.OozieClientException;
import org.apache.oozie.client.WorkflowJob;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.json.JSONException;
import org.testng.Assert;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* util functions related to instanceTest.
*/
public final class InstanceUtil {
public static final int INSTANCES_CREATED_TIMEOUT = OSUtil.IS_WINDOWS ? 20 : 10;
private static final Logger LOGGER = Logger.getLogger(InstanceUtil.class);
private static final EnumSet<Status> LIVE_STATUS = EnumSet.of(Status.RUNNING,
Status.PREP, Status.SUCCEEDED, Status.SUSPENDED);
private InstanceUtil() {
throw new AssertionError("Instantiating utility class...");
}
public static APIResult sendRequestProcessInstance(String url, String user)
throws IOException, URISyntaxException, AuthenticationException, InterruptedException {
return hitUrl(url, Util.getMethodType(url), user);
}
public static APIResult hitUrl(String url,
String method, String user) throws URISyntaxException,
IOException, AuthenticationException, InterruptedException {
BaseRequest request = new BaseRequest(url, method, user);
HttpResponse response = request.run();
String responseString = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
LOGGER.info("The web service response is:\n" + Util.prettyPrintXmlOrJson(responseString));
APIResult result;
if (url.contains("/summary/")) {
result = new InstancesSummaryResult(APIResult.Status.FAILED, responseString);
}else if (url.contains("/listing/")) {
result = new FeedInstanceResult(APIResult.Status.FAILED, responseString);
}else if (url.contains("instance/dependencies")) {
result = new InstanceDependencyResult(APIResult.Status.FAILED, responseString);
}else if (url.contains("instance/triage")) {
result = new TriageResult(APIResult.Status.FAILED, responseString);
}else {
result = new InstancesResult(APIResult.Status.FAILED, responseString);
}
Assert.assertNotNull(result, "APIResult is null");
for (ResponseErrors error : ResponseErrors.values()) {
if (responseString.contains(error.getError())) {
return result;
}
}
final String[] errorStrings = {
"(FEED) not found",
"is beforePROCESS start",
"is after end date",
"is after PROCESS's end",
"is before PROCESS's start",
"is before the entity was scheduled",
};
for (String error : errorStrings) {
if (responseString.contains(error)) {
return result;
}
}
try {
result = new GsonBuilder().registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
@Override
public Date deserialize(JsonElement json, Type t, JsonDeserializationContext c) {
return new DateTime(json.getAsString()).toDate();
}
}).create().fromJson(responseString, getClassOfResult(url));
} catch (JsonSyntaxException e) {
Assert.fail("Not a valid json:\n" + responseString);
}
LOGGER.info("statusCode: " + response.getStatusLine().getStatusCode());
LOGGER.info("message: " + result.getMessage());
LOGGER.info("APIResult.Status: " + result.getStatus());
return result;
}
/**
* Returns API result class matching to API request url.
*/
private static Class<? extends APIResult> getClassOfResult(String url) {
final Class<? extends APIResult> classOfResult;
if (url.contains("/listing/")) {
classOfResult = FeedInstanceResult.class;
} else if (url.contains("/summary/")) {
classOfResult = InstancesSummaryResult.class;
} else if (url.contains("instance/dependencies")) {
classOfResult = InstanceDependencyResult.class;
} else if (url.contains("instance/triage")) {
classOfResult = TriageResult.class;
} else {
classOfResult = InstancesResult.class;
}
return classOfResult;
}
/**
* Checks if API response reflects success and if it's instances match to expected status.
*
* @param instancesResult - kind of response from API which should contain information about
* instances
* @param bundle - bundle from which process instances are being analyzed
* @param wfStatus - - expected status of instances
*/
public static void validateSuccess(InstancesResult instancesResult, Bundle bundle,
InstancesResult.WorkflowStatus wfStatus) {
Assert.assertEquals(instancesResult.getStatus(), APIResult.Status.SUCCEEDED);
Assert.assertEquals(instancesInResultWithStatus(instancesResult, wfStatus),
bundle.getProcessConcurrency());
}
/**
* Check the number of instances in response which have the same status as expected.
*
* @param instancesResult kind of response from API which should contain information about
* instances
* @param workflowStatus expected status of instances
* @return number of instances which have expected status
*/
public static int instancesInResultWithStatus(InstancesResult instancesResult,
InstancesResult.WorkflowStatus workflowStatus) {
InstancesResult.Instance[] instances = instancesResult.getInstances();
LOGGER.info("instances: " + Arrays.toString(instances));
List<InstancesResult.WorkflowStatus> statuses =
new ArrayList<>();
for (InstancesResult.Instance instance : instances) {
LOGGER.info("instance: " + instance + " status = " + instance.getStatus());
statuses.add(instance.getStatus());
}
return Collections.frequency(statuses, workflowStatus);
}
/**
* Validates that response doesn't contains instances.
* @param r response
*/
public static void validateSuccessWOInstances(InstancesResult r) {
AssertUtil.assertSucceeded(r);
Assert.assertNull(r.getInstances(), "Unexpected :" + Arrays.toString(r.getInstances()));
}
/**
* Validates that failed response contains specific error message.
* @param instancesResult response
* @param error expected error
*/
public static void validateError(InstancesResult instancesResult, ResponseErrors error) {
Assert.assertTrue(instancesResult.getMessage().contains(error.getError()),
"Error should contains '" + error.getError() + "'");
}
/**
* Checks that actual number of instances with different statuses are equal to expected number
* of instances with matching statuses.
*
* @param instancesResult kind of response from API which should contain information about
* instances <p/>
* All parameters below reflect number of expected instances with some
* kind of status.
* @param totalCount total number of instances.
* @param runningCount number of running instances.
* @param suspendedCount number of suspended instance.
* @param waitingCount number of waiting instance.
* @param killedCount number of killed instance.
*/
public static void validateResponse(InstancesResult instancesResult, int totalCount,
int runningCount, int suspendedCount, int waitingCount, int killedCount) {
InstancesResult.Instance[] instances = instancesResult.getInstances();
LOGGER.info("instances: " + Arrays.toString(instances));
Assert.assertNotNull(instances, "instances should be not null");
Assert.assertEquals(instances.length, totalCount, "Total Instances");
List<InstancesResult.WorkflowStatus> statuses = new ArrayList<>();
for (InstancesResult.Instance instance : instances) {
final InstancesResult.WorkflowStatus status = instance.getStatus();
LOGGER.info("status: " + status + ", instance: " + instance.getInstance());
statuses.add(status);
}
Assert.assertEquals(Collections.frequency(statuses, InstancesResult.WorkflowStatus.RUNNING),
runningCount, "Running Instances");
Assert.assertEquals(Collections.frequency(statuses, InstancesResult.WorkflowStatus.SUSPENDED),
suspendedCount, "Suspended Instances");
Assert.assertEquals(Collections.frequency(statuses, InstancesResult.WorkflowStatus.WAITING),
waitingCount, "Waiting Instances");
Assert.assertEquals(Collections.frequency(statuses, InstancesResult.WorkflowStatus.KILLED),
killedCount, "Killed Instances");
}
/**
* Retrieves workflow IDs from every instances from response.
* @param instancesResult response
* @return list of workflow IDs
*/
public static List<String> getWorkflowJobIds(InstancesResult instancesResult) {
InstancesResult.Instance[] instances = instancesResult.getInstances();
LOGGER.info("Instances: " + Arrays.toString(instances));
Assert.assertNotNull(instances, "Instances should be not null");
List<String> wfIds = new ArrayList<>();
for (InstancesResult.Instance instance : instances) {
LOGGER.warn(String.format(
"instance: %s, status: %s, logs : %s", instance, instance.getStatus(), instance.getLogFile()));
if (instance.getStatus().name().equals("RUNNING") || instance.getStatus().name().equals("SUCCEEDED")) {
wfIds.add(instance.getLogFile());
}
if (instance.getStatus().name().equals("KILLED") || instance.getStatus().name().equals("WAITING")) {
Assert.assertNull(instance.getLogFile());
}
}
return wfIds;
}
/**
* Checks that expected number of failed instances matches actual number of failed ones.
*
* @param instancesResult kind of response from API which should contain information about
* instances.
* @param failCount number of instances which should be failed.
*/
public static void validateFailedInstances(InstancesResult instancesResult, int failCount) {
AssertUtil.assertSucceeded(instancesResult);
int counter = 0;
for (InstancesResult.Instance oneInstance : instancesResult.getInstances()) {
if (oneInstance.getStatus() == InstancesResult.WorkflowStatus.FAILED) {
counter++;
}
}
Assert.assertEquals(counter, failCount, "Actual number of failed instances does not "
+ "match to expected number of failed instances.");
}
/**
* Gets process workflows by given statuses.
* @param oozieClient oozie client of cluster where process is running
* @param processName process name
* @param statuses statuses workflows will be selected by
* @return list of matching workflows
* @throws OozieClientException
*/
public static List<String> getWorkflows(OozieClient oozieClient, String processName,
WorkflowJob.Status... statuses) throws OozieClientException {
String bundleID = OozieUtil.getBundles(oozieClient, processName, EntityType.PROCESS).get(0);
List<String> workflowJobIds = OozieUtil.getWorkflowJobs(oozieClient, bundleID);
List<String> toBeReturned = new ArrayList<>();
for (String jobId : workflowJobIds) {
WorkflowJob wfJob = oozieClient.getJobInfo(jobId);
LOGGER.info("wfJob.getId(): " + wfJob.getId() + " wfJob.getStartTime(): "
+ wfJob.getStartTime() + "jobId: " + jobId + " wfJob.getStatus(): " + wfJob.getStatus());
if (statuses.length == 0 || Arrays.asList(statuses).contains(wfJob.getStatus())) {
toBeReturned.add(jobId);
}
}
return toBeReturned;
}
public static boolean isWorkflowRunning(OozieClient oozieClient, String workflowID) throws
OozieClientException {
WorkflowJob.Status status = oozieClient.getJobInfo(workflowID).getStatus();
return status == WorkflowJob.Status.RUNNING;
}
public static void areWorkflowsRunning(OozieClient oozieClient, List<String> workflowIds,
int totalWorkflows, int runningWorkflows, int killedWorkflows,
int succeededWorkflows) throws OozieClientException {
if (totalWorkflows != -1) {
Assert.assertEquals(workflowIds.size(), totalWorkflows);
}
final List<WorkflowJob.Status> statuses = new ArrayList<>();
for (String wfId : workflowIds) {
final WorkflowJob.Status status = oozieClient.getJobInfo(wfId).getStatus();
LOGGER.info("wfId: " + wfId + " status: " + status);
statuses.add(status);
}
if (runningWorkflows != -1) {
Assert.assertEquals(Collections.frequency(statuses, WorkflowJob.Status.RUNNING),
runningWorkflows, "Number of running jobs doesn't match.");
}
if (killedWorkflows != -1) {
Assert.assertEquals(Collections.frequency(statuses, WorkflowJob.Status.KILLED),
killedWorkflows, "Number of killed jobs doesn't match.");
}
if (succeededWorkflows != -1) {
Assert.assertEquals(Collections.frequency(statuses, WorkflowJob.Status.SUCCEEDED),
succeededWorkflows, "Number of succeeded jobs doesn't match.");
}
}
public static List<CoordinatorAction> getProcessInstanceList(OozieClient oozieClient,
String processName, EntityType entityType) throws OozieClientException {
String coordId = OozieUtil.getLatestCoordinatorID(oozieClient, processName, entityType);
//String coordId = getDefaultCoordinatorFromProcessName(processName);
LOGGER.info("default coordID: " + coordId);
return oozieClient.getCoordJobInfo(coordId).getActions();
}
public static int getInstanceCountWithStatus(OozieClient oozieClient, String processName,
CoordinatorAction.Status status, EntityType entityType) throws OozieClientException {
List<CoordinatorAction> coordActions = getProcessInstanceList(oozieClient, processName, entityType);
List<CoordinatorAction.Status> statuses = new ArrayList<>();
for (CoordinatorAction action : coordActions) {
statuses.add(action.getStatus());
}
return Collections.frequency(statuses, status);
}
/**
* Retrieves status of one instance.
*
* @param oozieClient - server from which instance status will be retrieved.
* @param processName - name of process which mentioned instance belongs to.
* @param bundleNumber - ordinal number of one of the bundle which are related to that
* process.
* @param instanceNumber - ordinal number of instance which state will be returned.
* @return - state of mentioned instance.
* @throws OozieClientException
*/
public static CoordinatorAction.Status getInstanceStatus(OozieClient oozieClient, String processName,
int bundleNumber, int instanceNumber) throws OozieClientException {
String bundleID = OozieUtil.getSequenceBundleID(oozieClient, processName, EntityType.PROCESS, bundleNumber);
if (StringUtils.isEmpty(bundleID)) {
return null;
}
String coordID = OozieUtil.getDefaultCoordIDFromBundle(oozieClient, bundleID);
if (StringUtils.isEmpty(coordID)) {
return null;
}
CoordinatorJob coordInfo = oozieClient.getCoordJobInfo(coordID);
if (coordInfo == null) {
return null;
}
LOGGER.info("coordInfo = " + coordInfo);
List<CoordinatorAction> actions = coordInfo.getActions();
if (actions.size() == 0) {
return null;
}
LOGGER.info("actions = " + actions);
return actions.get(instanceNumber).getStatus();
}
/**
* Forms and sends process instance request based on url of action to be performed and it's
* parameters.
*
* @param colo - servers on which action should be performed
* @param user - whose credentials will be used for this action
* @return result from API
*/
public static APIResult createAndSendRequestProcessInstance(String url, String params, String colo, String user)
throws IOException, URISyntaxException, AuthenticationException, InterruptedException {
if (params != null && !colo.equals("")) {
url = url + params + "&" + colo.substring(1);
} else if (params != null) {
url = url + params;
} else {
url = url + colo;
}
return sendRequestProcessInstance(url, user);
}
public static org.apache.oozie.client.WorkflowJob.Status getInstanceStatusFromCoord(
OozieClient oozieClient, String coordID, int instanceNumber) throws OozieClientException {
CoordinatorJob coordInfo = oozieClient.getCoordJobInfo(coordID);
String jobId = coordInfo.getActions().get(instanceNumber).getExternalId();
LOGGER.info("jobId = " + jobId);
if (jobId == null) {
return null;
}
WorkflowJob actionInfo = oozieClient.getJobInfo(jobId);
return actionInfo.getStatus();
}
public static List<String> getInputFoldersForInstanceForReplication(
OozieClient oozieClient, String coordID, int instanceNumber) throws OozieClientException {
CoordinatorAction x = oozieClient.getCoordActionInfo(coordID + "@" + instanceNumber);
String jobId = x.getExternalId();
WorkflowJob wfJob = oozieClient.getJobInfo(jobId);
return getReplicationFolderFromInstanceRunConf(wfJob.getConf());
}
private static List<String> getReplicationFolderFromInstanceRunConf(String runConf) {
String conf;
conf = runConf.substring(runConf.indexOf("falconInPaths</name>") + 20);
conf = conf.substring(conf.indexOf("<value>") + 7);
conf = conf.substring(0, conf.indexOf("</value>"));
return new ArrayList<>(Arrays.asList(conf.split(",")));
}
public static int getInstanceRunIdFromCoord(OozieClient oozieClient, String coordID, int instanceNumber)
throws OozieClientException {
CoordinatorJob coordInfo = oozieClient.getCoordJobInfo(coordID);
WorkflowJob actionInfo = oozieClient.getJobInfo(coordInfo.getActions().get(instanceNumber).getExternalId());
return actionInfo.getRun();
}
public static int checkIfFeedCoordExist(AbstractEntityHelper helper,
String feedName, String coordType) throws OozieClientException {
LOGGER.info("feedName: " + feedName);
int numberOfCoord = 0;
final OozieClient oozieClient = helper.getOozieClient();
if (OozieUtil.getBundles(oozieClient, feedName, EntityType.FEED).size() == 0) {
return 0;
}
List<String> bundleIds = OozieUtil.getBundles(oozieClient, feedName, EntityType.FEED);
LOGGER.info("bundleIds: " + bundleIds);
for (String bundleId : bundleIds) {
LOGGER.info("bundleId: " + bundleId);
OozieUtil.waitForCoordinatorJobCreation(oozieClient, bundleId);
List<CoordinatorJob> coords =
OozieUtil.getBundleCoordinators(oozieClient, bundleId);
LOGGER.info("coords: " + coords);
for (CoordinatorJob coord : coords) {
if (coord.getAppName().contains(coordType)) {
numberOfCoord++;
}
}
}
return numberOfCoord;
}
public static List<CoordinatorAction> getProcessInstanceListFromAllBundles(
OozieClient oozieClient, String processName, EntityType entityType)
throws OozieClientException {
List<CoordinatorAction> list = new ArrayList<>();
final List<String> bundleIds = OozieUtil.getBundles(oozieClient, processName, entityType);
LOGGER.info("bundle size for process is " + bundleIds.size());
for (String bundleId : bundleIds) {
BundleJob bundleInfo = oozieClient.getBundleJobInfo(bundleId);
List<CoordinatorJob> coordJobs = bundleInfo.getCoordinators();
LOGGER.info("number of coordJobs in bundle " + bundleId + "=" + coordJobs.size());
for (CoordinatorJob coordJob : coordJobs) {
List<CoordinatorAction> actions =
oozieClient.getCoordJobInfo(coordJob.getId()).getActions();
LOGGER.info("number of actions in coordinator " + coordJob.getId() + " is "
+ actions.size());
list.addAll(actions);
}
}
String coordId = OozieUtil.getLatestCoordinatorID(oozieClient, processName, entityType);
LOGGER.info("default coordID: " + coordId);
return list;
}
public static String getOutputFolderForInstanceForReplication(OozieClient oozieClient,
String coordID, int instanceNumber) throws OozieClientException {
CoordinatorJob coordInfo = oozieClient.getCoordJobInfo(coordID);
final CoordinatorAction coordAction = coordInfo.getActions().get(instanceNumber);
final String actionConf = oozieClient.getJobInfo(coordAction.getExternalId()).getConf();
return getReplicatedFolderFromInstanceRunConf(actionConf);
}
private static String getReplicatedFolderFromInstanceRunConf(String runConf) {
String inputPathExample = getReplicationFolderFromInstanceRunConf(runConf).get(0);
String postFix = inputPathExample.substring(inputPathExample.length() - 7, inputPathExample.length());
return getReplicatedFolderBaseFromInstanceRunConf(runConf) + "/" + postFix;
}
public static String getOutputFolderBaseForInstanceForReplication(
OozieClient oozieClient, String coordID, int instanceNumber) throws OozieClientException {
CoordinatorJob coordInfo = oozieClient.getCoordJobInfo(coordID);
final CoordinatorAction coordAction = coordInfo.getActions().get(instanceNumber);
final String actionConf = oozieClient.getJobInfo(coordAction.getExternalId()).getConf();
return getReplicatedFolderBaseFromInstanceRunConf(actionConf);
}
private static String getReplicatedFolderBaseFromInstanceRunConf(String runConf) {
String conf = runConf.substring(runConf.indexOf("distcpTargetPaths</name>") + 24);
conf = conf.substring(conf.indexOf("<value>") + 7);
conf = conf.substring(0, conf.indexOf("</value>"));
return conf;
}
/**
* Waits till supplied number of instances of process/feed reach expected state during
* specific time.
*
* @param client oozie client to retrieve info about instances
* @param entityName name of feed or process
* @param instancesNumber instance number for which we wait to reach the required status
* @param expectedStatus expected status we are waiting for
* @param entityType type of entity - feed or process expected
* @param totalMinutesToWait time in minutes for which instance state should be polled
* @throws OozieClientException
*/
public static void waitTillInstanceReachState(OozieClient client, String entityName, int instancesNumber,
CoordinatorAction.Status expectedStatus, EntityType entityType, int totalMinutesToWait)
throws OozieClientException {
String filter;
// get the bundle ids
if (entityType.equals(EntityType.FEED)) {
filter = "name=FALCON_FEED_" + entityName;
} else {
filter = "name=FALCON_PROCESS_" + entityName;
}
List<BundleJob> bundleJobs = new ArrayList<>();
for (int retries = 0; retries < 20; ++retries) {
bundleJobs = OozieUtil.getBundles(client, filter, 0, 10);
if (bundleJobs.size() > 0) {
break;
}
TimeUtil.sleepSeconds(5);
}
if (bundleJobs.size() == 0) {
Assert.fail("Could not retrieve bundles");
}
List<String> bundleIds = OozieUtil.getBundleIds(bundleJobs);
Collections.sort(bundleIds, Collections.reverseOrder());
String coordId = null;
for (String bundleId : bundleIds) {
LOGGER.info(String.format("Using bundle %s", bundleId));
final Status status = client.getBundleJobInfo(bundleId).getStatus();
Assert.assertTrue(LIVE_STATUS.contains(status),
String.format("Bundle job %s is should be prep/running but is %s", bundleId, status));
OozieUtil.waitForCoordinatorJobCreation(client, bundleId);
List<CoordinatorJob> coords = client.getBundleJobInfo(bundleId).getCoordinators();
List<String> cIds = new ArrayList<>();
if (entityType == EntityType.PROCESS) {
for (CoordinatorJob coord : coords) {
cIds.add(coord.getId());
}
coordId = OozieUtil.getMinId(cIds);
break;
} else {
for (CoordinatorJob coord : coords) {
if (coord.getAppName().contains("FEED_REPLICATION")) {
cIds.add(coord.getId());
}
}
if (!cIds.isEmpty()) {
coordId = cIds.get(0);
break;
}
}
}
Assert.assertNotNull(coordId, "Coordinator id not found");
LOGGER.info(String.format("Using coordinator id: %s", coordId));
int maxTries = 50;
int totalSleepTime = totalMinutesToWait * 60;
int sleepTime = totalSleepTime / maxTries;
LOGGER.info(String.format("Sleep for %d seconds", sleepTime));
for (int i = 0; i < maxTries; i++) {
LOGGER.info(String.format("Try %d of %d", (i + 1), maxTries));
CoordinatorJob coordinatorJob = client.getCoordJobInfo(coordId);
final Status coordinatorStatus = coordinatorJob.getStatus();
if (expectedStatus != CoordinatorAction.Status.TIMEDOUT){
Assert.assertTrue(LIVE_STATUS.contains(coordinatorStatus),
String.format("Coordinator %s should be running/prep but is %s.", coordId, coordinatorStatus));
}
List<CoordinatorAction> coordinatorActions = coordinatorJob.getActions();
int instanceWithStatus = 0;
for (CoordinatorAction coordinatorAction : coordinatorActions) {
LOGGER.info(String.format("Coordinator Action %s status is %s on oozie %s",
coordinatorAction.getId(), coordinatorAction.getStatus(), client.getOozieUrl()));
if (expectedStatus == coordinatorAction.getStatus()) {
instanceWithStatus++;
}
}
if (instanceWithStatus >= instancesNumber) {
return;
} else {
TimeUtil.sleepSeconds(sleepTime);
}
}
Assert.fail("expected state of instance was never reached");
}
/**
* Waits till supplied number of instances of process/feed reach expected state during
* specific time.
*
* @param client oozie client to retrieve info about instances
* @param entityName name of feed or process
* @param numberOfInstance number of instances which status we are waiting for
* @param expectedStatus expected status we are waiting for
* @param entityType type of entity - feed or process expected
*/
public static void waitTillInstanceReachState(OozieClient client, String entityName,
int numberOfInstance,
CoordinatorAction.Status expectedStatus,
EntityType entityType)
throws OozieClientException {
int totalMinutesToWait = getMinutesToWait(entityType, expectedStatus);
waitTillInstanceReachState(client, entityName, numberOfInstance, expectedStatus,
entityType, totalMinutesToWait);
}
/**
* Generates time which is presumably needed for process/feed instances to reach particular
* state.
* Feed instances are running faster then process, so feed timeouts are less then process.
*
* @param entityType type of entity which instances status we are waiting for
* @param expectedStatus expected status we are waiting for
* @return minutes to wait for expected status
*/
public static int getMinutesToWait(EntityType entityType, CoordinatorAction.Status expectedStatus) {
switch (expectedStatus) {
case RUNNING:
if (entityType == EntityType.PROCESS) {
return OSUtil.IS_WINDOWS ? 20 : 10;
} else if (entityType == EntityType.FEED) {
return OSUtil.IS_WINDOWS ? 10 : 5;
}
case WAITING:
return OSUtil.IS_WINDOWS ? 6 : 3;
case SUCCEEDED:
if (entityType == EntityType.PROCESS) {
return OSUtil.IS_WINDOWS ? 25 : 15;
} else if (entityType == EntityType.FEED) {
return OSUtil.IS_WINDOWS ? 20 : 10;
}
case KILLED:
case TIMEDOUT:
return OSUtil.IS_WINDOWS ? 40 : 20;
default:
return OSUtil.IS_WINDOWS ? 30 : 15;
}
}
/**
* Waits till instances of specific job will be created during specific time.
* Use this method directly in unusual test cases where timeouts are different from trivial.
* In other cases use waitTillInstancesAreCreated(OozieClient,String,int)
*
* @param oozieClient oozie client of the cluster on which job is running
* @param entity definition of entity which describes job
* @param bundleSeqNo bundle number if update has happened.
* @throws OozieClientException
*/
public static void waitTillInstancesAreCreated(OozieClient oozieClient, String entity, int bundleSeqNo,
int totalMinutesToWait) throws OozieClientException {
String entityName = Util.readEntityName(entity);
EntityType type = Util.getEntityType(entity);
String bundleID = OozieUtil.getSequenceBundleID(oozieClient, entityName,
type, bundleSeqNo);
String coordID = OozieUtil.getDefaultCoordIDFromBundle(oozieClient, bundleID);
for (int sleepCount = 0; sleepCount < totalMinutesToWait; sleepCount++) {
CoordinatorJob coordInfo = oozieClient.getCoordJobInfo(coordID);
if (coordInfo.getActions().size() > 0) {
break;
}
LOGGER.info("Coord " + coordInfo.getId() + " still doesn't have "
+ "instance created on oozie: " + oozieClient.getOozieUrl());
TimeUtil.sleepSeconds(5);
}
}
/**
* Waits till instances of specific job will be created during timeout.
* Timeout is common for most of usual test cases.
*
* @param oozieClient oozieClient of cluster job is running on
* @param entity definition of entity which describes job
* @param bundleSeqNo bundle number if update has happened.
* @throws OozieClientException
*/
public static void waitTillInstancesAreCreated(OozieClient oozieClient, String entity, int bundleSeqNo
) throws OozieClientException {
int sleep = INSTANCES_CREATED_TIMEOUT * 60 / 5;
waitTillInstancesAreCreated(oozieClient, entity, bundleSeqNo, sleep);
}
/**
* Asserts instances of specific job will be present for given instanceTime.
*
* @param instancesResult InstanceDependencyResult
* @param oozieClient oozieClient of cluster job is running on
* @param bundleID bundleId of job
* @param time instanceTime.
* @throws JSONException
* @throws ParseException
*/
public static void assertProcessInstances(InstanceDependencyResult instancesResult, OozieClient oozieClient,
String bundleID, String time)
throws OozieClientException, ParseException, JSONException {
List<String> inputPath = new ArrayList<>();
List<String> outputPath = new ArrayList<>();
SchedulableEntityInstance[] instances = instancesResult.getDependencies();
LOGGER.info("instances: " + Arrays.toString(instances));
Assert.assertNotNull(instances, "instances should be not null");
for (SchedulableEntityInstance instance : instances) {
Assert.assertNotNull(instance.getCluster());
Assert.assertNotNull(instance.getEntityName());
Assert.assertNotNull(instance.getEntityType());
Assert.assertNotNull(instance.getInstanceTime());
Assert.assertNotNull(instance.getTags());
if (instance.getTags().equals("Input")) {
inputPath.add(new DateTime(instance.getInstanceTime(), DateTimeZone.UTC).toString());
}
if (instance.getTags().equals("Output")) {
outputPath.add(new DateTime(instance.getInstanceTime(), DateTimeZone.UTC).toString());
}
}
List<String> inputActual = getMinuteDatesToPath(inputPath.get(inputPath.indexOf(
Collections.min(inputPath))), inputPath.get(inputPath.indexOf(Collections.max(inputPath))), 5);
List<String> outputActual = getMinuteDatesToPath(outputPath.get(outputPath.indexOf(Collections.min(
outputPath))), outputPath.get(outputPath.indexOf(Collections.max(outputPath))), 5);
Configuration conf = OozieUtil.getProcessConf(oozieClient, bundleID, time);
Assert.assertNotNull(conf, "Configuration should not be null");
List<String> inputExp = Arrays.asList(conf.get("inputData").split(","));
List<String> outputExp = Arrays.asList(conf.get("outputData").split(","));
Assert.assertTrue(matchList(inputExp, inputActual), " Inputs dont match");
Assert.assertTrue(matchList(outputExp, outputActual), " Outputs dont match");
}
/**
* Returns list of path based on given start and end time.
*
* @param startOozieDate start date
* @param endOozieDate end date
* @param minuteSkip difference between paths
* @throws ParseException
*/
public static List<String> getMinuteDatesToPath(String startOozieDate, String endOozieDate,
int minuteSkip) throws ParseException {
String myFormat = "yyyy'-'MM'-'dd'T'HH':'mm'Z'";
String userFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'";
return TimeUtil.getMinuteDatesOnEitherSide(TimeUtil.parseDate(startOozieDate, myFormat, userFormat),
TimeUtil.parseDate(endOozieDate, myFormat, userFormat), minuteSkip);
}
/**
* Parses date from one format to another.
*
* @param oozieDate input date
* @throws ParseException
*/
public static String getParsedDates(String oozieDate) throws ParseException {
String myFormat = "yyyy'-'MM'-'dd'T'HH':'mm'Z'";
String userFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'";
return TimeUtil.parseDate(oozieDate, myFormat, userFormat);
}
/**
* Asserts Whether two list are equal or not.
*
* @param firstList list<String> to be comapred
* @param secondList list<String> to be compared
*/
public static boolean matchList(List<String> firstList, List<String> secondList) {
Collections.sort(firstList);
Collections.sort(secondList);
if (firstList.size() != secondList.size()) {
return false;
}
for (int index = 0; index < firstList.size(); index++) {
if (!firstList.get(index).contains(secondList.get(index))) {
return false;
}
}
return true;
}
/**
* Asserts instanceDependencyResult of specific job for a given feed.
*
* @param instancesResult InstanceDependencyResult
* @param processName process name for given bundle
* @param tag Input/Output
* @param expectedInstances instance for given instanceTime.
* @throws ParseException
*/
public static void assertFeedInstances(InstanceDependencyResult instancesResult, String processName, String tag,
List<String> expectedInstances) throws ParseException {
List<String> actualInstances = new ArrayList<>();
SchedulableEntityInstance[] instances = instancesResult.getDependencies();
LOGGER.info("instances: " + Arrays.toString(instances));
Assert.assertNotNull(instances, "instances should be not null");
for (SchedulableEntityInstance instance : instances) {
Assert.assertNotNull(instance.getCluster());
Assert.assertNotNull(instance.getEntityName());
Assert.assertNotNull(instance.getEntityType());
Assert.assertNotNull(instance.getInstanceTime());
Assert.assertNotNull(instance.getTags());
Assert.assertTrue(instance.getEntityType().toString().equals("PROCESS"), "Type should be PROCESS");
Assert.assertTrue(instance.getEntityName().equals(processName), "Expected name is : " + processName);
Assert.assertTrue(instance.getTags().equals(tag));
actualInstances.add(getParsedDates(new DateTime(instance.getInstanceTime(), DateTimeZone.UTC).toString()));
}
Set<String> expectedInstancesSet = new HashSet<>(expectedInstances);
Set<String> actualInstancesSet = new HashSet<>(actualInstances);
Assert.assertEquals(expectedInstancesSet, actualInstancesSet, "Instances don't match");
}
}