/*************************************************************************
* (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* This file may incorporate work covered under the following copyright
* and permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms,
* with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
* THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
* COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
* AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
* SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
* WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
* REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
* IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
* NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
************************************************************************/
package com.eucalyptus.blockstorage;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import com.eucalyptus.blockstorage.entities.DirectStorageInfo;
import com.eucalyptus.blockstorage.util.StorageProperties;
import com.eucalyptus.component.Faults;
import com.eucalyptus.system.Threads;
import com.eucalyptus.util.EucalyptusCloudException;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import edu.ucsb.eucalyptus.util.StreamConsumer;
import edu.ucsb.eucalyptus.util.SystemUtil.CommandOutput;
/**
* Wraps calls to TGT for handling timeouts and error codes Cannot always trust TGT error codes for failure status. Most calls also check the error
* stream.
*
*/
public class TGTWrapper {
private static final String TGTADM = "tgtadm";
final public static String TGT_SERVICE_NAME = "tgtd";
final private static Logger LOG = Logger.getLogger(TGTWrapper.class);
final private static String ROOT_WRAP = StorageProperties.EUCA_ROOT_WRAPPER;
private static final ReadWriteLock serviceLock = new ReentrantReadWriteLock();
private static ExecutorService service; // do not access directly, use getExecutor / getExecutorWithInit
final private static int RESOURCE_NOT_FOUND = 22; // The tgt return code for resource not found.
// If you change the below INITIATOR_ACCESS_LIST, change the javadoc comments for bindTarget() and unbindTarget()
final private static String INITIATOR_ACCESS_LIST = "ALL"; // Any initiator can access this target
// TODO define fault IDs in a enum
private static final int TGT_HOSED = 2000;
private static final int TGT_CORRUPTED = 2002;
private static final Joiner JOINER = Joiner.on(" ").skipNulls();
private static final Splitter LINE_SPLITTER = Splitter.on('\n').omitEmptyStrings().trimResults();
private static final Pattern LUN_PATTERN = Pattern.compile("\\s*LUN:\\s*(\\d+)\\s*");
private static final Pattern TARGET_PATTERN = Pattern.compile("\\s*Target\\s*(\\d+):\\s+(\\S+)\\s*");
private static final Pattern RESOURCE_PATTERN = Pattern.compile("\\s*Backing store path:\\s*(\\S+)\\s*");
private static final Pattern USER_HEADER_PATTERN = Pattern.compile("\\s*Account information:\\s*");
private static final Pattern INITIATORS_HEADER_PATTERN = Pattern.compile("\\s*ACL information:\\s*");
private static final Pattern TRIMMED_ANYTHING_PATTERN = Pattern.compile("\\s*(\\S+)\\s*");
public static class ResourceNotFoundException extends EucalyptusCloudException {
private static final long serialVersionUID = 1L;
public ResourceNotFoundException() {
super("Resource not found");
}
public ResourceNotFoundException(String resource) {
super("Resource " + resource + " not found");
}
}
public static class OperationFailedException extends EucalyptusCloudException {
private static final long serialVersionUID = 1L;
private int errorCode = -1;
private String outputContent = null;
private String errorContent = null;
public OperationFailedException() {
super("TGT operation failed");
}
public OperationFailedException(String message) {
super("TGT operation failed: " + message);
}
public OperationFailedException(String operation, String message) {
super("TGT operation " + operation + " failed: " + message);
}
public OperationFailedException(String output, String errorOutput, int errorCode) {
super("TGT operation failed");
this.outputContent = output;
this.errorCode = errorCode;
this.errorContent = errorOutput;
}
public OperationFailedException(EucalyptusCloudException e) {
super(e);
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public String getOutputContent() {
return outputContent;
}
public void setOutput(String output) {
this.outputContent = output;
}
public String getErrorContent() {
return errorContent;
}
public void setErrorContent(String errorOutput) {
this.errorContent = errorOutput;
}
}
public static class CallTimeoutException extends EucalyptusCloudException {
private static final long serialVersionUID = 1L;
public CallTimeoutException() {
super("Call timed out");
}
public CallTimeoutException(String command) {
super("Call timed for " + command);
}
}
/**
* Idempotent start
*/
public static void start() {
serviceLock.writeLock().lock();
try {
if (service == null) {
service = Executors.newFixedThreadPool(10, Threads.threadFactory( "storage-tgt-pool-%d" ));
}
} finally {
serviceLock.writeLock().unlock();
}
}
public static void stop() {
serviceLock.writeLock().lock();
try {
ExecutorService toShutdown = service;
service = null;
toShutdown.shutdownNow();
} catch (Exception e) {
LOG.warn("Unable to shutdown thread pool", e);
} finally {
serviceLock.writeLock().unlock();
}
}
private static ExecutorService getExecutor() {
serviceLock.readLock().lock();
try {
if (service == null) {
throw new IllegalStateException("Not started");
}
return service;
} finally {
serviceLock.readLock().unlock();
}
}
private static ExecutorService getExecutorWithInit() {
ExecutorService service;
try {
service = getExecutor();
} catch (IllegalStateException e) {
start();
service = getExecutor();
}
return service;
}
/**
* Separate thread to wait for {@link java.lang.Process Process} to complete and return its exit value
*
*/
public static class ProcessMonitor implements Callable<Integer> {
private Process process;
ProcessMonitor(Process process) {
this.process = process;
}
public Integer call() throws Exception {
process.waitFor();
return process.exitValue();
}
}
// Implementation of EUCA-3597
/**
* executeTGTs the specified tgt command in a separate process. A {@link DirectStorageInfo#timeoutInMillis timeout} is enforced on the process using
* {@link java.util.concurrent.ExecutorService ExecutorService} framework. If the process does not complete with in the timeout, it is cancelled.
*
* @param command
* @param timeout
* @return CommandOutput
* @throws EucalyptusCloudException
*/
private static CommandOutput execute(@Nonnull String[] command, @Nonnull Long timeout) throws EucalyptusCloudException, CallTimeoutException {
try {
Integer returnValue = -999999;
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command);
StreamConsumer error = new StreamConsumer(process.getErrorStream());
StreamConsumer output = new StreamConsumer(process.getInputStream());
error.start();
output.start();
Callable<Integer> processMonitor = new ProcessMonitor(process);
Future<Integer> processController = getExecutorWithInit().submit(processMonitor);
try {
returnValue = processController.get(timeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException tex) {
String commandStr = buildCommand(command);
LOG.error(commandStr + " timed out. Cancelling the process, logging a fault and exceptioning out");
processController.cancel(true);
Faults.forComponent(Storage.class).havingId(TGT_HOSED).withVar("component", "Storage Controller").withVar("timeout", Long.toString(timeout))
.log();
throw new CallTimeoutException("No response from the command " + commandStr + ". Process timed out after waiting for " + timeout
+ " milliseconds");
}
output.join();
error.join();
LOG.debug("TGTWrapper executed: " + JOINER.join(command) + "\n return=" + returnValue + "\n stdout=" + output.getReturnValue() + "\n stderr="
+ error.getReturnValue());
return new CommandOutput(returnValue, output.getReturnValue(), error.getReturnValue());
} catch (CallTimeoutException e) {
throw e;
} catch (Exception ex) {
throw new EucalyptusCloudException(ex);
}
}
/**
* Calls exclusively for TGT commands, does some analysis of return code and error stream to send better exceptions
*
* @param command
* @param timeout
* @return
*
* @throws CallTimeoutException
* @throws OperationFailedException
* @throws ResourceNotFoundException
*/
private static CommandOutput executeTGT(@Nonnull String[] command, @Nonnull Long timeout) throws OperationFailedException, CallTimeoutException,
ResourceNotFoundException {
CommandOutput output = null;
try {
output = execute(command, timeout);
} catch (ResourceNotFoundException e) {
throw e;
} catch (CallTimeoutException e) {
throw e;
} catch (EucalyptusCloudException e) {
throw new OperationFailedException(e);
}
if (output.returnValue == 22) {
if (output.error.contains("target"))
throw new ResourceNotFoundException("target");
if (output.error.contains("account"))
throw new ResourceNotFoundException("account");
if (output.error.contains("logicalunit"))
throw new ResourceNotFoundException("logicalunit");
throw new ResourceNotFoundException();
}
return output;
}
private static String buildCommand(@Nonnull String[] command) {
StringBuilder builder = new StringBuilder();
for (String part : command) {
builder.append(part).append(' ');
}
return builder.toString();
}
/**
* Runs pre-checks for startup etc.
* If we can run a tgtadm command that connects to the tgtd and
* does not return an error, assume we are OK.
*
* @param timeout
* @throws EucalyptusCloudException
*/
public static void precheckService(Long timeout) throws EucalyptusCloudException {
CommandOutput output = null;
output = execute(new String[] {ROOT_WRAP, "tgtadm", "--help"}, timeout);
if (output.returnValue != 0 || StringUtils.isNotBlank(output.error)) {
String errmsg = "Unable to run 'tgtadm --help' command " +
"(SCSI Target Administration Utility). " +
"Is the scsi-target-utils package installed? " +
"Is the tgtd service running? " +
"Is /usr/sbin/tgtadm accessible? " +
"Error output: " + output.error;
LOG.warn(errmsg);
Faults.forComponent(Storage.class).havingId(TGT_CORRUPTED).withVar("component", "Storage Controller")
.withVar("operation", "tgtadm --help")
.withVar("error", output.error).log();
throw new EucalyptusCloudException(errmsg);
}
checkService(timeout);
}
/**
* Runs a service check on TGT.
* If we can run a tgtadm command that connects to the tgtd and
* does not return an error, assume we are OK.
*
* @param timeout
* @throws EucalyptusCloudException
*/
public static void checkService(Long timeout) throws EucalyptusCloudException {
CommandOutput output = null;
output = execute(new String[] {ROOT_WRAP, "tgtadm", "--lld", "iscsi", "--mode", "target", "--op", "show"}, timeout);
if (output.returnValue != 0 || StringUtils.isNotBlank(output.error)) {
String cmdline = "tgtadm --lld iscsi --mode target --op show";
String errmsg = "Unable to run tgtadm command " +
"(SCSI Target Administration Utility). " +
"Is the scsi-target-utils package installed? " +
"Is the tgtd service running? " +
"Is /usr/sbin/tgtadm accessible? " +
"Attempted command: '" + cmdline + "' " +
"Error output: " + output.error;
LOG.warn(errmsg);
Faults.forComponent(Storage.class).havingId(TGT_CORRUPTED).withVar("component", "Storage Controller")
.withVar("operation", cmdline)
.withVar("error", output.error).log();
throw new EucalyptusCloudException(errmsg);
}
}
/**
* Creates a new target with the given name and target ID
*
* @param volumeId
* @param tid
* @param name
* @param timeout
* @return true on success, false on failure
* @throws CallTimeoutException
* @throws EucalyptusCloudException
*/
public static void createTarget(@Nonnull String volumeId, int tid, @Nonnull String name, @Nonnull Long timeout) throws CallTimeoutException,
OperationFailedException, ResourceNotFoundException {
CommandOutput output =
executeTGT(new String[] {ROOT_WRAP, TGTADM, "--lld", "iscsi", "--op", "new", "--mode", "target", "--tid", String.valueOf(tid), "-T", name},
timeout);
if (output.failed() || StringUtils.isNotBlank(output.error)) {
throw new OperationFailedException(output.output, output.error, output.returnValue);
}
}
/**
* Removes the target from TGT, only operates on the given target
*
* @param volumeId
* @param tid
* @param timeout
* @param force
* @return
* @throws EucalyptusCloudException
*/
public static void deleteTarget(@Nonnull String volumeId, int tid, @Nonnull Long timeout, boolean force) throws OperationFailedException,
ResourceNotFoundException, CallTimeoutException {
LOG.debug("Tearing down target " + tid + " for volume " + volumeId);
CommandOutput output = null;
if (force) {
output =
executeTGT(
new String[] {ROOT_WRAP, TGTADM, "--lld", "iscsi", "--op", "delete", "--mode", "target", "--tid", String.valueOf(tid), "--force"},
timeout);
} else {
output =
executeTGT(new String[] {ROOT_WRAP, TGTADM, "--lld", "iscsi", "--op", "delete", "--mode", "target", "--tid", String.valueOf(tid)}, timeout);
}
if (output.failed() || StringUtils.isNotBlank(output.error)) {
throw new OperationFailedException(output.output, output.error, output.returnValue);
}
}
/**
* Creates a lun in the given target that is backed by the resource in resourcePath (file/block device)
*
* @param volumeId
* @param tid
* @param lun
* @param resourcePath
* @param timeout
* @throws ResourceNotFoundException
* @throws CallTimeoutException
* @throws EucalyptusCloudException
*/
public static void createLun(@Nonnull String volumeId, int tid, int lun, @Nonnull String resourcePath, @Nonnull Long timeout)
throws OperationFailedException, ResourceNotFoundException, CallTimeoutException {
CommandOutput output =
executeTGT(new String[] {ROOT_WRAP, TGTADM, "--lld", "iscsi", "--op", "new", "--mode", "logicalunit", "--tid", String.valueOf(tid), "--lun",
String.valueOf(lun), "-b", resourcePath}, timeout);
if (output.failed() || StringUtils.isNotBlank(output.error)) {
if (output.returnValue == 22) {
throw new ResourceNotFoundException(String.valueOf(tid));
}
throw new OperationFailedException("Create lun operation failed");
}
}
/**
* Removes the target from TGT, only operates on the given target
*
* @param volumeId
* @param tid
* @param lun
* @param timeout
*/
public static void deleteLun(@Nonnull String volumeId, int tid, int lun, @Nonnull Long timeout) throws OperationFailedException,
ResourceNotFoundException, CallTimeoutException {
LOG.debug("Removing LUN " + lun + " from target " + tid + " for volume " + volumeId);
CommandOutput output =
executeTGT(new String[] {ROOT_WRAP, TGTADM, "--lld", "iscsi", "--op", "delete", "--mode", "logicalunit", "--tid", String.valueOf(tid),
"--lun", String.valueOf(lun)}, timeout);
if (output.failed() || StringUtils.isNotBlank(output.error)) {
// If the logical unit is not found, return true, since it is not in the target.
if (output.returnValue == RESOURCE_NOT_FOUND && output.error.contains("can't find the logical unit")) {
LOG.debug("Volume: " + volumeId + " logical unit already removed.");
}
throw new OperationFailedException("Delete lun operation failed");
}
}
/**
* Binds a chap user/account to the target, requiring initiators to use chap to connect
*
* @param volumeId
* @param user
* @param tid
* @param timeout
* @throws ResourceNotFoundException
* @throws CallTimeoutException
* @throws EucalyptusCloudException
*/
public static void bindUser(@Nonnull String volumeId, @Nonnull String user, int tid, @Nonnull Long timeout) throws OperationFailedException,
ResourceNotFoundException, CallTimeoutException {
CommandOutput output =
executeTGT(new String[] {ROOT_WRAP, TGTADM, "--lld", "iscsi", "--op", "bind", "--mode", "account", "--tid", String.valueOf(tid), "--user",
user}, timeout);
if (output.failed() || StringUtils.isNotBlank(output.error)) {
if (output.returnValue == 22 && output.error.contains("account")) {
throw new ResourceNotFoundException(user);
} else if (output.returnValue == 22 && output.error.contains("target")) {
throw new ResourceNotFoundException("Target " + String.valueOf(tid));
}
throw new OperationFailedException("Bind user operation failed");
}
}
/**
* Binds the target to all host initiators (permits connections)
*
* @param volumeId
* @param tid
* @param timeout
* @return
* @throws ResourceNotFoundException
* @throws CallTimeoutException
* @throws EucalyptusCloudException
*/
public static void bindTarget(@Nonnull String volumeId, int tid, @Nonnull Long timeout) throws OperationFailedException, ResourceNotFoundException,
CallTimeoutException {
try {
CommandOutput output =
executeTGT(
new String[] {ROOT_WRAP, TGTADM, "--lld", "iscsi", "--op", "bind", "--mode", "target", "--tid", String.valueOf(tid), "-I", INITIATOR_ACCESS_LIST},
timeout);
} catch (EucalyptusCloudException e) {
}
}
/**
* Unbinds all initiators from the given target.
*
* @param volumeId
* @param tid
* @param lun
* @param timeout
* @return
*/
public static void unbindTarget(String volumeId, int tid, @Nonnull Long timeout) throws OperationFailedException, ResourceNotFoundException,
CallTimeoutException {
LOG.debug("Unbinding target " + tid + " for volume " + volumeId);
CommandOutput output =
executeTGT(
new String[] {ROOT_WRAP, TGTADM, "--lld", "iscsi", "--op", "unbind", "--mode", "target", "--tid", String.valueOf(tid), "-I", INITIATOR_ACCESS_LIST},
timeout);
if (output.failed() || StringUtils.isNotBlank(output.error)) {
if (output.returnValue == RESOURCE_NOT_FOUND && output.error.contains("can't find the target")) {
LOG.debug("Volume: " + volumeId + " target not found, cannot unbind, returning unbind success.");
throw new ResourceNotFoundException("target " + tid);
}
LOG.error("Volume: " + volumeId + " Unable to unbind tid: " + tid);
throw new OperationFailedException(output.output, output.error, output.returnValue);
}
}
/**
* Checks if the target exists, and optionally checks the resource for that target too.
*
* @param volumeId the volume ID, e.g. "vol-de174154"
* @param tid the target ID, e.g. 32
* @param resource the backing resource, e.g. "/dev/euca-ebs-storage-vg-vol-de174154/euca-vol-de174154". May be null.
* @param timeout timeout in milliseconds for each tgtadm command to complete
* @return If a target with the given tid exists, and the given resource is null, returns true.
* If the resource is not null, and that resource must be backing a LUN of that target, returns true.
* A LUN ID check is not done, any LUN number in the target will match.
* Otherwise returns false.
*/
public static boolean targetExists(@Nonnull String volumeId, int tid, String resource, @Nonnull Long timeout) throws EucalyptusCloudException {
// Don't check for user nor initiator list
return targetConfigured(volumeId, tid, resource, timeout, null, false);
}
/**
* Checks if the target exists, and optionally checks its configuration.
*
* @param volumeId the volume ID, e.g. "vol-de174154"
* @param tid the target ID, e.g. 32
* @param resource the backing resource, e.g. "/dev/euca-ebs-storage-vg-vol-de174154/euca-vol-de174154". May be null.
* @param timeout timeout in milliseconds for each tgtadm command to complete
* @param user the account user that must be bound to that target. May be null.
* @param checkInitiators if true, check that the access list (ACL) of initiators is correct. If false, don't check.
* @return If a target with the given tid exists, and the given resource is null, returns true.
* If the resource is not null, and that resource must be backing a LUN of that target, returns true
* if the supplied user is the user bound to that target and the initiators list is correct, skipping
* either check if they are null/false.
* A LUN ID check is not done, any LUN number in the target will match.
* Otherwise returns false.
*/
public static boolean targetConfigured(@Nonnull String volumeId, int tid, String resource, @Nonnull Long timeout,
String user, boolean checkInitiators) throws EucalyptusCloudException {
try {
CommandOutput output =
executeTGT(new String[] {ROOT_WRAP, "tgtadm", "--lld", "iscsi", "--op", "show", "--mode", "target", "--tid", String.valueOf(tid)}, timeout);
if (StringUtils.isBlank(output.error)) {
if (resource != null) {
output = executeTGT(new String[] {ROOT_WRAP, "tgtadm", "--lld", "iscsi", "--op", "show", "--mode", "target"}, timeout);
if (hasResource(output.output, tid, resource, user, checkInitiators)) {
LOG.debug("Volume " + volumeId + " check for target " + tid + " and resource " + resource + " returning true. Target exists");
return true;
} else {
LOG.debug("Volume: " + volumeId + " Target: " + tid + " Resource: " + resource +
(user == null ? "" : " User: " + user) +
(checkInitiators ? " Initiators: " + INITIATOR_ACCESS_LIST : "") +
" not found");
return false;
}
} else {
LOG.debug("Volume " + volumeId + " check for target " + tid + " returning true. Target exists");
return true;
}
} else {
LOG.debug("Volume: " + volumeId + " Target: " + tid + " not found");
return false;
}
} catch (ResourceNotFoundException e) {
// Fall through, return false
} catch (EucalyptusCloudException e) {
// This case includes call timeouts
LOG.error("Caught unexpected exception checking for target existence for volume " + volumeId, e);
throw e;
}
return false;
}
/**
* Returns true if target found had the requested lun
*
* @param volumeId
* @param tid
* @param lun
* @param timeout
* @return
* @throws EucalyptusCloudException
*/
public static boolean targetHasLun(@Nonnull String volumeId, int tid, int lun, @Nonnull Long timeout) throws EucalyptusCloudException {
try {
CommandOutput output =
executeTGT(new String[] {ROOT_WRAP, "tgtadm", "--lld", "iscsi", "--op", "show", "--mode", "target", "--tid", String.valueOf(tid)}, timeout);
output = executeTGT(new String[] {ROOT_WRAP, "tgtadm", "--lld", "iscsi", "--op", "show", "--mode", "target"}, timeout);
return hasLun(output.output, tid, lun);
} catch (ResourceNotFoundException e) {
// Fall through, return false
} catch (EucalyptusCloudException e) {
// This case includes call timeouts
LOG.error("Caught unexpected exception checking for target existence for volume " + volumeId, e);
throw e;
}
return false;
}
/**
* Check the output for the given resource string, but only if the tid matches
*
* @param output
* @param resource
* @return
*/
private static boolean hasResource(@Nonnull String output, int tid, @Nonnull String resource, String user, boolean checkInitiators) {
Matcher targetMatcher = null;
Matcher resourceMatcher = null;
Matcher userHeaderMatcher = null;
Matcher userMatcher = null;
Matcher initiatorsHeaderMatcher = null;
Matcher initiatorsMatcher = null;
String target = null;
boolean resourceFound = false;
boolean userHeaderFound = false;
boolean userFound = false;
boolean initiatorsHeaderFound = false;
boolean initiatorsFound = false;
for (String line : LINE_SPLITTER.split(output)) {
// Look for a target ID line even after we find ours. This ensures that if we
// don't find what we're looking for before the next target ID line, we won't
// find it in a later target because we'll break out at the next target ID.
targetMatcher = TARGET_PATTERN.matcher(line);
if (targetMatcher.matches()) {
if (target == null) {
if (Integer.parseInt(targetMatcher.group(1)) == tid) {
// Found the target
target = targetMatcher.group(1);
}
} else {
// We already found the target, so this is the next target.
// So we never found what we were looking for in the target we wanted. Get out.
break;
}
continue;
}
if (target == null) {
// No reason to keep looking at this line
continue;
}
if (!resourceFound) {
// Found the target already, looking for the resource (LUN)
resourceMatcher = RESOURCE_PATTERN.matcher(line);
if (resourceMatcher.matches() && resourceMatcher.group(1).equals(resource)) {
resourceFound = true;
}
continue;
}
// We've already found the target and its LUN.
// Only check the target's user account if the user parameter is provided
if (user != null && !userFound) {
if (!userHeaderFound) {
// Looking for the account user header line
userHeaderMatcher = USER_HEADER_PATTERN.matcher(line);
if (userHeaderMatcher.matches()) {
userHeaderFound = true;
continue;
}
// Not the header line, fall through to look for initiators
} else {
// We found the user account header already, so this line better be the user
userMatcher = TRIMMED_ANYTHING_PATTERN.matcher(line);
if (userMatcher.matches()) {
if (userMatcher.group(1).equals(user)) {
userFound = true;
// Fall through to the final check
} else {
// The user is not the right one, fail.
return false;
}
} else {
// Couldn't match anything in the line, so no user.
return false;
}
}
} // end if looking for user
// Only try to match the target's initiator list if the parameter is provided
if (checkInitiators && !initiatorsFound) {
if (!initiatorsHeaderFound) {
// Looking for the initiators header line
initiatorsHeaderMatcher = INITIATORS_HEADER_PATTERN.matcher(line);
if (initiatorsHeaderMatcher.matches()) {
initiatorsHeaderFound = true;
continue;
}
// Not the header line, fall through to the final check
} else {
// We found the initiators header already, so this line better be the initiator list
initiatorsMatcher = TRIMMED_ANYTHING_PATTERN.matcher(line);
if (initiatorsMatcher.matches()) {
if (initiatorsMatcher.group(1).equals(INITIATOR_ACCESS_LIST)) {
initiatorsFound = true;
// Fall through to the final check
} else {
// The initiators list is not right, fail.
return false;
}
} else {
// Couldn't match anything in the line, so no initiators list.
return false;
}
}
} // end if looking for initiators
if ((user == null || userFound) &&
(!checkInitiators || initiatorsFound)) {
// Found what we needed, we're done
return true;
}
} // end for each line in the tgtadm output
// Never found what we were looking for
return false;
} // end hasResource()
/**
* Check the output for the given lun
*
* @param output
* @param lun
* @param tid the target to look in
* @return
*/
private static boolean hasLun(@Nonnull String output, int tid, int lun) {
Matcher targetMatcher = null;
Matcher lunMatcher = null;
String target = null;
for (String line : LINE_SPLITTER.split(output)) {
// Look for a target ID line even after we find ours. This ensures that if we
// don't find what we're looking for before the next target ID line, we won't
// find it in a later target because we'll break out at the next target ID.
targetMatcher = TARGET_PATTERN.matcher(line);
if (targetMatcher.matches()) {
if (target == null) {
if (Integer.parseInt(targetMatcher.group(1)) == tid) {
// Found the target
target = targetMatcher.group(1);
}
} else {
// We already found the target, so this is the next target.
// So we never found what we were looking for in the target we wanted. Get out.
break;
}
continue;
}
if (target == null) {
// No reason to keep looking at this line
continue;
}
// Only try lun match if found the target we're looking for
lunMatcher = LUN_PATTERN.matcher(line);
if (lunMatcher.matches() && lunMatcher.group(1).equals(String.valueOf(lun))) {
return true;
}
}
return false;
}
public static void addUser(@Nonnull String username, @Nonnull String password, @Nonnull Long timeout) throws OperationFailedException,
ResourceNotFoundException, CallTimeoutException {
executeTGT(new String[] {ROOT_WRAP, "tgtadm", "--lld", "iscsi", "--op", "new", "--mode", "account", "--user", username, "--password", password},
timeout);
}
public static void deleteUser(@Nonnull String username, @Nonnull Long timeout) throws OperationFailedException, ResourceNotFoundException,
CallTimeoutException {
executeTGT(new String[] {ROOT_WRAP, "tgtadm", "--lld", "iscsi", "--op", "delete", "--mode", "account", "--user", username}, timeout);
}
public static boolean userExists(@Nonnull String username, @Nonnull Long timeout) throws OperationFailedException, ResourceNotFoundException,
CallTimeoutException {
CommandOutput output = executeTGT(new String[] {ROOT_WRAP, "tgtadm", "--op", "show", "--mode", "account"}, timeout);
String returnValue = output.output;
if (returnValue.length() > 0) {
Pattern p = Pattern.compile(username);
Matcher m = p.matcher(returnValue);
if (m.find())
return true;
else
return false;
}
return false;
}
}