/* * Copyright (c) 2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.resource; import static com.emc.storageos.api.mapper.BlockMapper.map; import static com.emc.storageos.api.mapper.DbObjectMapper.toNamedRelatedResource; import static com.emc.storageos.api.mapper.TaskMapper.toTask; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.api.mapper.BlockMigrationMapper; import com.emc.storageos.api.service.impl.placement.VPlexScheduler; import com.emc.storageos.api.service.impl.placement.VpoolUse; import com.emc.storageos.api.service.impl.resource.utils.VirtualPoolChangeAnalyzer; import com.emc.storageos.api.service.impl.response.BulkList; import com.emc.storageos.db.client.model.DiscoveredDataObject; import com.emc.storageos.db.client.model.Migration; import com.emc.storageos.db.client.model.Operation; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringMap; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.VirtualArray; import com.emc.storageos.db.client.model.VirtualPool; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.model.BulkIdParam; import com.emc.storageos.model.ResourceOperationTypeEnum; import com.emc.storageos.model.ResourceTypeEnum; import com.emc.storageos.model.TaskResourceRep; import com.emc.storageos.model.block.BlockMigrationBulkRep; import com.emc.storageos.model.block.MigrationList; import com.emc.storageos.model.block.MigrationParam; import com.emc.storageos.model.block.MigrationRestRep; import com.emc.storageos.security.authentication.StorageOSUser; import com.emc.storageos.security.authorization.ACL; import com.emc.storageos.security.authorization.CheckPermission; import com.emc.storageos.security.authorization.DefaultPermissions; import com.emc.storageos.security.authorization.Role; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.svcs.errorhandling.resources.InternalException; import com.emc.storageos.util.ConnectivityUtil; import com.emc.storageos.volumecontroller.Recommendation; import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper; import com.emc.storageos.vplex.api.VPlexApiConstants; import com.emc.storageos.vplex.api.VPlexApiException; import com.emc.storageos.vplex.api.VPlexMigrationInfo; import com.emc.storageos.vplexcontroller.VPlexController; import com.google.common.base.Function; /** * Service used to manage resource migrations. */ @Path("/block/migrations") @DefaultPermissions(readRoles = { Role.SYSTEM_MONITOR, Role.TENANT_ADMIN }, readAcls = { ACL.OWN, ACL.ALL }, writeRoles = { Role.TENANT_ADMIN }, writeAcls = { ACL.OWN, ACL.ALL }) public class MigrationService extends TaskResourceService { // A reference to the BlockServiceApi for VPlex. VPlexBlockServiceApiImpl _vplexBlockServiceApi = null; // A logger reference. private static final Logger s_logger = LoggerFactory .getLogger(MigrationService.class); /** * Setter for the VPlex BlockServiceApi called through Spring configuration. * * @param vplexBlockServiceApi A reference to the BlockServiceApi for VPlex. */ public void setVplexBlockServiceApi(VPlexBlockServiceApiImpl vplexBlockServiceApi) { _vplexBlockServiceApi = vplexBlockServiceApi; } /** * Performs a non-disruptive migration for the passed VPLEX virtual volume. * The backend volume of the VPLEX volume that is migrated is the backend * volume on the passed source storage system. The volume is migrated to the * passed target storage system, which must be connected to the same VPLEX * cluster as the source storage system. * * * @prereq none * * @param migrateParam A reference to the migration parameters. * @deprecated Use the Change Virtual Pool API instead * @brief Perform a non-disruptive migration for a VPLEX volume. * @return A TaskResourceRep for the volume being migrated. * @throws InternalException */ @Deprecated @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SYSTEM_ADMIN }) public TaskResourceRep migrateVolume(MigrationParam migrateParam) throws InternalException { // Create a unique task id. String taskId = UUID.randomUUID().toString(); s_logger.info( "Migrate volume {} from storage system {} to storage system {}", new Object[] { migrateParam.getVolume(), migrateParam.getSrcStorageSystem(), migrateParam.getTgtStorageSystem() }); // Verify the requested volume supports migration. Volume vplexVolume = verifyRequestedVolumeSupportsMigration(migrateParam.getVolume()); s_logger.debug("Verfified requested volume"); // Make sure that we don't have some pending // operation against the volume checkForPendingTasks(Arrays.asList(vplexVolume.getTenant().getURI()), Arrays.asList(vplexVolume)); // Determine the backend volume of the requested VPlex volume that // is to be migrated. It is the volume on the passed source storage // system. Volume migrationSrc = getMigrationSource(vplexVolume, migrateParam.getSrcStorageSystem()); s_logger.debug("Migration source is {}", migrationSrc.getId()); // The project for the migration target will be the same as that // of the source. Project migrationTgtProject = _permissionsHelper.getObjectById(migrationSrc .getProject().getURI(), Project.class); s_logger.debug("Migration target project is {}", migrationTgtProject.getId()); // The VirtualArray for the migration target will be the same as // that of the source. VirtualArray migrationTargetVarray = _permissionsHelper.getObjectById( migrationSrc.getVirtualArray(), VirtualArray.class); s_logger.debug("Migration target VirtualArray is {}", migrationTargetVarray.getId()); // Verify the requested target storage system exists and // is a system to which the migration source volume can // be migrated. verifyTargetStorageSystemForMigration(migrateParam.getVolume(), vplexVolume.getStorageController(), migrateParam.getSrcStorageSystem(), migrateParam.getTgtStorageSystem()); s_logger.debug("Verified target storage system {}", migrateParam.getTgtStorageSystem()); // Get the VirtualPool for the migration target. VirtualPool migrationTgtCos = getVirtualPoolForMigrationTarget(migrateParam.getVirtualPool(), vplexVolume, migrationSrc); s_logger.debug("Migration target VirtualPool is {}", migrationTgtCos.getId()); // Get the VPlex storage system for the virtual volume. URI vplexSystemURI = vplexVolume.getStorageController(); Set<URI> requestedVPlexSystems = new HashSet<URI>(); requestedVPlexSystems.add(vplexSystemURI); // Get a placement recommendation on the requested target storage // system connected to the VPlex storage system of the VPlex volume. VPlexScheduler vplexScheduler = _vplexBlockServiceApi.getBlockScheduler(); VirtualPoolCapabilityValuesWrapper cosWrapper = new VirtualPoolCapabilityValuesWrapper(); cosWrapper.put(VirtualPoolCapabilityValuesWrapper.SIZE, migrationSrc.getCapacity()); cosWrapper.put(VirtualPoolCapabilityValuesWrapper.RESOURCE_COUNT, new Integer(1)); List<Recommendation> recommendations = vplexScheduler.scheduleStorage( migrationTargetVarray, requestedVPlexSystems, migrateParam.getTgtStorageSystem(), migrationTgtCos, false, null, null, cosWrapper, migrationTgtProject, VpoolUse.ROOT, new HashMap<VpoolUse, List<Recommendation>>()); if (recommendations.isEmpty()) { throw APIException.badRequests.noStorageFoundForVolumeMigration(migrationTgtCos.getLabel(), migrationTargetVarray.getLabel(), vplexVolume.getId()); } s_logger.debug("Got recommendation for migration target"); // There should be a single recommendation. Recommendation recommendation = recommendations.get(0); URI recommendedSystem = recommendation.getSourceStorageSystem(); URI recommendedPool = recommendation.getSourceStoragePool(); s_logger.debug("Recommendation storage system is {}", recommendedSystem); s_logger.debug("Recommendation storage pool is {}", recommendedPool); // Prepare the migration target. List<URI> migrationTgts = new ArrayList<URI>(); Map<URI, URI> poolTgtMap = new HashMap<URI, URI>(); Long size = _vplexBlockServiceApi.getVolumeCapacity(migrationSrc); Volume migrationTgt = VPlexBlockServiceApiImpl.prepareVolumeForRequest( size, migrationTgtProject, migrationTargetVarray, migrationTgtCos, recommendedSystem, recommendedPool, migrationSrc.getLabel(), ResourceOperationTypeEnum.CREATE_BLOCK_VOLUME, taskId, _dbClient); URI migrationTgtURI = migrationTgt.getId(); migrationTgts.add(migrationTgtURI); poolTgtMap.put(recommendedPool, migrationTgtURI); s_logger.debug("Prepared migration target volume {}", migrationTgtURI); // Prepare the migration. Map<URI, URI> migrationsMap = new HashMap<URI, URI>(); Migration migration = _vplexBlockServiceApi .prepareMigration(migrateParam.getVolume(), migrationSrc.getId(), migrationTgt.getId(), taskId); migrationsMap.put(migrationTgtURI, migration.getId()); s_logger.debug("Prepared migration {}", migration.getId()); // Create a task for the virtual volume being migrated and set the // initial task state to pending. Operation op = _dbClient.createTaskOpStatus(Volume.class, vplexVolume.getId(), taskId, ResourceOperationTypeEnum.MIGRATE_BLOCK_VOLUME); TaskResourceRep task = toTask(vplexVolume, taskId, op); s_logger.debug("Created task for volume {}", migrateParam.getVolume()); try { VPlexController controller = _vplexBlockServiceApi.getController(); String successMsg = String.format("Migration succeeded for volume %s", migrateParam.getVolume()); String failMsg = String.format("Migration failed for volume %s", migrateParam.getVolume()); controller.migrateVolumes(vplexSystemURI, migrateParam.getVolume(), migrationTgts, migrationsMap, poolTgtMap, (migrateParam.getVirtualPool() != null ? migrateParam.getVirtualPool() : null), null, successMsg, failMsg, null, taskId, null); s_logger.debug("Got VPlex controller and created migration workflow"); } catch (InternalException e) { s_logger.error("Controller Error", e); String errMsg = String.format("Controller Error: %s", e.getMessage()); task.setState(Operation.Status.error.name()); task.setMessage(errMsg); Operation opStatus = new Operation(Operation.Status.error.name(), errMsg); _dbClient.updateTaskOpStatus(Volume.class, task.getResource() .getId(), taskId, opStatus); migrationTgt.setInactive(true); _dbClient.persistObject(migrationTgt); migration.setInactive(true); _dbClient.persistObject(migration); throw e; } return task; } /** * Returns a list of the migrations the user is permitted to see or an empty * list if the user is not authorized for any migrations. * * * @prereq none * * @brief List migrations * @return A MigrationList specifying the name, id, and self link for each * migration. */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @CheckPermission(roles = { Role.TENANT_ADMIN, Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR }) public MigrationList getMigrations() { // Return the migrations the user is authorized to see. MigrationList migrationList = new MigrationList(); List<URI> migrationURIs = _dbClient.queryByType(Migration.class, true); Iterator<URI> uriIter = migrationURIs.iterator(); while (uriIter.hasNext()) { Migration migration = queryResource(uriIter.next()); if (BulkList.MigrationFilter.isUserAuthorizedForMigration(migration, getUserFromContext(), _permissionsHelper)) { migrationList.getMigrations().add( toNamedRelatedResource(migration, migration.getLabel())); } } return migrationList; } /** * Returns the data for the migration with the id specified in the request. * * * @prereq none * * @param id the URN of a ViPR migration. * * @brief Show data for a migration. * @return A MigrationRestRep instance specifying the information about the * migration. */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}") @CheckPermission(roles = { Role.TENANT_ADMIN, Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR }) public MigrationRestRep getMigration(@PathParam("id") URI id) { // Return the migration or throw an exception when the user is // not authorized or the migration is not found. ArgValidator.checkFieldUriType(id, Migration.class, "id"); Migration migration = queryResource(id); if (!BulkList.MigrationFilter.isUserAuthorizedForMigration(migration, getUserFromContext(), _permissionsHelper)) { StorageOSUser user = getUserFromContext(); throw APIException.forbidden.insufficientPermissionsForUser(user.getName()); } else { return map(migration); } } /** * Pause a migration that is in progress. * * * @prereq The migration is in progress * * @param id the URN of a ViPR migration. * * @brief Pause a migration. * @return A TaskResourceRep */ @POST @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/pause") @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public TaskResourceRep pauseMigration(@PathParam("id") URI id) { ArgValidator.checkFieldUriType(id, Migration.class, "id"); Migration migration = queryResource(id); if (!BulkList.MigrationFilter.isUserAuthorizedForMigration(migration, getUserFromContext(), _permissionsHelper)) { StorageOSUser user = getUserFromContext(); throw APIException.forbidden.insufficientPermissionsForUser(user.getName()); } String status = migration.getMigrationStatus(); String migrationName = migration.getLabel(); if (status == null || status.isEmpty() || migrationName == null || migrationName.isEmpty()) { throw APIException.badRequests.migrationHasntStarted(id.toString()); } if (status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.COMPLETE.getStatusValue()) || status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.ERROR.getStatusValue()) || status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.COMMITTED.getStatusValue()) || status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.CANCELLED.getStatusValue())) { throw APIException.badRequests.migrationCantBePaused(migrationName, status); } URI volId = migration.getVolume(); Volume vplexVol = _dbClient.queryObject(Volume.class, volId); // Create a unique task id. String taskId = UUID.randomUUID().toString(); // Create a task for the volume and set the // initial task state to pending. Operation op = _dbClient.createTaskOpStatus(Volume.class, volId, taskId, ResourceOperationTypeEnum.PAUSE_MIGRATION); TaskResourceRep task = toTask(vplexVol, taskId, op); if (status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.PAUSED.getStatusValue())) { // it has been paused. s_logger.info("Migration {} has been paused", id); op.ready(); vplexVol.getOpStatus().createTaskStatus(taskId, op); _dbClient.persistObject(vplexVol); return task; } try { VPlexController controller = _vplexBlockServiceApi.getController(); controller.pauseMigration(vplexVol.getStorageController(), id, taskId); } catch (InternalException e) { s_logger.error("Error", e); String errMsg = String.format("Error: %s", e.getMessage()); task.setState(Operation.Status.error.name()); task.setMessage(errMsg); op.error(e); vplexVol.getOpStatus().updateTaskStatus(taskId, op); _dbClient.persistObject(vplexVol); } return task; } /** * Resume a migration that was previously paused. * * * @prereq The migration is paused * * @param id the URN of a ViPR migration. * * @brief Resume a paused migration. * @return A TaskResourceRep */ @POST @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/resume") @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public TaskResourceRep resumeMigration(@PathParam("id") URI id) { ArgValidator.checkFieldUriType(id, Migration.class, "id"); Migration migration = queryResource(id); if (!BulkList.MigrationFilter.isUserAuthorizedForMigration(migration, getUserFromContext(), _permissionsHelper)) { StorageOSUser user = getUserFromContext(); throw APIException.forbidden.insufficientPermissionsForUser(user.getName()); } String status = migration.getMigrationStatus(); String migrationName = migration.getLabel(); if (status == null || status.isEmpty() || migrationName == null || migrationName.isEmpty()) { throw APIException.badRequests.migrationHasntStarted(id.toString()); } if (!status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.PAUSED.getStatusValue())) { throw APIException.badRequests.migrationCantBeResumed(migrationName, status); } URI volId = migration.getVolume(); Volume vplexVol = _dbClient.queryObject(Volume.class, volId); // Create a unique task id. String taskId = UUID.randomUUID().toString(); // Create a task for the virtual volume being migrated and set the // initial task state to pending. Operation op = _dbClient.createTaskOpStatus(Volume.class, volId, taskId, ResourceOperationTypeEnum.RESUME_MIGRATION); TaskResourceRep task = toTask(vplexVol, taskId, op); try { VPlexController controller = _vplexBlockServiceApi.getController(); controller.resumeMigration(vplexVol.getStorageController(), id, taskId); } catch (InternalException e) { s_logger.error("Error", e); String errMsg = String.format("Error: %s", e.getMessage()); task.setState(Operation.Status.error.name()); task.setMessage(errMsg); op.error(e); vplexVol.getOpStatus().updateTaskStatus(taskId, op); _dbClient.persistObject(vplexVol); } return task; } /** * Cancel a migration that has yet to be committed. * * * @prereq none * * @param id the URN of a ViPR migration. * * @brief Cancel an uncommitted migration. * @return A TaskResourceRep */ @POST @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/cancel") @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public TaskResourceRep cancelMigration(@PathParam("id") URI id) { ArgValidator.checkFieldUriType(id, Migration.class, "id"); Migration migration = queryResource(id); if (!BulkList.MigrationFilter.isUserAuthorizedForMigration(migration, getUserFromContext(), _permissionsHelper)) { StorageOSUser user = getUserFromContext(); throw APIException.forbidden.insufficientPermissionsForUser(user.getName()); } if (migration == null || migration.getInactive()) { throw APIException.badRequests.cancelMigrationFailed(id.toString(), "The migration is invalid"); } String status = migration.getMigrationStatus(); String migrationName = migration.getLabel(); URI volId = migration.getVolume(); Volume vplexVol = _dbClient.queryObject(Volume.class, volId); if (vplexVol == null || vplexVol.getInactive()) { throw APIException.badRequests.cancelMigrationFailed(migrationName, "The migrating volume is not valid"); } // Don't allow cancel operation if the vplex volume is in a CG URI cgURI = vplexVol.getConsistencyGroup(); if (!NullColumnValueGetter.isNullURI(cgURI)) { throw APIException.badRequests.cancelMigrationFailed(migrationName, "Migration cancellation is not supported for the volumes in consistency group"); } if (status == null || status.isEmpty() || migrationName == null || migrationName.isEmpty()) { throw APIException.badRequests.migrationHasntStarted(id.toString()); } if (status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.COMMITTED.getStatusValue())) { throw APIException.badRequests.migrationCantBeCancelled(migrationName, status); } // Create a unique task id. String taskId = UUID.randomUUID().toString(); Operation op = _dbClient.createTaskOpStatus(Volume.class, volId, taskId, ResourceOperationTypeEnum.CANCEL_MIGRATION); TaskResourceRep task = toTask(vplexVol, taskId, op); if (status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.CANCELLED.getStatusValue()) || status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.PARTIALLY_CANCELLED.getStatusValue())) { // it has been cancelled s_logger.info("Migration {} has been cancelled", id); op.ready(); vplexVol.getOpStatus().createTaskStatus(taskId, op); _dbClient.persistObject(vplexVol); return task; } try { VPlexController controller = _vplexBlockServiceApi.getController(); controller.cancelMigration(vplexVol.getStorageController(), id, taskId); } catch (InternalException e) { s_logger.error("Controller Error", e); String errMsg = String.format("Controller Error: %s", e.getMessage()); task.setState(Operation.Status.error.name()); task.setMessage(errMsg); op.error(e); vplexVol.getOpStatus().updateTaskStatus(taskId, op); _dbClient.persistObject(vplexVol); } return task; } /** * {@inheritDoc} */ @Override protected Migration queryResource(URI id) { ArgValidator.checkUri(id); Migration migration = _permissionsHelper.getObjectById(id, Migration.class); ArgValidator.checkEntityNotNull(migration, id, isIdEmbeddedInURL(id)); return migration; } /** * {@inheritDoc} */ @Override protected URI getTenantOwner(URI id) { return null; } /** * Determines if the passed volume supports migration. * * @param volumeURI The URI of the volume. * * @return true if the volume can be migrated, false otherwise. */ private Volume verifyRequestedVolumeSupportsMigration(URI volumeURI) { // Verify the VPlex virtual volume exists and is active. Volume vplexVolume = _permissionsHelper.getObjectById(volumeURI, Volume.class); ArgValidator.checkEntity(vplexVolume, volumeURI, false); // Verify this is in fact a volume on a VPlex storage system. // If not, then the migration can't be done non-disruptively. StorageSystem vplexSystem = null; URI vplexSystemURI = vplexVolume.getStorageController(); if (vplexSystemURI == null) { // Must be an RP protected volume, which uses a protection // controller. throw APIException.badRequests.requestedVolumeIsNotVplexVolume(volumeURI); } else { vplexSystem = _permissionsHelper.getObjectById(vplexSystemURI, StorageSystem.class); if (!DiscoveredDataObject.Type.vplex.name().equals( vplexSystem.getSystemType())) { // The volume is not a VPlex volume. throw APIException.badRequests.requestedVolumeIsNotVplexVolume(volumeURI); } } return vplexVolume; } /** * Gets the source volume for the migration, which is the backend volume of * the virtual volume that resides on the passed source storage system. * * @param vplexVolume A reference to the VPlex virtual volume. * @param srcStorageSystemURI The URI of the source storage system. * * @return A reference to the source Volume for the migration. */ private Volume getMigrationSource(Volume vplexVolume, URI srcStorageSystemURI) { // Determine the backend volume of the passed VPlex volume that // is to be migrated. It is the volume on the passed source // storage system. Volume migrationSrc = null; StringSet assocVolumeIds = vplexVolume.getAssociatedVolumes(); for (String assocVolumeId : assocVolumeIds) { Volume assocVolume = _permissionsHelper.getObjectById( URI.create(assocVolumeId), Volume.class); if (assocVolume.getStorageController().toString() .equals(srcStorageSystemURI.toString())) { migrationSrc = assocVolume; break; } } // Validate that we found the migration source. if (migrationSrc == null) { throw APIException.badRequests.invalidParameterVolumeNotOnSystem(vplexVolume.getId(), srcStorageSystemURI); } return migrationSrc; } /** * Verifies that the passed target storage system is connected to the passed * VPlex storage system. * * @param vplexVolumeURI The URI of the VPlex virtual volume. * @param vplexSystemURI The URI of the VPlex storage system. * @param srcStorageSystemURI The URI of the source storage system. * @param tgtStorageSystemURI The URI of the target storage system. */ private void verifyTargetStorageSystemForMigration(URI vplexVolumeURI, URI vplexSystemURI, URI srcStorageSystemURI, URI tgtStorageSystemURI) { // Intention is tech refresh, so the source and target systems should // be different. if (tgtStorageSystemURI.toString().equals(srcStorageSystemURI.toString())) { throw APIException.badRequests.targetAndSourceStorageCannotBeSame(); } // Verify requested target storage system is active. StorageSystem tgtStorageSystem = _permissionsHelper.getObjectById( tgtStorageSystemURI, StorageSystem.class); ArgValidator.checkEntity(tgtStorageSystem, tgtStorageSystemURI, false); // Verify the target storage system is at least connected to the // VPlex storage system for the VPlex virtual volume. Technically, // it must be connected to the same VPlex cluster as the source // storage system, i.e., it must be in the same VirtualArray. // If it is not it will fail in the placement logic when we // try to get recommendations for the migration target. boolean isConnectedToVPlex = false; Set<URI> associatedVplexes = ConnectivityUtil .getVPlexSystemsAssociatedWithArray(_dbClient, tgtStorageSystemURI); if (associatedVplexes.contains(vplexSystemURI)) { isConnectedToVPlex = true; } if (!isConnectedToVPlex) { throw APIException.badRequests.storageSystemNotConnectedToCorrectVPlex(tgtStorageSystemURI, vplexSystemURI); } } /** * Gets the VirtualPool for the migration target. * * @param requestedCosURI The VirtualPool specified in the migration request. * @param vplexVolume A reference to the VPlex virtual volume. * @param migrationSrc A reference to the migration source. * * @return A reference to the VirtualPool for the migration target volume. */ private VirtualPool getVirtualPoolForMigrationTarget(URI requestedCosURI, Volume vplexVolume, Volume migrationSrc) { // Get the VirtualPool for the migration source. VirtualPool cosForMigrationSrc = _permissionsHelper.getObjectById( migrationSrc.getVirtualPool(), VirtualPool.class); // Determine the VirtualPool for the migration target based on // the VirtualPool specified in the request, if any. Note that the // VirtualPool specified in the request should be the new VirtualPool for // the passed VPlex volume after the migration is complete. VirtualPool cosForMigrationTgt = null; if (requestedCosURI != null) { // Get the new VirtualPool for the virtual volume verifying // that the VirtualPool is valid for the project's tenant and // set it initially as the VirtualPool for the migration // target. Project vplexVolumeProject = _permissionsHelper.getObjectById( vplexVolume.getProject(), Project.class); cosForMigrationTgt = BlockService.getVirtualPoolForRequest(vplexVolumeProject, requestedCosURI, _dbClient, _permissionsHelper); // Now get the VirtualArray of the migration source volume. // We need to know if this is the primary volume or the HA // volume. URI migrationNhURI = migrationSrc.getVirtualArray(); if (!migrationNhURI.toString().equals( vplexVolume.getVirtualArray().toString())) { // The HA backend volume is being migrated. // The VirtualPool for the HA volume is potentially // specified by the HA VirtualPool map in the requested // VirtualPool. If not, then the VirtualPool for the HA volume // is the same as that of the VPlex volume. StringMap haNhCosMap = cosForMigrationTgt.getHaVarrayVpoolMap(); if ((haNhCosMap != null) && (haNhCosMap.containsKey(migrationNhURI.toString()))) { cosForMigrationTgt = BlockService.getVirtualPoolForRequest( vplexVolumeProject, URI.create(haNhCosMap.get(migrationNhURI.toString())), _dbClient, _permissionsHelper); } // Now verify the VirtualPool change is legitimate. VirtualPoolChangeAnalyzer.verifyVirtualPoolChangeForTechRefresh(cosForMigrationSrc, cosForMigrationTgt); } else { // The primary or source volume is being migrated. // The VirtualPool for the primary volume is the same as // that for the VPlex volume. We still need to verify // this is a legitimate VirtualPool change. VirtualPoolChangeAnalyzer.verifyVirtualPoolChangeForTechRefresh(cosForMigrationSrc, cosForMigrationTgt); } } else { // A new VirtualPool was not specified for the virtual volume, so // the VirtualPool for the migration target will be the same as that // for the migration source. cosForMigrationTgt = cosForMigrationSrc; } return cosForMigrationTgt; } /** * Migration is not a zone level resource */ @Override protected boolean isZoneLevelResource() { return false; } @Override protected ResourceTypeEnum getResourceType() { return ResourceTypeEnum.MIGRATION; } /** * Implements function called by bulk API to map migration instances to * their corresponding REST responses. */ private class BlockMigrationAdapter implements Function<Migration, MigrationRestRep> { /** * {@inheritDoc} */ @Override public MigrationRestRep apply(final Migration migration) { return BlockMigrationMapper.map(migration); } } /** * {@inheritDoc} */ @POST @Path("/bulk") @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public BlockMigrationBulkRep getBulkResources(BulkIdParam param) { return (BlockMigrationBulkRep) super.getBulkResources(param); } /** * {@inheritDoc} */ @Override public BlockMigrationBulkRep queryBulkResourceReps(List<URI> ids) { Iterator<Migration> _dbIterator = _dbClient.queryIterativeObjects( getResourceClass(), ids); return new BlockMigrationBulkRep(BulkList.wrapping(_dbIterator, new BlockMigrationAdapter())); } /** * {@inheritDoc} */ @Override protected BlockMigrationBulkRep queryFilteredBulkResourceReps(List<URI> ids) { Iterator<Migration> _dbIterator = _dbClient.queryIterativeObjects( getResourceClass(), ids); return new BlockMigrationBulkRep(BulkList.wrapping(_dbIterator, new BlockMigrationAdapter(), new BulkList.MigrationFilter( getUserFromContext(), _permissionsHelper))); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public Class<Migration> getResourceClass() { return Migration.class; } /** * Delete a migration that has been committed or cancelled * * * @param id the URN of a ViPR migration. * * @brief Delete a committed or cancelled migration. * @return A TaskResourceRep */ @POST @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Path("/{id}/deactivate") @CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN }) public TaskResourceRep deleteMigration(@PathParam("id") URI id) { ArgValidator.checkFieldUriType(id, Migration.class, "id"); Migration migration = queryResource(id); if (!BulkList.MigrationFilter.isUserAuthorizedForMigration(migration, getUserFromContext(), _permissionsHelper)) { StorageOSUser user = getUserFromContext(); throw APIException.forbidden.insufficientPermissionsForUser(user.getName()); } String status = migration.getMigrationStatus(); String migrationName = migration.getLabel(); if (status == null || status.isEmpty() || migrationName == null || migrationName.isEmpty()) { throw APIException.badRequests.migrationHasntStarted(id.toString()); } if (!status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.COMMITTED.getStatusValue()) && !status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.CANCELLED.getStatusValue()) && !status.equalsIgnoreCase(VPlexMigrationInfo.MigrationStatus.ERROR.getStatusValue())) { throw VPlexApiException.exceptions.cantRemoveMigrationInvalidState(migrationName); } URI volId = migration.getVolume(); Volume vplexVol = _dbClient.queryObject(Volume.class, volId); // Create a unique task id. String taskId = UUID.randomUUID().toString(); Operation op = _dbClient.createTaskOpStatus(Volume.class, volId, taskId, ResourceOperationTypeEnum.DELETE_MIGRATION); TaskResourceRep task = toTask(vplexVol, taskId, op); if (migration.getInactive()) { s_logger.info("Migration {} has been deleted", id); op.ready(); vplexVol.getOpStatus().createTaskStatus(taskId, op); _dbClient.persistObject(vplexVol); return task; } try { VPlexController controller = _vplexBlockServiceApi.getController(); controller.deleteMigration(vplexVol.getStorageController(), id, taskId); } catch (InternalException e) { s_logger.error("Error", e); String errMsg = String.format("Error: %s", e.getMessage()); task.setState(Operation.Status.error.name()); task.setMessage(errMsg); op.error(e); vplexVol.getOpStatus().updateTaskStatus(taskId, op); _dbClient.persistObject(vplexVol); } return task; } }