/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.resource; import java.net.URI; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.ExportGroup; import com.emc.storageos.db.client.model.ExportPathParams; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.util.StringSetUtil; import com.emc.storageos.model.TaskResourceRep; import com.emc.storageos.model.block.export.ExportUpdateParam; import com.emc.storageos.svcs.errorhandling.model.ServiceCoded; import com.emc.storageos.svcs.errorhandling.resources.InternalServerErrorException; import com.emc.storageos.util.ExportUtils; import com.emc.storageos.volumecontroller.BlockExportController; import com.google.common.base.Joiner; /** * Background thread that runs the placement, scheduling, and controller dispatching of an export group update * request. This allows the API to return a Task object quickly. */ class CreateExportGroupUpdateSchedulingThread implements Runnable { static final Logger _log = LoggerFactory.getLogger(CreateExportGroupUpdateSchedulingThread.class); private final DbClient dbClient; private final ExportGroupService exportGroupService; private final ExportUpdateParam exportUpdateParam; private Project project; private ExportGroup exportGroup; private String task; private TaskResourceRep taskRes; private CreateExportGroupUpdateSchedulingThread(DbClient dbClient, ExportGroupService exportGroupService, Project project, ExportGroup exportGroup, ExportUpdateParam exportUpdateParam, String task, TaskResourceRep taskRes) { this.dbClient = dbClient; this.exportGroupService = exportGroupService; this.project = project; this.exportGroup = exportGroup; this.exportUpdateParam = exportUpdateParam; this.task = task; this.taskRes = taskRes; } @Override public void run() { _log.info("Starting scheduling for export group update thread..."); try { Map<URI, Integer> newVolumesMap = exportGroupService.getUpdatedVolumesMap( exportUpdateParam, exportGroup); Map<URI, Map<URI, Integer>> storageMap = exportGroupService.computeAndValidateVolumes(newVolumesMap, exportGroup, exportUpdateParam); _log.info("Updated volumes belong to storage systems: {}", Joiner.on(',').join(storageMap.keySet())); // Convert the storageMap to a list of added and removed Block Objects newVolumesMap.clear(); for (Map.Entry<URI, Map<URI, Integer>> entry : storageMap.entrySet()) { newVolumesMap.putAll(entry.getValue()); } Map<URI, Integer> addedBlockObjectsMap = new HashMap<URI, Integer>(); Map<URI, Integer> removedBlockObjectsMap = new HashMap<URI, Integer>(); ExportUtils.getAddedAndRemovedBlockObjects(newVolumesMap, exportGroup, addedBlockObjectsMap, removedBlockObjectsMap); _log.info("Added volumes: {}", Joiner.on(',').join(addedBlockObjectsMap.keySet())); _log.info("Removed volumes: {}", Joiner.on(',').join(removedBlockObjectsMap.keySet())); // If ExportPathParameter block is present, and volumes are added, capture ExportPathParameters arguments. // This looks weird, but isn't. We use the added volumes from ExportCreateParam instead of addedBlockObjectsMap // because the user may want to change the parameters for volumes that are already exported. In this way, // the same volume can have different parameters to different hosts. Map<URI, Integer> addedVolumeParams = exportGroupService.getChangedVolumes(exportUpdateParam, true); ExportPathParams exportPathParam = null; if (exportUpdateParam.getExportPathParameters() != null && !addedVolumeParams.keySet().isEmpty()) { exportPathParam = exportGroupService.validateAndCreateExportPathParam( exportUpdateParam.getExportPathParameters(), exportGroup, addedVolumeParams.keySet()); exportGroupService.addBlockObjectsToPathParamMap(addedVolumeParams.keySet(), exportPathParam.getId(), exportGroup); } // Remove the block objects being deleted from any existing path parameters. exportGroupService.removeBlockObjectsFromPathParamMap(removedBlockObjectsMap.keySet(), exportGroup); Set<URI> addedClusters = new HashSet<>(); Set<URI> removedClusters = new HashSet<>(); Set<URI> addedHosts = new HashSet<>(); Set<URI> removedHosts = new HashSet<>(); Set<URI> addedInitiators = new HashSet<>(); Set<URI> removedInitiators = new HashSet<>(); // Validate updated entries List<URI> newInitiators = StringSetUtil.stringSetToUriList(exportGroup.getInitiators()); List<URI> newHosts = StringSetUtil.stringSetToUriList(exportGroup.getHosts()); List<URI> newClusters = StringSetUtil.stringSetToUriList(exportGroup.getClusters()); exportGroupService.validateClientsAndUpdate(exportGroup, project, storageMap.keySet(), exportUpdateParam, newClusters, newHosts, newInitiators, addedClusters, removedClusters, addedHosts, removedHosts, addedInitiators, removedInitiators); _log.info("All clients were successfully validated"); dbClient.persistObject(exportGroup); if (exportPathParam != null) { dbClient.createObject(exportPathParam); } // push it to storage devices BlockExportController exportController = exportGroupService.getExportController(); _log.info("Submitting export group update request."); exportController.exportGroupUpdate(exportGroup.getId(), addedBlockObjectsMap, removedBlockObjectsMap, addedClusters, removedClusters, addedHosts, removedHosts, addedInitiators, removedInitiators, task); } catch (Exception ex) { if (ex instanceof ServiceCoded) { dbClient.error(ExportGroup.class, taskRes.getResource().getId(), taskRes.getOpId(), (ServiceCoded) ex); } else { dbClient.error(ExportGroup.class, taskRes.getResource().getId(), taskRes.getOpId(), InternalServerErrorException.internalServerErrors .unexpectedErrorExportGroupPlacement(ex)); } _log.error(ex.getMessage(), ex); taskRes.setMessage(ex.getMessage()); } _log.info("Ending export group update scheduling thread..."); } /** * Interface to kick off an ExportGroup update. * * @param exportGroupService [IN] - ExportGroupService reference for accessing shared methods and references * @param executorService [IN] - ExecutorService used for scheduling these requests * @param dbClient [IN] - DbClient for DB access * @param project [IN] - Project in which this ExportGroup (and volumes) belong * @param exportGroup [IN] - ExportGroup that will be updated * @param exportUpdateParam [IN] - ExportGroup Update parameters * @param task [IN] - Task ID String * @param taskRes [IN] - TaskRestRep for updating task messages */ public static void executeApiTask(ExportGroupService exportGroupService, ExecutorService executorService, DbClient dbClient, Project project, ExportGroup exportGroup, ExportUpdateParam exportUpdateParam, String task, TaskResourceRep taskRes) { CreateExportGroupUpdateSchedulingThread schedulingThread = new CreateExportGroupUpdateSchedulingThread(dbClient, exportGroupService, project, exportGroup, exportUpdateParam, task, taskRes); try { executorService.execute(schedulingThread); } catch (Exception e) { String message = "Failed to execute export group update API task for resource " + exportGroup.getId(); _log.error(message); taskRes.setMessage(message); } } }