use of org.dcache.xrootd.protocol.messages.RedirectResponse in project dcache by dCache.
the class XrootdRedirectHandler method doOnOpen.
/**
* For client-server read and write, the open, if successful, will always result in a redirect
* response to the proper pool; hence no subsequent requests like sync, read, write or close are
* expected at the door.
* <p>
* For third-party copy where dCache is the source, the interactions are as follows:
* <p>
* 1. The client opens the file to check availability (the 'placement' stage). An OK response
* is followed by the client closing the file. 2. The client opens the file again with
* rendezvous metadata. The client will close the file only when notified by the destination
* server that the transfer has completed. 3. The destination server will open the file for the
* actual read.
* <p>
* The order of 2, 3 is not deterministic; hence the response here must provide for the
* possibility that the destination server attempts an open before the client specifies a
* time-to-live on the rendezvous point.
* <p>
* The strategy adopted is therefore as follows: response to (1) is simply to check file
* permissions. No metadata is generated and a "dummy" file handle is returned. For 2 and 3,
* whichever occurs first will cause a metadata object to be stored. If the destination server
* open occurs first, a wait response will tell the server to try again in a maximum of 3
* seconds; otherwise, if the request matches and occurs within the ttl, the mover will be
* started and the destination redirected to the pool. Response to the client will carry a file
* handle but will not actually open a mover. The close from the client is handled at the door
* by removing the rendezvous information.
* <p>
* Third-party copy where dCache is the destination should proceed with the usual upload
* transfer creation, but when the client is redirected to the pool and calls kXR_open there, a
* third-party client will be started which does read requests from the source and then writes
* the data to the mover channel.
* <p>
* NOTE: with the changed TPC Lite protocol, the client is not required to open the source
* again during the copy phase (2) if delegation is being used.
*/
@Override
protected XrootdResponse<OpenRequest> doOnOpen(ChannelHandlerContext ctx, OpenRequest req) {
/*
* TODO
*
* We ought to process this asynchronously to not block the calling thread during
* staging or queuing. We should also switch to an asynchronous reply model if
* the request is nearline or is queued on a pool. The naive approach to always
* use an asynchronous reply model doesn't work because the xrootd 3.x client
* introduces an artificial 1 second delay when processing such a response.
*/
InetSocketAddress localAddress = getDestinationAddress();
InetSocketAddress remoteAddress = getSourceAddress();
LoginSessionInfo loginSessionInfo = sessionInfo();
Map<String, String> opaque;
try {
opaque = OpaqueStringParser.getOpaqueMap(req.getOpaque());
if (opaque.isEmpty()) {
/*
* create a new HashMap as empty opaque map is immutable
*/
opaque = new HashMap<>();
}
} catch (ParseException e) {
_log.warn("Ignoring malformed open opaque {}: {}", req.getOpaque(), e.getMessage());
opaque = new HashMap<>();
}
try {
FsPath path = createFullPath(req.getPath());
XrootdResponse response = conditionallyHandleThirdPartyRequest(req, loginSessionInfo, opaque, path, remoteAddress.getHostName());
if (response != null) {
return response;
}
FilePerm neededPerm = req.getRequiredPermission();
_log.info("Opening {} for {}", req.getPath(), neededPerm.xmlText());
if (_log.isDebugEnabled()) {
logDebugOnOpen(req);
}
String ioQueue = appSpecificQueue(req);
Long size = null;
try {
String value = opaque.get("oss.asize");
if (value != null) {
size = Long.valueOf(value);
}
} catch (NumberFormatException exception) {
_log.warn("Ignoring malformed oss.asize: {}", exception.getMessage());
}
_log.info("OPAQUE : {}", opaque);
Set<String> triedHosts = extractTriedHosts(opaque);
UUID uuid = UUID.randomUUID();
opaque.put(UUID_PREFIX, uuid.toString());
/*
* In case this is a third-party open as destination,
* pass the client information to the pool.
*/
opaque.put("org.dcache.xrootd.client", getTpcClientId(req.getSession()));
String opaqueString = OpaqueStringParser.buildOpaqueString(opaque);
/*
* Interact with core dCache to open the requested file.
*/
XrootdTransfer transfer;
if (neededPerm == FilePerm.WRITE) {
/**
* boolean createDir = req.isMkPath() has
* been changed to default to true
* so as to conform to the general expectations that this
* behavior should not depend on the client.
*/
boolean overwrite = req.isDelete() && !req.isNew();
boolean persistOnSuccessfulClose = (req.getOptions() & XrootdProtocol.kXR_posc) == XrootdProtocol.kXR_posc;
// TODO: replace with req.isPersistOnSuccessfulClose() with the latest xrootd4j
transfer = _door.write(remoteAddress, path, triedHosts, ioQueue, uuid, true, overwrite, size, loginSessionInfo.getMaximumUploadSize(), localAddress, loginSessionInfo.getSubject(), loginSessionInfo.getRestriction(), persistOnSuccessfulClose, ((loginSessionInfo.isLoggedIn()) ? loginSessionInfo.getUserRootPath() : _rootPath), req.getSession().getDelegatedCredential(), opaque);
} else {
/*
* If this is a tpc transfer, then dCache is source here.
*
* Since we accept (from the destination server) any
* valid form of authentication, but without requiring
* the associated user to be mapped, we can override
* file permission restrictions (since we possess the
* 'token' rendezvous key, and the client file permissions
* have been checked during its open request).
*/
Subject subject;
if (opaque.get("tpc.key") == null) {
subject = loginSessionInfo.getSubject();
} else {
subject = Subjects.ROOT;
}
transfer = _door.read(remoteAddress, path, triedHosts, ioQueue, uuid, localAddress, subject, loginSessionInfo.getRestriction(), opaque);
/*
* Again, if this is a tpc transfer, then dCache is source here.
* The transfer is initiated by the destination server
* (= current session). However, we wish the doorinfo
* client in billing to reflect the original user connection,
* so we overwrite the transfer client address, which
* is unused by the mover.
*/
String client = opaque.get("tpc.org");
if (client != null) {
int index = client.indexOf("@");
if (index != -1 && index < client.length() - 1) {
client = client.substring(index + 1);
transfer.setClientAddress(new InetSocketAddress(client, 0));
}
}
}
/*
* ok, open was successful
*/
InetSocketAddress address = transfer.getRedirect();
/*
* Do not use the IP address as host name, as this will block
* TLS from working.
*
* According to https://tools.ietf.org/html/rfc5280#section-4.2.1.6
* an IP is required to be in the list of Subject Alternative Names
* in the host certificate, but these are rarely added in practice.
* TLS enforces the RFC and this is a workaround.
*/
String host = address.getHostName();
if (InetAddresses.isInetAddress(host)) {
_log.warn("Unable to resolve IP address {} " + "to a canonical name", host);
}
_log.info("Redirecting to {}, {}", host, address);
return new RedirectResponse<>(req, host, address.getPort(), opaqueString, "");
} catch (ParseException e) {
return withError(req, kXR_ArgInvalid, "Path arguments do not parse");
} catch (FileNotFoundCacheException e) {
return withError(req, xrootdErrorCode(e.getRc()), "No such file");
} catch (FileExistsCacheException e) {
return withError(req, kXR_NotAuthorized, "File already exists");
} catch (TimeoutCacheException e) {
return withError(req, xrootdErrorCode(e.getRc()), "Internal timeout");
} catch (PermissionDeniedCacheException e) {
return withError(req, xrootdErrorCode(e.getRc()), e.getMessage());
} catch (FileIsNewCacheException e) {
return withError(req, xrootdErrorCode(e.getRc()), "File is locked by upload");
} catch (NotFileCacheException e) {
return withError(req, xrootdErrorCode(e.getRc()), "Not a file");
} catch (CacheException e) {
return withError(req, xrootdErrorCode(e.getRc()), String.format("Failed to open file (%s [%d])", e.getMessage(), e.getRc()));
} catch (InterruptedException e) {
/* Interrupt may be caused by cell shutdown or client
* disconnect. If the client disconnected, then the error
* message will never reach the client, so saying that the
* server shut down is okay.
*/
return withError(req, kXR_ServerError, "Server shutdown");
} catch (XrootdException e) {
return withError(req, e.getError(), e.getMessage());
}
}
use of org.dcache.xrootd.protocol.messages.RedirectResponse in project dcache by dCache.
the class AccessLogHandler method write.
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof XrootdResponse<?> && logger.isErrorEnabled()) {
XrootdResponse<?> response = (XrootdResponse<?>) msg;
XrootdRequest request = response.getRequest();
NetLoggerBuilder.Level level;
if (response instanceof ErrorResponse) {
level = ERROR;
} else if (request instanceof WriteRequest || request instanceof ReadRequest || request instanceof ReadVRequest) {
level = DEBUG;
} else {
level = INFO;
}
if (level == ERROR || level == INFO && logger.isInfoEnabled() || level == DEBUG && logger.isDebugEnabled()) {
NetLoggerBuilder log = new NetLoggerBuilder(level, "org.dcache.xrootd.request").omitNullValues();
log.add("session", CDC.getSession());
log.add("request", getRequestId(request));
if (request instanceof PathRequest) {
log.add("path", (Strings.emptyToNull(((PathRequest) request).getPath())));
log.add("opaque", (Strings.emptyToNull(((PathRequest) request).getOpaque())));
if (request instanceof OpenRequest) {
if (!((OpenRequest) request).isReadOnly()) {
int mode = ((OpenRequest) request).getUMask();
if (mode == 0) {
log.add("mode", "0");
} else {
log.add("mode", "0" + Integer.toOctalString(mode));
}
}
log.add("options", "0x" + Integer.toHexString(((OpenRequest) request).getOptions()));
} else if (request instanceof LocateRequest) {
log.add("options", "0x" + Integer.toHexString(((LocateRequest) request).getOptions()));
} else if (request instanceof MkDirRequest) {
log.add("options", "0x" + Integer.toHexString(((MkDirRequest) request).getOptions()));
} else if (request instanceof StatRequest) {
if (((StatRequest) request).getTarget() == Target.FHANDLE) {
log.add("handle", ((StatRequest) request).getFhandle());
}
log.add("vfs", ((StatRequest) request).isVfsSet());
}
} else if (request instanceof CloseRequest) {
log.add("handle", ((CloseRequest) request).getFileHandle());
} else if (request instanceof LoginRequest) {
log.add("username", ((LoginRequest) request).getUserName());
log.add("capver", ((LoginRequest) request).getClientProtocolVersion());
log.add("pid", ((LoginRequest) request).getPID());
log.add("token", emptyToNull(((LoginRequest) request).getToken()));
} else if (request instanceof MvRequest) {
log.add("source", ((MvRequest) request).getSourcePath());
log.add("target", ((MvRequest) request).getTargetPath());
} else if (request instanceof PrepareRequest) {
log.add("options", "0x" + Integer.toHexString(((PrepareRequest) request).getOptions()));
if (((PrepareRequest) request).getPathList().length == 1) {
log.add("path", ((PrepareRequest) request).getPathList()[0]);
} else {
log.add("files", ((PrepareRequest) request).getPathList().length);
}
} else if (request instanceof QueryRequest) {
log.add("reqcode", getQueryReqCode(request));
int fhandle = ((QueryRequest) request).getFhandle();
if (fhandle != 0) {
log.add("fhandle", fhandle);
}
log.add("args", Strings.emptyToNull(((QueryRequest) request).getArgs()));
} else if (request instanceof StatxRequest) {
if (((StatxRequest) request).getPaths().length == 1) {
log.add("path", ((StatxRequest) request).getPaths()[0]);
} else {
log.add("files", ((StatxRequest) request).getPaths().length);
}
} else if (request instanceof SetRequest) {
final String APPID_PREFIX = "appid ";
final int APPID_PREFIX_LENGTH = APPID_PREFIX.length();
final int APPID_MSG_LENGTH = 80;
String data = ((SetRequest) request).getData();
if (data.startsWith(APPID_PREFIX)) {
log.add("appid", data.substring(APPID_PREFIX_LENGTH, Math.min(APPID_PREFIX_LENGTH + APPID_MSG_LENGTH, data.length())));
}
} else if (request instanceof EndSessionRequest) {
log.add("sessionId", ((EndSessionRequest) request).getSessionId());
} else if (request instanceof SyncRequest) {
log.add("handle", ((SyncRequest) request).getFileHandle());
}
log.add("response", getStatusCode(response));
if (response instanceof ErrorResponse) {
log.add("error.code", getErrorCode((ErrorResponse) response));
log.add("error.msg", ((ErrorResponse) response).getErrorMessage());
} else if (response instanceof RedirectResponse) {
log.add("host", ((RedirectResponse) response).getHost());
log.add("port", ((RedirectResponse) response).getPort());
log.add("token", emptyToNull(((RedirectResponse) response).getToken()));
} else if (response instanceof StatResponse) {
log.add("flags", ((StatResponse) response).getFlags());
log.add("modtime", Instant.ofEpochSecond(((StatResponse) response).getModificationTime()));
log.add("size", ((StatResponse) response).getSize());
} else if (response instanceof LoginResponse) {
log.add("sessionId", ((LoginResponse) response).getSessionId());
log.add("sec", emptyToNull(((LoginResponse) response).getSec()));
} else if (response instanceof OpenResponse) {
log.add("handle", ((OpenResponse) response).getFileHandle());
FileStatus fs = ((OpenResponse) response).getFileStatus();
if (fs != null) {
log.add("flags", fs.getFlags());
log.add("modtime", Instant.ofEpochSecond(fs.getModificationTime()));
log.add("size", fs.getSize());
}
}
log.toLogger(logger);
}
}
ctx.write(msg, promise);
}
Aggregations