/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package controllers;
import static com.emc.vipr.client.core.util.ResourceUtils.uri;
import static util.BourneUtil.getViprClient;
import static com.emc.vipr.client.core.TasksResources.SYSTEM_TENANT;
import static com.emc.vipr.client.core.TasksResources.FETCH_ALL;
import java.net.URI;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.text.WordUtils;
import com.emc.sa.util.ResourceType;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.model.DataObjectRestRep;
import com.emc.storageos.model.RelatedResourceRep;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.tasks.TaskStatsRestRep;
import com.emc.storageos.model.workflow.WorkflowStepRestRep;
import com.emc.vipr.client.ViPRCoreClient;
import com.emc.vipr.client.core.impl.TaskUtil.State;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import controllers.security.Security;
import controllers.tenant.TenantSelector;
import controllers.util.Models;
import models.datatable.TaskLogsDataTable;
import models.datatable.TasksDataTable;
import play.Logger;
import play.data.binding.As;
import play.i18n.Messages;
import play.mvc.Controller;
import play.mvc.Util;
import play.mvc.With;
import util.MessagesUtils;
import util.TagUtils;
import util.TaskUtils;
import util.datatable.DataTablesSupport;
@With(Common.class)
public class Tasks extends Controller {
private static final String UNKNOWN = "resource.task.unknown";
private static final String DELETED = "resource.task.deleted";
private static final int NORMAL_DELAY = 3000;
private static final int MAX_TASKS = 1000;
// Currently the backend only shows progresses of 0 or 100, so for show this as the miminum progress
private static final int MINIMUM_TASK_PROGRESS = 10;
private static final int MILLISECONDS_IN_12HOURS = 43200000;
private static TasksDataTable tasksDataTable = new TasksDataTable(true);
private static Comparator orderedTaskComparitor = new Comparator<TaskResourceRep>() {
public int compare(TaskResourceRep o1, TaskResourceRep o2) {
if (o1.getStartTime() == null || o2.getStartTime() == null) {
return 1;
}
return o2.getStartTime().compareTo(o1.getStartTime());
}
};
public static void listAll(Boolean systemTasks) {
TenantSelector.addRenderArgs();
if (systemTasks == null) {
systemTasks = Boolean.FALSE;
}
if (systemTasks && Security.isSystemAdminOrRestrictedSystemAdmin() == false) {
forbidden();
}
renderArgs.put("isSystemAdmin", Security.isSystemAdminOrRestrictedSystemAdmin());
renderArgs.put("systemTasks", systemTasks);
renderArgs.put("dataTable", new TasksDataTable(true));
Common.angularRenderArgs().put("tenantId", systemTasks ? "system" : Models.currentAdminTenant());
render();
}
public static void listAllJson(Long lastUpdated, Boolean systemTasks) {
if (systemTasks == null) {
systemTasks = Boolean.FALSE;
}
if (systemTasks && Security.isSystemAdminOrRestrictedSystemAdmin() == false) {
forbidden();
}
ViPRCoreClient client = getViprClient();
List<TaskResourceRep> taskResourceReps = null;
if (lastUpdated == null) {
if (systemTasks) {
taskResourceReps = client.tasks().getByRefs(client.tasks().listByTenant(SYSTEM_TENANT, MAX_TASKS));
}
else {
taskResourceReps = client.tasks().getByRefs(client.tasks().listByTenant(uri(Models.currentAdminTenant()), MAX_TASKS));
}
}
else {
taskResourceReps = taskPoll(lastUpdated, systemTasks);
}
Collections.sort(taskResourceReps, orderedTaskComparitor);
List<TasksDataTable.Task> tasks = Lists.newArrayList();
if (taskResourceReps != null) {
for (TaskResourceRep taskResourceRep : taskResourceReps) {
TasksDataTable.Task task = new TasksDataTable.Task(taskResourceRep);
if (Objects.equals(task.state, "pending") ||
Objects.equals(task.state, "queued")) {
task.progress = Math.max(task.progress, MINIMUM_TASK_PROGRESS);
}
tasks.add(task);
}
}
renderJSON(DataTablesSupport.createJSON(tasks, params));
}
public static void getActiveCount() {
ViPRCoreClient client = getViprClient();
int activeCount = client.tasks().getStatsByTenant(uri(Security.getUserInfo().getTenant())).getPending();
if (Security.isSystemAdmin()) {
activeCount += client.tasks().getStatsByTenant(SYSTEM_TENANT).getPending();
}
renderJSON(activeCount);
}
public static void getCountSummary(URI tenantId) {
ViPRCoreClient client = getViprClient();
TaskStatsRestRep stats = client.tasks().getStatsByTenant(tenantId);
renderJSON(stats);
}
public static void getRecentTasks() {
ViPRCoreClient client = getViprClient();
long minsAgo = new Date().getTime() - MILLISECONDS_IN_12HOURS;
List<TaskResourceRep> tasks = client.tasks().findCreatedSince(uri(Security.getUserInfo().getTenant()), minsAgo, 5);
if (Security.isSecurityAdmin()) {
tasks.addAll(client.tasks().findCreatedSince(SYSTEM_TENANT, minsAgo, 5));
}
Collections.sort(tasks, orderedTaskComparitor);
renderJSON(toTaskSummaries(tasks));
}
private static List<TaskResourceRep> tasksLongPoll(Long lastUpdated, Boolean systemTasks) {
while (true) {
List<TaskResourceRep> taskResourceReps = taskPoll(lastUpdated, systemTasks);
if (!taskResourceReps.isEmpty()) {
return taskResourceReps;
}
// Pause and check again
int delay = NORMAL_DELAY;
Logger.debug("No update for tasks, waiting for %s ms", delay);
await(delay);
}
}
private static List<TaskResourceRep> taskPoll(Long lastUpdated, Boolean systemTasks) {
List<TaskResourceRep> taskResourceReps = Lists.newArrayList();
ViPRCoreClient client = getViprClient();
URI tenant = null;
if (systemTasks) {
tenant = SYSTEM_TENANT;
}
else {
tenant = uri(Models.currentAdminTenant());
}
for (TaskResourceRep item : client.tasks().findCreatedSince(tenant, lastUpdated, FETCH_ALL)) {
taskResourceReps.add(item);
}
return taskResourceReps;
}
public static void list(String resourceId) {
renderArgs.put("dataTable", tasksDataTable);
render();
}
public static void listJson(String resourceId) {
List<TasksDataTable.Task> tasks = TasksDataTable.fetch(uri(resourceId));
renderJSON(DataTablesSupport.createJSON(tasks, params));
}
public static void logsJson(String taskId) {
List<TaskLogsDataTable.Log> logs = TaskLogsDataTable.fetch(uri(taskId));
renderJSON(DataTablesSupport.createJSON(logs, params));
}
public static void itemsJson(@As(",") String[] ids) {
List<TasksDataTable.Task> results = Lists.newArrayList();
if (ids != null && ids.length > 0) {
for (String id : ids) {
if (StringUtils.isNotBlank(id)) {
TaskResourceRep task = TaskUtils.getTask(uri(id));
if (task != null) {
results.add(new TasksDataTable.Task(task));
}
}
}
}
renderJSON(results);
}
public static void details(String taskId) {
if (StringUtils.isBlank(taskId)) {
listAll(false);
}
TaskResourceRep task = TaskUtils.getTask(uri(taskId));
if (task == null) {
flash.error(MessagesUtils.get(UNKNOWN, taskId));
listAll(false);
}
if (task != null && task.getResource() != null && task.getResource().getId() != null) {
ResourceType resourceType = ResourceType.fromResourceId(task.getResource().getId().toString());
renderArgs.put("resourceType", resourceType);
}
String orderId = TagUtils.getOrderIdTagValue(task);
String orderNumber = TagUtils.getOrderNumberTagValue(task);
Common.angularRenderArgs().put("task", getTaskSummary(task));
TaskLogsDataTable dataTable = new TaskLogsDataTable();
render(task, dataTable, orderId, orderNumber);
}
public static void detailsJson(String taskId) {
if (StringUtils.isBlank(taskId)) {
notFound("Task [" + taskId + "]");
}
TaskResourceRep task = TaskUtils.getTask(uri(taskId));
if (task == null) {
notFound("Task [" + taskId + "]");
}
renderJSON(getTaskSummary(task));
}
@Util
public static TaskSummary getTaskSummary(TaskResourceRep task) {
TaskSummary taskSummary = new TaskSummary(task);
if (task != null && task.getResource() != null && task.getResource().getId() != null) {
ResourceType resourceType = ResourceType.fromResourceId(task.getResource().getId().toString());
taskSummary.resourceType = resourceType.name();
}
taskSummary.orderId = TagUtils.getOrderIdTagValue(task);
taskSummary.orderNumber = TagUtils.getOrderNumberTagValue(task);
if (Security.isSystemAdmin() || Security.isSystemMonitor()) {
if (task.getWorkflow() != null && task.getWorkflow().getId() != null) {
taskSummary.steps = getWorkflowSteps(task.getWorkflow().getId());
}
}
return taskSummary;
}
@Util
public static List<WorkflowStep> getWorkflowSteps(URI workflowId) {
List<WorkflowStepRestRep> workflowSteps = getViprClient().workflows().getSteps(workflowId);
// Order Workflow steps by date started, not started tasks will sink to the bottom of the list
Collections.sort(workflowSteps, new Comparator<WorkflowStepRestRep>() {
@Override
public int compare(WorkflowStepRestRep o1, WorkflowStepRestRep o2) {
if (o1.getStartTime() == null && o2.getStartTime() == null) {
// If both steps not started yet, then just order on creation time
return o1.getCreationTime().compareTo(o2.getCreationTime());
}
if (o1.getStartTime() == null && o2.getStartTime() != null) {
return 1;
}
if (o1.getStartTime() != null && o2.getStartTime() == null) {
return -1;
}
return o1.getStartTime().compareTo(o2.getStartTime());
}
});
// Get the names of all resources
Map<String, DataObjectRestRep> systemObjects = Maps.newHashMap();
for (WorkflowStepRestRep step : workflowSteps) {
ResourceType type = ResourceType.fromResourceId(step.getSystem());
DataObjectRestRep dataObject = null;
switch (type) {
case STORAGE_SYSTEM:
dataObject = getViprClient().storageSystems().get(uri(step.getSystem()));
break;
case PROTECTION_SYSTEM:
dataObject = getViprClient().protectionSystems().get(uri(step.getSystem()));
break;
case NETWORK_SYSTEM:
dataObject = getViprClient().networkSystems().get(uri(step.getSystem()));
break;
case COMPUTE_SYSTEM:
dataObject = getViprClient().computeSystems().get(uri(step.getSystem()));
break;
}
if (dataObject != null) {
systemObjects.put(step.getSystem(), dataObject);
}
}
List<WorkflowStep> steps = Lists.newArrayList();
for (WorkflowStepRestRep workflowStep : workflowSteps) {
steps.add(new WorkflowStep(workflowStep, systemObjects));
}
return steps;
}
private static List<TaskSummary> toTaskSummaries(List<TaskResourceRep> tasks) {
List<TaskSummary> taskSummaries = Lists.newArrayList();
for (TaskResourceRep task : tasks) {
TaskSummary taskSummary = new TaskSummary(task);
taskSummary.progress = Math.max(taskSummary.progress, MINIMUM_TASK_PROGRESS);
taskSummaries.add(taskSummary);
}
return taskSummaries;
}
public static void deleteTask(String taskId) {
if (StringUtils.isNotBlank(taskId)) {
getViprClient().tasks().delete(uri(taskId));
flash.success(MessagesUtils.get(DELETED, taskId));
}
listAll(false);
}
public static void rollbackTask(String taskId) {
if (StringUtils.isNotBlank(taskId)) {
getViprClient().tasks().rollback(uri(taskId));
}
details(taskId);
}
public static void retryTask(String taskId) {
if (StringUtils.isNotBlank(taskId)) {
getViprClient().tasks().resume(uri(taskId));
}
details(taskId);
}
public static void resumeTask(String taskId) {
if (StringUtils.isNotBlank(taskId)) {
getViprClient().tasks().resume(uri(taskId));
}
details(taskId);
}
// "Suppressing Sonar violation of Field names should comply with naming convention"
@SuppressWarnings("squid:S00116")
private static class TaskSummary {
public URI id;
public String opId;
public String name;
public String description;
public String state;
public String message;
public String resourceName;
public int progress;
public long startDate;
public long endDate;
public long elapsedTime;
public String queueName;
public long queuedStartTime;
public long queuedElapsedTime;
public boolean systemTask;
public String resourceType;
public String resourceId;
public boolean isError = false;
public boolean isComplete = false;
public String serviceCode_error;
public String serviceCode_errorDesc;
public String serviceCode_message;
public String orderId;
public String orderNumber;
public String workflowId;
public List<WorkflowStep> steps = Collections.emptyList();
public List<String> warningMessages = Lists.newArrayList();
public TaskSummary(TaskResourceRep task) {
id = task.getId();
opId = task.getOpId();
if (StringUtils.isBlank(task.getDescription())) {
description = WordUtils.capitalize(task.getName().toLowerCase() + " " + task.getResource().getName());
}
else {
description = task.getDescription();
}
message = task.getMessage();
name = task.getName();
state = task.getState();
progress = task.getProgress() == null ? 0 : task.getProgress();
startDate = task.getStartTime() == null ? 0 : task.getStartTime().getTimeInMillis();
endDate = task.getEndTime() == null ? 0 : task.getEndTime().getTimeInMillis();
systemTask = task.getTenant() == null;
resourceType = task.getResource() == null ? "" : URIUtil.getTypeName(task.getResource().getId());
resourceName = task.getResource().getName();
resourceId = task.getResource().getId().toString();
isComplete = !task.getState().equals("pending") && !task.getState().equals("queued");
queuedStartTime = task.getQueuedStartTime() == null ? 0 : task.getQueuedStartTime().getTimeInMillis();
if (NullColumnValueGetter.isNotNullValue(task.getQueueName())) {
queueName = task.getQueueName();
}
if (endDate == 0) {
elapsedTime = new Date().getTime() - startDate;
}
else {
elapsedTime = endDate - startDate;
}
if (queuedStartTime != 0) {
queuedElapsedTime = new Date().getTime() - queuedStartTime;
}
if (Security.isSecurityAdmin() || Security.isSystemMonitor()) {
if (task.getWorkflow() != null) {
workflowId = task.getWorkflow().getId().toString();
}
}
if (task.getServiceError() != null) {
serviceCode_error = task.getServiceError().getCode() + "";
serviceCode_errorDesc = task.getServiceError().getCodeDescription();
serviceCode_message = task.getServiceError().getDetailedMessage();
}
// Temporary Fix since ERROR tasks don't show as complete
if (task.getState().equals("error")) {
progress = 100;
isError = true;
}
warningMessages = task.getWarningMessages();
}
}
public static class WorkflowStep {
public String name;
public String state;
public String message;
public String description;
public String systemName;
public long startDate;
public long endDate;
public long elapsedTime;
public List<RelatedResourceRep> childFlow;
public List<WorkflowStepRestRep> childSteps;
public WorkflowStep(WorkflowStepRestRep step, Map<String, DataObjectRestRep> systemObjects) {
state = step.getState();
name = step.getName();
message = step.getMessage();
description = step.getDescription();
if (step.getSystem() == null) {
systemName = Messages.get("workflowstep.systemUnknown");
}
else {
systemName = systemObjects.containsKey(step.getSystem()) ? systemObjects.get(step.getSystem()).getName() : step.getSystem();
}
if (step.getStartTime() != null) {
startDate = step.getStartTime().getTime();
}
if (step.getEndTime() != null) {
endDate = step.getEndTime().getTime();
}
if (endDate == 0) {
elapsedTime = new Date().getTime() - startDate;
}
else {
elapsedTime = endDate - startDate;
}
if (step.getChildWorkflows() == null) {
childSteps = null;
}
else {
childFlow = step.getChildWorkflows();
for (int i = 0; i < childFlow.size(); i++) {
childSteps = getViprClient().workflows().getSteps(step.getChildWorkflows().get(i).getId());
}
}
}
public boolean isSuspended() {
return state != null && (State.suspended_no_error.name().equalsIgnoreCase(state) ||
State.suspended_error.name().equalsIgnoreCase(state));
}
}
}