use of org.dcache.xrootd.protocol.messages.OpenResponse in project dcache by dCache.
the class XrootdRedirectHandler method conditionallyHandleThirdPartyRequest.
/**
* <p>Special handling of third-party requests. Distinguishes among
* several different cases for the open and either returns a response directly to the caller or
* proceeds with the usual mover open and redirect to the pool by returning <code>null</code>.
* Also verifies the rendezvous information in the case of the destination server contacting
* dCache as source.</p>
*
* <p>With the modified TPC lite (delegation) protocol, there is no
* need to wait for the rendezvous destination check by comparing the open from the source.</p>
*
* <p>There is also the case where no delegated proxy exists but
* a different authentication protocol (like ZTN/scitokens) is being used. It seems that even
* with delegation in this case the initiating client does not call open. A check for authz in
* the opaque data has been added (03/21/2021).</p>
*/
private XrootdResponse<OpenRequest> conditionallyHandleThirdPartyRequest(OpenRequest req, LoginSessionInfo loginSessionInfo, Map<String, String> opaque, FsPath fsPath, String remoteHost) throws CacheException, XrootdException, ParseException {
if (!_door.isReadAllowed(fsPath)) {
throw new PermissionDeniedCacheException("Read permission denied");
}
Subject subject = loginSessionInfo.getSubject();
Restriction restriction = loginSessionInfo.getRestriction();
if ("placement".equals(opaque.get("tpc.stage"))) {
FileStatus status = _door.getFileStatus(fsPath, subject, restriction, remoteHost);
int fd = _door.nextTpcPlaceholder();
_log.debug("placement response to {} sent to {} with fhandle {}.", req, remoteHost, fd);
return new OpenResponse(req, fd, null, null, status);
}
String tpcKey = opaque.get("tpc.key");
if (tpcKey == null) {
_log.debug("{} –– not a third-party request.", req);
// proceed as usual with mover + redirect
return null;
}
if (opaque.containsKey(Cgi.AUTHZ.key())) {
_log.debug("{} –– request contains authorization token.", req);
// proceed as usual with mover + redirect
return null;
}
enforceClientTlsIfDestinationRequiresItForTpc(opaque);
/*
* Check the session for the delegated credential to avoid hanging
* in the case that tpc cgi have been passed by the destination
* server even with TPC with delegation.
*/
if (req.getSession().getDelegatedCredential() != null) {
_log.debug("{} –– third-party request with delegation.", req);
// proceed as usual with mover + redirect
return null;
}
String slfn = req.getPath();
XrootdTpcInfo info = _door.createOrGetRendezvousInfo(tpcKey);
/*
* The request originated from the TPC destination server.
* If the client has not yet opened the file here,
* tells the destination to wait. If the verification, including
* time to live, fails, the request is cancelled. Otherwise,
* the destination is allowed to open the mover and get the
* normal redirect response.
*
* Note that the tpc info is created by either the client or the
* server, whichever gets here first. Verification of the key
* itself is implicit (it has been found in the map); correctness is
* further satisfied by matching org, host and file name.
*/
if (opaque.containsKey("tpc.org")) {
info.addInfoFromOpaque(slfn, opaque);
switch(info.verify(remoteHost, slfn, opaque.get("tpc.org"))) {
case READY:
_log.debug("Open request {} from destination server, info {}: " + "OK to proceed.", req, info);
/*
* This means that the destination server open arrived
* second, the client server open succeeded with
* the correct permissions; proceed as usual
* with mover + redirect.
*/
return null;
case PENDING:
_log.debug("Open request {} from destination server, info {}: " + "PENDING client open.", req, info);
/*
* This means that the destination server open arrived
* first; return a wait-retry reply.
*/
return new AwaitAsyncResponse<>(req, 3);
case CANCELLED:
String error = info.isExpired() ? "ttl expired" : "dst, path or org" + " did not match";
_log.warn("Open request {} from destination server, info {}: " + "CANCELLED: {}.", req, info, error);
_door.removeTpcPlaceholder(info.getFd());
return withError(req, kXR_InvalidRequest, "tpc rendezvous for " + tpcKey + ": " + error);
case ERROR:
/*
* This means that the destination server requested open
* before the client did, and the client did not have
* read permissions on this file.
*/
error = "invalid open request (file permissions).";
_log.warn("Open request {} from destination server, info {}: " + "ERROR: {}.", req, info, error);
_door.removeTpcPlaceholder(info.getFd());
return withError(req, kXR_InvalidRequest, "tpc rendezvous for " + tpcKey + ": " + error);
}
}
/*
* The request originated from the TPC client, indicating door
* is the source.
*/
if (opaque.containsKey("tpc.dst")) {
_log.debug("Open request {} from client to door as source, " + "info {}: OK.", req, info);
FileStatus status = _door.getFileStatus(fsPath, subject, restriction, remoteHost);
int flags = status.getFlags();
if ((flags & kXR_readable) != kXR_readable) {
/*
* Update the info with ERROR, so when the destination checks
* it, an error can be returned.
*/
info.setStatus(Status.ERROR);
return withError(req, kXR_InvalidRequest, "not allowed to read file.");
}
info.addInfoFromOpaque(slfn, opaque);
return new OpenResponse(req, info.getFd(), null, null, status);
}
/*
* The request originated from the TPC client, indicating door
* is the destination.
*
* First check for TLS capability if this is required.
*
* Remove the rendezvous info (not needed),
* allow mover to start and redirect the client to the pool.
*
* It is not necessary to delegate the tpc information through the
* protocol, particularly the rendezvous key, because it is part of
* the opaque data, and if any of the opaque tpc info is missing
* from redirected call to the pool, the transfer will fail.
*
* However, the calling method will need to fetch a delegated
* proxy credential and add that to the protocol.
*/
if (opaque.containsKey("tpc.src")) {
_log.debug("Open request {} from client to door as destination: OK;" + "removing info {}.", req, info);
_door.removeTpcPlaceholder(info.getFd());
// proceed as usual with mover + redirect
return null;
}
/*
* Something went wrong.
*/
String error = String.format("Request metadata is invalid: %s: %s, %s.", req, fsPath, remoteHost);
throw new CacheException(CacheException.THIRD_PARTY_TRANSFER_FAILED, error);
}
use of org.dcache.xrootd.protocol.messages.OpenResponse in project dcache by dCache.
the class XrootdPoolRequestHandler method doOnOpen.
/**
* Obtains the right mover channel using an opaque token in the request. The mover channel is
* wrapped by a file descriptor. The file descriptor is stored for subsequent access.
* <p>
* In the case that this is a write request as destination in a third party copy, a third-party
* client is started. The client issues login, open and read requests to the source server, and
* writes the responses to the file descriptor.
* <p>
* The third-party client also sends a sync response back to the client when the transfer has
* completed.
*/
@Override
protected XrootdResponse<OpenRequest> doOnOpen(ChannelHandlerContext ctx, OpenRequest msg) throws XrootdException {
try {
Map<String, String> opaqueMap = getOpaqueMap(msg.getOpaque());
UUID uuid = getUuid(opaqueMap);
if (uuid == null) {
_log.info("Request to open {} contains no UUID.", msg.getPath());
throw new XrootdException(kXR_NotAuthorized, "Request lacks the " + UUID_PREFIX + " property.");
}
enforceClientTlsIfDestinationRequiresItForTpc(opaqueMap);
NettyTransferService<XrootdProtocolInfo>.NettyMoverChannel file = _server.openFile(uuid, false);
if (file == null) {
_log.info("No mover found for {} with UUID {}.", msg.getPath(), uuid);
return redirectToDoor(ctx, msg, () -> {
throw new XrootdException(kXR_NotAuthorized, UUID_PREFIX + " is no longer valid.");
});
}
/*
* Stop any timer in case this is a reconnect.
*/
_server.cancelReconnectTimeoutForMover(uuid);
_log.debug("doOnOpen, called cancel on reconnect timers for {}", uuid);
XrootdProtocolInfo protocolInfo = file.getProtocolInfo();
try {
FileDescriptor descriptor;
boolean isWrite = file.getIoMode().contains(StandardOpenOption.WRITE);
if (msg.isNew() && !isWrite) {
throw new XrootdException(kXR_FileNotOpen, "File exists.");
} else if (msg.isDelete() && !isWrite) {
throw new XrootdException(kXR_Unsupported, "File exists.");
/*
* Some clients express only kXR_delete when then intend to write
* so we need to consider delete as a write request here.
*/
} else if ((msg.isNew() || msg.isReadWrite() || msg.isDelete()) && isWrite) {
boolean posc = (msg.getOptions() & kXR_posc) == kXR_posc || protocolInfo.getFlags().contains(XrootdProtocolInfo.Flags.POSC);
if (opaqueMap.containsKey("tpc.src")) {
_log.debug("Request to open {} is as third-party destination.", msg);
XrootdTpcInfo tpcInfo = new XrootdTpcInfo(opaqueMap);
tpcInfo.setDelegatedProxy(protocolInfo.getDelegatedCredential());
tpcInfo.setUid(protocolInfo.getTpcUid());
tpcInfo.setGid(protocolInfo.getTpcGid());
descriptor = new TpcWriteDescriptor(file, posc, ctx, _server, opaqueMap.get("org.dcache.xrootd.client"), tpcInfo, tlsSessionInfo);
} else {
descriptor = new WriteDescriptor(file, posc);
}
} else {
descriptor = new ReadDescriptor(file);
}
FileStatus stat = msg.isRetStat() ? stat(file) : null;
int fd = addDescriptor(descriptor);
_redirectingDoor = protocolInfo.getDoorAddress();
file = null;
_hasOpenedFiles = true;
return new OpenResponse(msg, fd, null, null, stat);
} finally {
if (file != null) {
file.release();
}
}
} catch (ParseException e) {
throw new XrootdException(kXR_ArgInvalid, e.getMessage());
} catch (IOException e) {
throw new XrootdException(kXR_IOError, e.getMessage());
}
}
use of org.dcache.xrootd.protocol.messages.OpenResponse in project xrootd4j by dCache.
the class DataServerHandler method doOnOpen.
/**
* Obtains the right mover instance using an opaque token in the
* request and instruct the mover to open the file in the request.
* Associates the mover with the file-handle that is produced during
* processing
*/
@Override
protected OpenResponse doOnOpen(ChannelHandlerContext ctx, OpenRequest msg) throws XrootdException {
try {
File file = getFile(msg.getPath());
if (file.isDirectory()) {
throw new XrootdException(kXR_isDirectory, "Not a file: " + file);
}
File parent = file.getParentFile();
RandomAccessFile raf;
if (msg.isReadWrite()) {
if (msg.isMkPath() && !parent.exists() && !parent.mkdirs()) {
throw new XrootdException(kXR_IOError, "Failed to create directories: " + parent);
}
if (msg.isNew() && !file.createNewFile()) {
throw new XrootdException(kXR_IOError, "Failed to create file: " + file);
}
raf = new RandomAccessFile(file, "rw");
} else {
raf = new RandomAccessFile(file, "r");
}
try {
if (msg.isReadWrite() && msg.isDelete()) {
raf.setLength(0);
}
FileStatus stat = null;
if (msg.isRetStat()) {
stat = getFileStatusOf(file);
}
int fd = addOpenFile(raf);
raf = null;
return new OpenResponse(msg, fd, null, null, stat);
} finally {
if (raf != null) {
raf.close();
}
}
} catch (FileNotFoundException e) {
throw new XrootdException(kXR_NotFound, e.getMessage());
} catch (IOException e) {
throw new XrootdException(kXR_IOError, e.getMessage());
}
}
use of org.dcache.xrootd.protocol.messages.OpenResponse in project dcache-cta by dCache.
the class DataServerHandler method doOnOpen.
/**
* Obtains the right mover instance using an opaque token in the request and instruct the mover
* to open the file in the request. Associates the mover with the file-handle that is produced
* during processing
*/
@Override
protected OpenResponse doOnOpen(ChannelHandlerContext ctx, OpenRequest msg) throws XrootdException {
try {
var pr = getIORequest(msg.getPath());
var r = pr.getRequest();
var file = getFile(r);
LOGGER.info("Request {} scheduling time: {}", file, TimeUtils.describe(Duration.between(Instant.now(), pr.getSubmissionTime()).abs()).orElse("-"));
RandomAccessFile raf;
if (msg.isReadWrite() || msg.isNew() || msg.isDelete()) {
if (!(r instanceof StageRequest)) {
throw new XrootdException(kXR_ArgInvalid, "An attempt to open-for-read for stage requests");
}
LOGGER.info("Opening {} for writing", file);
raf = new RandomAccessFile(file, "rw");
if (msg.isDelete()) {
raf.setLength(0);
}
} else {
if (!(r instanceof FlushRequest)) {
throw new XrootdException(kXR_ArgInvalid, "An attempt to open-for-write for flush requests");
}
LOGGER.info("Opening {} for reading.", file);
raf = new RandomAccessFile(file, "r");
}
FileStatus stat = null;
if (msg.isRetStat()) {
stat = statusByFile(file);
}
var migrationRequest = new MigrationRequest(r, raf);
int fd = addOpenFile(migrationRequest);
return new OpenResponse(msg, fd, null, null, stat);
} catch (FileNotFoundException e) {
throw new XrootdException(kXR_NotFound, e.getMessage());
} catch (IOException e) {
throw new XrootdException(kXR_IOError, e.getMessage());
}
}
use of org.dcache.xrootd.protocol.messages.OpenResponse 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