/* * Copyright (c) 2012-2015 iWave Software LLC * All Rights Reserved */ package com.emc.sa.service.linux; import static com.emc.sa.service.vipr.ViPRExecutionUtils.addAffectedResource; import java.net.URI; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import com.emc.sa.engine.ExecutionUtils; import com.emc.sa.machinetags.KnownMachineTags; import com.emc.sa.service.linux.UnmountBlockVolumeHelper.VolumeSpec; import com.emc.sa.service.linux.tasks.AddToFSTab; import com.emc.sa.service.linux.tasks.CheckFileSystem; import com.emc.sa.service.linux.tasks.CheckForFileSystemCompatibility; import com.emc.sa.service.linux.tasks.CheckForMultipath; import com.emc.sa.service.linux.tasks.CheckForPowerPath; import com.emc.sa.service.linux.tasks.CreateDirectory; import com.emc.sa.service.linux.tasks.DeleteDirectory; import com.emc.sa.service.linux.tasks.FindHBAs; import com.emc.sa.service.linux.tasks.FindIScsiInitiators; import com.emc.sa.service.linux.tasks.FindIScsiSessions; import com.emc.sa.service.linux.tasks.FindLunz; import com.emc.sa.service.linux.tasks.FindMountPointsForVolumes; import com.emc.sa.service.linux.tasks.FindMultiPathEntriesForMountPoint; import com.emc.sa.service.linux.tasks.FindMultiPathEntryForDmName; import com.emc.sa.service.linux.tasks.FindMultiPathEntryForVolume; import com.emc.sa.service.linux.tasks.FindPowerPathEntriesForMountPoint; import com.emc.sa.service.linux.tasks.FindPowerPathEntryForVolume; import com.emc.sa.service.linux.tasks.FormatVolume; import com.emc.sa.service.linux.tasks.GetDirectoryContents; import com.emc.sa.service.linux.tasks.GetMultipathBlockDevices; import com.emc.sa.service.linux.tasks.GetMultipathPrimaryPartitionDeviceParentDmName; import com.emc.sa.service.linux.tasks.GetPowerpathBlockDevices; import com.emc.sa.service.linux.tasks.GetPowerpathPrimaryPartitionDeviceParent; import com.emc.sa.service.linux.tasks.GetPrimaryPartitionDeviceMultiPath; import com.emc.sa.service.linux.tasks.LinuxExecutionTask; import com.emc.sa.service.linux.tasks.ListMountPoints; import com.emc.sa.service.linux.tasks.MountPath; import com.emc.sa.service.linux.tasks.RemoveFromFSTab; import com.emc.sa.service.linux.tasks.RemoveLunz; import com.emc.sa.service.linux.tasks.RemoveMultipathEntry; import com.emc.sa.service.linux.tasks.RemovePowerPathDevice; import com.emc.sa.service.linux.tasks.RescanBlockDevices; import com.emc.sa.service.linux.tasks.RescanHBAs; import com.emc.sa.service.linux.tasks.RescanIScsiInitiators; import com.emc.sa.service.linux.tasks.RescanPartitionMap; import com.emc.sa.service.linux.tasks.ResizeFileSystem; import com.emc.sa.service.linux.tasks.ResizeMultipathPath; import com.emc.sa.service.linux.tasks.ResizePartition; import com.emc.sa.service.linux.tasks.UnmountPath; import com.emc.sa.service.linux.tasks.UpdateMultiPathEntries; import com.emc.sa.service.linux.tasks.UpdatePowerPathEntries; import com.emc.sa.service.linux.tasks.VerifyMountPoint; import com.emc.sa.service.vipr.ViPRExecutionUtils; import com.emc.sa.service.vipr.block.BlockStorageUtils; import com.emc.sa.service.vipr.block.tasks.RemoveBlockVolumeMachineTag; import com.emc.sa.service.vipr.block.tasks.SetBlockVolumeMachineTag; import com.emc.storageos.db.client.model.HostInterface.Protocol; import com.emc.storageos.db.client.model.Initiator; import com.emc.storageos.model.block.BlockObjectRestRep; import com.emc.storageos.model.block.VolumeRestRep; import com.emc.storageos.model.block.export.ITLRestRep; import com.iwave.ext.linux.LinuxSystemCLI; import com.iwave.ext.linux.model.HBAInfo; import com.iwave.ext.linux.model.IScsiHost; import com.iwave.ext.linux.model.IScsiSession; import com.iwave.ext.linux.model.LunInfo; import com.iwave.ext.linux.model.MountPoint; import com.iwave.ext.linux.model.MultiPathEntry; import com.iwave.ext.linux.model.PowerPathDevice; public class LinuxSupport { private final LinuxSystemCLI targetSystem; private final List<Initiator> hostPorts; public LinuxSupport(LinuxSystemCLI targetSystem, List<Initiator> hostPorts) { this.targetSystem = targetSystem; this.hostPorts = hostPorts; } public String getHostName() { return targetSystem.getHost(); } protected void logDebug(String message, Object... args) { ExecutionUtils.currentContext().logDebug(message, args); } protected void logInfo(String message, Object... args) { ExecutionUtils.currentContext().logInfo(message, args); } protected void logWarn(String message, Object... args) { ExecutionUtils.currentContext().logWarn(message, args); } protected void logError(String message, Object... args) { ExecutionUtils.currentContext().logError(message, args); } protected void logError(Throwable cause, String message, Object... args) { ExecutionUtils.currentContext().logError(message, args); } public void refreshStorage(Collection<? extends BlockObjectRestRep> volumes, boolean usePowerPath) { Set<URI> virtualArrays = BlockStorageUtils.getVolumeVirtualArrays(volumes); Set<Initiator> fcInitiators = BlockStorageUtils.findInitiatorsInVirtualArrays(virtualArrays, hostPorts, Protocol.FC); Set<Initiator> iscsiInitiators = BlockStorageUtils.findInitiatorsInVirtualArrays(virtualArrays, hostPorts, Protocol.iSCSI); if (!fcInitiators.isEmpty()) { List<HBAInfo> hbas = findHBAs(fcInitiators); removeLunz(); rescanHBAs(hbas); } if (!iscsiInitiators.isEmpty()) { checkIScsiConnectivity(iscsiInitiators, volumes); rescanIScsiInitiators(); } updateMultipathingSoftware(usePowerPath); } private void checkIScsiConnectivity(Set<Initiator> initiators, Collection<? extends BlockObjectRestRep> volumes) { for (BlockObjectRestRep volume : volumes) { checkIScsiConnectivity(initiators, volume); } } private void checkIScsiConnectivity(Set<Initiator> initiators, BlockObjectRestRep volume) { List<ITLRestRep> exports = BlockStorageUtils.getExportsForBlockObject(volume.getId()); List<ITLRestRep> connectedExports = BlockStorageUtils.getExportsForInitiators(exports, initiators); // Ensure we have at least one connection to the volume Set<String> targetPorts = BlockStorageUtils.getTargetPortsForExports(connectedExports); String sourceIqns = StringUtils.join(BlockStorageUtils.getPortNames(initiators), ", "); String targetIqns = StringUtils.join(targetPorts, ", "); logInfo("linux.support.check.connectivity", sourceIqns, targetIqns); int connections = 0; List<IScsiHost> iScsiInitiators = findIScsiIInitiators(initiators); for (IScsiHost iScsiInitiator : iScsiInitiators) { for (IScsiSession session : iScsiInitiator.getSessions()) { String sourceIqn = session.getIfaceInitiatorName(); if (session.getTarget() != null) { String targetIqn = session.getTarget().getIqn(); if (targetPorts.contains(targetIqn)) { logInfo("linux.support.connected", sourceIqn, targetIqn); connections++; } } } } if (connections == 0) { List<IScsiSession> iScsiSessions = findIScsiSessions(initiators); for (IScsiSession session : iScsiSessions) { String sourceIqn = session.getIfaceInitiatorName(); if (session.getTarget() != null) { String targetIqn = session.getTarget().getIqn(); if (targetPorts.contains(targetIqn)) { logInfo("linux.support.connected", sourceIqn, targetIqn); connections++; } } } } if (connections == 0) { Object[] detailArgs = new Object[] { volume.getId(), buildInitiatorsString(initiators) }; Object[] messageArgs = new Object[] { sourceIqns, targetIqns }; ExecutionUtils.fail("failTask.LinuxSupport.iqnConnectivity", detailArgs, messageArgs); } } protected String buildInitiatorsString(Set<Initiator> initiators) { StringBuilder sb = new StringBuilder(); for (Initiator i : initiators) { sb.append(i.getId()); sb.append(","); } // remove the last comma return sb.toString().substring(0, sb.toString().lastIndexOf(",")); } private void updateMultipathingSoftware(boolean usePowerPath) { if (usePowerPath) { updatePowerPathEntries(); } else { updateMultipathEntries(); } } public void removeLunz() { List<LunInfo> lunz = execute(new FindLunz()); for (LunInfo lun : lunz) { execute(new RemoveLunz(lun.getHost(), lun.getChannel(), lun.getId())); } } protected boolean checkForMultipathingSoftware() { String powerPathError = checkForPowerPath(); if (powerPathError == null) { return true; } String multipathError = checkForMultipath(); if (multipathError == null) { return false; } ExecutionUtils.fail("failTask.LinuxSupport.noMultipath", new Object[] {}, powerPathError, multipathError); return false; // we'll never get here as .fail will throw an exception } public String checkForMultipath() { return execute(new CheckForMultipath()); } public String checkForPowerPath() { return execute(new CheckForPowerPath()); } public void updateMultipathEntries() { execute(new UpdateMultiPathEntries()); } private void updatePowerPathEntries() { execute(new UpdatePowerPathEntries()); } public List<HBAInfo> findHBAs(Collection<Initiator> ports) { return execute(new FindHBAs(BlockStorageUtils.getPortNames(ports))); } public List<IScsiHost> findIScsiIInitiators(Collection<Initiator> ports) { return execute(new FindIScsiInitiators(BlockStorageUtils.getPortNames(ports))); } public List<IScsiSession> findIScsiSessions(Collection<Initiator> ports) { return execute(new FindIScsiSessions(BlockStorageUtils.getPortNames(ports))); } public void rescanHBAs(List<HBAInfo> hbas) { execute(new RescanHBAs(hbas)); } public void rescanIScsiInitiators() { execute(new RescanIScsiInitiators()); } public MultiPathEntry findMultiPathEntry(BlockObjectRestRep volume) { return execute(new FindMultiPathEntryForVolume(volume)); } public PowerPathDevice findPowerPathEntry(BlockObjectRestRep volume) { return execute(new FindPowerPathEntryForVolume(volume)); } public void removeMultipathEntries(Collection<MultiPathEntry> entries) { for (MultiPathEntry entry : entries) { execute(new RemoveMultipathEntry(entry)); } } public void removePowerPathDevices(Collection<PowerPathDevice> devices) { for (PowerPathDevice device : devices) { execute(new RemovePowerPathDevice(device)); } } public void verifyMountPoint(String path) { execute(new VerifyMountPoint(path)); } public MountPoint findMountPoint(BlockObjectRestRep volume) { return LinuxUtils.getMountPoint(targetSystem.getHostId(), getMountPoints(), volume); } public Map<String, MountPoint> getMountPoints() { return execute(new ListMountPoints()); } public void findMountPoints(List<VolumeSpec> volumes) { execute(new FindMountPointsForVolumes(targetSystem.getHostId(), volumes)); } public void findMultipathEntries(List<VolumeSpec> volumes) { execute(new FindMultiPathEntriesForMountPoint(volumes)); } public void findPowerPathDevices(List<VolumeSpec> volumes) { execute(new FindPowerPathEntriesForMountPoint(volumes)); } public void formatVolume(String device, String fsType, String blockSize) { boolean journaling = FormatVolume.EXT3.equalsIgnoreCase(fsType) || FormatVolume.EXT4.equalsIgnoreCase(fsType); execute(new FormatVolume(device, fsType, blockSize, journaling)); } public void createDirectory(String path) { execute(new CreateDirectory(path)); addRollback(new DeleteDirectory(path)); } public void deleteDirectory(String path) { execute(new DeleteDirectory(path)); } public boolean isDirectoryEmpty(String path) { return execute(new GetDirectoryContents(path)).isEmpty(); } public void addToFSTab(String device, String path, String fsType, String options) { execute(new AddToFSTab(device, path, fsType, StringUtils.defaultIfEmpty(options, AddToFSTab.DEFAULT_OPTIONS))); addRollback(new RemoveFromFSTab(path)); } public void removeFromFSTab(String path) { execute(new RemoveFromFSTab(path)); } public void mountPath(String path) { execute(new MountPath(path)); addRollback(new UnmountPath(path)); } public void unmountPath(String path) { execute(new UnmountPath(path)); } public void setVolumeMountPointTag(BlockObjectRestRep volume, String mountPoint) { ExecutionUtils.execute(new SetBlockVolumeMachineTag(volume.getId(), getMountPointTagName(), mountPoint)); ExecutionUtils.addRollback(new RemoveBlockVolumeMachineTag(volume.getId(), getMountPointTagName())); addAffectedResource(volume.getId()); } public void removeVolumesMountPointTag(Collection<? extends VolumeRestRep> volumes) { for (VolumeRestRep volume : volumes) { removeVolumeMountPointTag(volume); } } public void removeVolumeMountPointTag(BlockObjectRestRep volume) { ExecutionUtils.execute(new RemoveBlockVolumeMachineTag(volume.getId(), getMountPointTagName())); addAffectedResource(volume.getId()); } public void ensureVolumesAreMounted(Collection<VolumeSpec> volumes) { for (VolumeSpec volume : volumes) { if (LinuxUtils.getMountPoint(targetSystem.getHostId(), getMountPoints(), volume.viprVolume) == null) { ExecutionUtils .fail("failTask.LinuxSupport.ensureVolumesAreMounted", volume.viprVolume.getId(), volume.viprVolume.getName()); } } } public void resizeFileSystem(String device) { // Force a check of the file system before and after execute(new CheckFileSystem(device, true)); execute(new ResizeFileSystem(device)); execute(new CheckFileSystem(device, true)); } public <T extends BlockObjectRestRep> String getDevice(T volume, boolean usePowerPath) { // TODO : this could probably be implemented using RetryableTask try { // we will retry this up to 5 times int remainingAttempts = 5; while (remainingAttempts-- >= 0) { try { if (usePowerPath) { return findPowerPathEntry(volume).getDevice(); } else { return LinuxUtils.getDeviceForEntry(findMultiPathEntry(volume)); } } catch (Exception e) { String errorMessage = String.format("Unable to find device for WWN %s. %s more attempts will be made.", volume.getWwn(), remainingAttempts); if (remainingAttempts == 0) { getDeviceFailed(volume, errorMessage, e); } logWarn("linux.support.device.not.found", volume.getWwn(), remainingAttempts); Thread.sleep(10000); refreshStorage(Collections.singleton(volume), usePowerPath); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return null; } protected <T extends BlockObjectRestRep> void getDeviceFailed(T volume, String errorMessage, Exception exception) { ExecutionUtils.fail("failTask.getDeviceName", volume.getWwn(), errorMessage, exception.getMessage()); } public void checkForFileSystemCompatibility(String fsType) { execute(new CheckForFileSystemCompatibility(fsType)); } /** * Method to setup rollbacks for mounting a Linux volume * @param volume to be mounted * @param mountPoint of the original mount */ public void addMountExpandRollback(BlockObjectRestRep volume, MountPoint mountPoint) { ExecutionUtils.addRollback(new SetBlockVolumeMachineTag(volume.getId(), getMountPointTagName(), mountPoint.getPath())); addRollback(new MountPath(mountPoint.getPath())); addRollback(new AddToFSTab(mountPoint.getDevice(), mountPoint.getPath(), mountPoint.getFsType(), mountPoint.getOptions())); } public void resizeVolume(BlockObjectRestRep volume, Double newSizeInGB) { BlockStorageUtils.expandVolume(volume.getId(), newSizeInGB); } public void resizePartition(String device) { execute(new ResizePartition(device)); execute(new RescanPartitionMap(device)); } public void resizeMultipathPath(String device) { execute(new ResizeMultipathPath(device)); } protected <T> T execute(LinuxExecutionTask<T> task) { task.setTargetSystem(targetSystem); return ViPRExecutionUtils.execute(task); } public void addRollback(LinuxExecutionTask<?> rollbackTask) { rollbackTask.setTargetSystem(targetSystem); ExecutionUtils.addRollback(rollbackTask); } private String getMountPointTagName() { return KnownMachineTags.getHostMountPointTagName(targetSystem.getHostId()); } public String getPrimaryPartitionDevice(BlockObjectRestRep volume, String mountPoint, String device, boolean usePowerPath) { if (usePowerPath) { String deviceName = StringUtils.substringAfterLast(device, "/"); List<String> contents = execute(new GetDirectoryContents("/sys/block/" + deviceName)); for (String content : contents) { if (content.startsWith(deviceName)) { logInfo("linux.support.powerpath.name", deviceName); return StringUtils.substringBeforeLast(device, "/") + "/" + content; } } ExecutionUtils.fail("failTask.LinuxSupport.getPrimaryPartitionDevice", device, device); return StringUtils.EMPTY; // we will never get here - .fail() will throw an exception } else { String dmname = findMultiPathEntry(volume).getDmName(); logInfo("linux.support.multipath.name", dmname); return execute(new GetPrimaryPartitionDeviceMultiPath(device, dmname)); } } public List<String> getBlockDevices(String parentDevice, BlockObjectRestRep volume, boolean usePowerPath) { if (usePowerPath) { return execute(new GetPowerpathBlockDevices(parentDevice)); } else { String dmname = findMultiPathEntry(volume).getDmName(); return execute(new GetMultipathBlockDevices(dmname)); } } public void rescanBlockDevices(List<String> devices) { execute(new RescanBlockDevices(devices)); } public String getParentDevice(String device, boolean usePowerPath) { if (usePowerPath) { return execute(new GetPowerpathPrimaryPartitionDeviceParent(device)); } else { return getParentMultipathDevice(device); } } protected String getParentMultipathDevice(String device) { String parentDeviceDmName = execute(new GetMultipathPrimaryPartitionDeviceParentDmName(device)); MultiPathEntry multipathEntry = execute(new FindMultiPathEntryForDmName(parentDeviceDmName)); return StringUtils.substringBeforeLast(device, "/") + "/" + multipathEntry.getName(); } }