/* * Copyright (c) 2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.hds.prov; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.Operation; import com.emc.storageos.db.client.model.StoragePool; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.exceptions.DeviceControllerErrors; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.hds.HDSConstants; import com.emc.storageos.hds.HDSException; import com.emc.storageos.hds.api.HDSApiClient; import com.emc.storageos.hds.api.HDSApiFactory; import com.emc.storageos.hds.model.LDEV; import com.emc.storageos.hds.model.LogicalUnit; import com.emc.storageos.svcs.errorhandling.model.ServiceCoded; import com.emc.storageos.svcs.errorhandling.model.ServiceError; import com.emc.storageos.volumecontroller.JobContext; import com.emc.storageos.volumecontroller.MetaVolumeOperations; import com.emc.storageos.volumecontroller.TaskCompleter; import com.emc.storageos.volumecontroller.Job.JobStatus; import com.emc.storageos.volumecontroller.impl.JobPollResult; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.MetaVolumeTaskCompleter; import com.emc.storageos.volumecontroller.impl.hds.prov.job.HDSCreateMetaVolumeMembersJob; import com.emc.storageos.volumecontroller.impl.hds.prov.job.HDSJob; import com.emc.storageos.volumecontroller.impl.hds.prov.utils.HDSUtils; import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper; import com.google.common.base.Function; import com.google.common.base.Splitter; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; public class HDSMetaVolumeOperations implements MetaVolumeOperations { private static Logger log = LoggerFactory.getLogger(HDSMetaVolumeOperations.class); private static final int SYNC_WRAPPER_WAIT = 5000; private static final int SYNC_WRAPPER_TIME_OUT = 1200000; private static final String VOLUME_FORMAT_TYPE = "noformat"; private DbClient dbClient; private HDSApiFactory hdsApiFactory; public void setDbClient(DbClient dbClient) { this.dbClient = dbClient; } /** * @param hdsApiFactory the hdsApiFactory to set */ public void setHdsApiFactory(HDSApiFactory hdsApiFactory) { this.hdsApiFactory = hdsApiFactory; } /** * Create meta volume member devices. These devices provide capacity to meta volume. * * @param storageSystem * @param storagePool * @param metaHead * @param memberCount * @param memberCapacity * @param metaVolumeTaskCompleter * @return list of native ids of meta member devices * @throws Exception */ @Override public List<String> createMetaVolumeMembers(StorageSystem storageSystem, StoragePool storagePool, Volume metaHead, int memberCount, long memberCapacity, MetaVolumeTaskCompleter metaVolumeTaskCompleter) throws Exception { log.info(String .format("Create Meta Volume Members Start - Array: %s, Pool: %s, %n Volume: %s, Count:%s, Member capacity: %s", storageSystem.getSerialNumber(), storagePool.getNativeId(), metaHead.getLabel(), memberCount, memberCapacity)); try { String systemObjectID = HDSUtils.getSystemObjectID(storageSystem); String poolObjectID = HDSUtils.getPoolObjectID(storagePool); HDSApiClient hdsApiClient = hdsApiFactory.getClient( HDSUtils.getHDSServerManagementServerInfo(storageSystem), storageSystem.getSmisUserName(), storageSystem.getSmisPassword()); // commenting this to rever the fix. // Integer ldevIdToUse = getLDEVNumberToCreateMetaMembers(hdsApiClient, systemObjectID); String asyncTaskMessageId = hdsApiClient.createThickVolumes(systemObjectID, poolObjectID, memberCapacity, memberCount, "", VOLUME_FORMAT_TYPE, storageSystem.getModel(), null); HDSCreateMetaVolumeMembersJob metaVolumeMembersJob = new HDSCreateMetaVolumeMembersJob( asyncTaskMessageId, storageSystem.getId(), metaHead, memberCount, metaVolumeTaskCompleter); invokeMethodSynchronously(hdsApiFactory, asyncTaskMessageId, metaVolumeMembersJob); return metaVolumeMembersJob.getMetaMembers(); } catch (Exception e) { log.error("Problem in createMetaVolumeMembers: ", e); ServiceError error = DeviceControllerErrors.hds.methodFailed( "createMetaVolumeMemebers", e.getMessage()); metaVolumeTaskCompleter.getVolumeTaskCompleter().error(dbClient, error); throw e; } finally { log.info(String .format("Create Meta Volume Members End - Array: %s, Pool: %s, %n Volume: %s", storageSystem.getSerialNumber(), storagePool.getNativeId(), metaHead.getLabel())); } } /** * Utility finds the highest ldev Id which is used on HiCommand DM. * * @param hdsApiClient * @param systemObjectID * @return * @throws Exception */ private Integer getLDEVNumberToCreateMetaMembers(HDSApiClient hdsApiClient, String systemObjectID) throws Exception { List<LogicalUnit> allVolumes = hdsApiClient.getHDSApiVolumeManager().getAllLogicalUnits(systemObjectID); Integer highestLDEVId = 1; if (null != allVolumes && !allVolumes.isEmpty()) { List<Integer> allVolumeLDEVIds = new ArrayList<Integer>(Collections2.transform(allVolumes, fctnLogicalUnitToVolumeIDs())); Collections.sort(allVolumeLDEVIds); highestLDEVId = allVolumeLDEVIds.get(allVolumeLDEVIds.size() - 1); } // Incrementing the highest return (highestLDEVId + 1); } public Function<LogicalUnit, Integer> fctnLogicalUnitToVolumeIDs() { return new Function<LogicalUnit, Integer>() { @Override public Integer apply(LogicalUnit logicalUnit) { return logicalUnit.getDevNum(); } }; } @Override public void createMetaVolumeHead(StorageSystem storageSystem, StoragePool storagePool, Volume metaHead, long capacity, VirtualPoolCapabilityValuesWrapper capabilities, MetaVolumeTaskCompleter metaVolumeTaskCompleter) throws Exception { throw new DeviceControllerException("Unsupported operation"); } @Override public void createMetaVolume(StorageSystem storageSystem, StoragePool storagePool, Volume metaHead, List<String> metaMembers, String metaType, VirtualPoolCapabilityValuesWrapper capabilities, MetaVolumeTaskCompleter metaVolumeTaskCompleter) throws Exception { throw new DeviceControllerException("Unsupported operation"); } @Override public void createMetaVolumes(StorageSystem storageSystem, StoragePool storagePool, List<Volume> volumes, VirtualPoolCapabilityValuesWrapper capabilities, TaskCompleter taskCompleter) throws Exception { throw new DeviceControllerException("Unsupported operation"); } /** * Meta volume expansion is similar to the way expand Volume as meta. * Hence we are calling expandVolumeAsMetaVolume inside this. */ @Override public void expandMetaVolume(StorageSystem storageSystem, StoragePool storagePool, Volume metaHead, List<String> newMetaMembers, MetaVolumeTaskCompleter metaVolumeTaskCompleter) throws Exception { expandVolumeAsMetaVolume(storageSystem, storagePool, metaHead, newMetaMembers, null, metaVolumeTaskCompleter); } @Override public void expandVolumeAsMetaVolume(StorageSystem storageSystem, StoragePool storagePool, Volume metaHead, List<String> newMetaMembers, String metaType, MetaVolumeTaskCompleter metaVolumeTaskCompleter) throws Exception { HDSApiClient hdsApiClient = hdsApiFactory.getClient( HDSUtils.getHDSServerManagementServerInfo(storageSystem), storageSystem.getSmisUserName(), storageSystem.getSmisPassword()); String systemObjectID = HDSUtils.getSystemObjectID(storageSystem); LogicalUnit metaHeadVolume = hdsApiClient.getLogicalUnitInfo( systemObjectID, HDSUtils.getLogicalUnitObjectId(metaHead.getNativeId(), storageSystem)); String metaHeadLdevId = null; List<String> metaMembersLdevObjectIds = new ArrayList<String>(); // Step 1: Get LDEV id's of the meta members and format them if (null != newMetaMembers && !newMetaMembers.isEmpty()) { for (String metaMember : newMetaMembers) { if (null != metaMember) { String asyncTaskMessageId = hdsApiClient.formatLogicalUnit(systemObjectID, metaMember); HDSJob formatLUJob = new HDSJob(asyncTaskMessageId, storageSystem.getId(), metaVolumeTaskCompleter.getVolumeTaskCompleter(), "formatLogicalUnit"); invokeMethodSynchronously(hdsApiFactory, asyncTaskMessageId, formatLUJob); } LogicalUnit metaMemberVolume = hdsApiClient.getLogicalUnitInfo( systemObjectID, metaMember); if (null != metaMemberVolume && !metaMemberVolume.getLdevList().isEmpty()) { for (LDEV ldev : metaMemberVolume.getLdevList()) { // Format the logical unit. This is synchronous operation // should wait it the operation completes. metaMembersLdevObjectIds.add(ldev.getObjectID()); } } } } log.info("New Meta member LDEV ids: {}", metaMembersLdevObjectIds); // Step 2: Get LDEV id of the meta volume head. if (null != metaHeadVolume && null != metaHeadVolume.getLdevList()) { for (LDEV ldev : metaHeadVolume.getLdevList()) { // LUSE volumes operate at LDEV level, So, metaHead will // always be the least LDEV Id. // Since the volume is already created thru ViPR and it // will be the lowest LDEV Id as meta members // will be created during expansion of volume. if (getLDEVID(ldev.getObjectID()).equalsIgnoreCase( metaHead.getNativeId())) { metaHeadLdevId = ldev.getObjectID(); break; } } } // Step 3: Create LUSE Volume using metaHead LDEV & meta // members LDEV Ids. LogicalUnit logicalUnit = hdsApiClient.createLUSEVolume(systemObjectID, metaHeadLdevId, metaMembersLdevObjectIds); if (null != logicalUnit) { long capacityInBytes = Long.valueOf(logicalUnit.getCapacityInKB()) * 1024L; metaHead.setProvisionedCapacity(capacityInBytes); metaHead.setAllocatedCapacity(capacityInBytes); dbClient.persistObject(metaHead); } } /** * Makes a call to Hicommand DM and returns the response back to the caller. If the HDS call * is a asynchronous, it waits for the HDS job to complete before returning. This is done to * allow callers to make consecutive asynchronous calls without the need for a workflow. * <em>This function should be used for asynchronous HDS calls only and will throw an exception * if the call did not return a job path.</em> * * @param hdsApiFactory: HDSApiFactory * @param asyncMessageId : Async task id. * @param job * for handling special cases of intermediate and final hds job results. Null should * be used when no special handling is needed. */ public void invokeMethodSynchronously(HDSApiFactory hdsApiFactory, String asyncMessageId, HDSJob job) throws Exception { // if this is an async call, wait for the job to complete if (asyncMessageId != null) { try { waitForAsyncHDSJob(job.getStorageSystemURI(), asyncMessageId, job, hdsApiFactory); } catch (Exception ex) { log.error("Exception occurred while waiting on async job {} to complete", asyncMessageId); HDSException.exceptions .asyncTaskFailed(ex.getMessage()); } } else { HDSException.exceptions.asyncTaskFailedForMetaVolume( job.getStorageSystemURI()); } } /** * Waits the thread to till the operation completes. * * @param storageDeviceURI * @param messageId * @param job * @param hdsApiFactory * @return * @throws HDSException */ private JobStatus waitForAsyncHDSJob(URI storageDeviceURI, String messageId, HDSJob job, HDSApiFactory hdsApiFactory) throws HDSException { JobStatus status = JobStatus.IN_PROGRESS; if (job == null) { TaskCompleter taskCompleter = new TaskCompleter() { @Override public void ready(DbClient dbClient) throws DeviceControllerException { } @Override public void error(DbClient dbClient, ServiceCoded serviceCoded) throws DeviceControllerException { } @Override protected void complete(DbClient dbClient, Operation.Status status, ServiceCoded coded) throws DeviceControllerException { } }; job = new HDSJob(messageId, storageDeviceURI, taskCompleter, ""); } else { job.setHDSJob(messageId); } JobContext jobContext = new JobContext(dbClient, null, null, hdsApiFactory, null, null, null, null); long startTime = System.currentTimeMillis(); while (true) { JobPollResult result = job.poll(jobContext, SYNC_WRAPPER_WAIT); if (result.getJobStatus().equals(JobStatus.IN_PROGRESS) || result.getJobStatus().equals(JobStatus.ERROR)) { if (System.currentTimeMillis() - startTime > SYNC_WRAPPER_TIME_OUT) { HDSException.exceptions .asyncTaskFailedTimeout(System.currentTimeMillis() - startTime); } else { try { Thread.sleep(SYNC_WRAPPER_WAIT); } catch (InterruptedException e) { log.error("Thread waiting for hds job to complete was interrupted and " + "will be resumed"); } } } else { status = result.getJobStatus(); if (!status.equals(JobStatus.SUCCESS)) { HDSException.exceptions .asyncTaskFailedWithErrorResponseWithoutErrorCode(messageId, result.getErrorDescription()); } break; } } return status; } /** * Return the LDEVID of the metaHead. * * @param ldevObjectID * @return */ private String getLDEVID(String ldevObjectID) { Iterable<String> splitter = Splitter.on(HDSConstants.DOT_OPERATOR).limit(4) .split(ldevObjectID); return Iterables.getLast(splitter); } @Override public String defineExpansionType(StorageSystem storageSystem, Volume volume, String metaVolumeType, MetaVolumeTaskCompleter metaVolumeTaskCompleter) throws Exception { throw new DeviceControllerException("Unsupported operation"); } @Override public void deleteBCVHelperVolume(StorageSystem storageSystem, Volume volume) throws Exception { throw new DeviceControllerException("Unsupported operation"); } }