package org.dcache.missingfiles.plugins;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.security.auth.Subject;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.dcache.auth.FQAN;
import org.dcache.auth.Subjects;
import org.dcache.util.ConfigurationProperties;
import org.dcache.util.FireAndForgetTask;
import static com.google.common.collect.Lists.newArrayList;
/**
* Missing-files plugin for sending notification that access was attempted to
* a file. We use an external program, SEMsg_SendNotAvailable to send the actual
* messages. This plugin collects the messages and sends them in batches.
*/
public class SEMsgPlugin implements Plugin
{
private static final Logger _log =
LoggerFactory.getLogger(SEMsgPlugin.class);
private Executor _executor = Executors.newSingleThreadExecutor();
private static final String PROPERTY_TOPIC = "missing-files.plugins.semsg.broker.topic";
private static final String PROPERTY_ENDPOINT = "missing-files.plugins.semsg.broker.endpoint";
private static final String PROPERTY_COMMAND = "missing-files.plugins.semsg.command";
private static final String PROPERTY_CERTIFICATE = "missing-files.plugins.semsg.certificate";
private static final String PROPERTY_PRIVATE_KEY = "missing-files.plugins.semsg.private-key";
private static final Future<Result> DEFER = new FinalResult(Result.DEFER);
private String _topic;
private String _endpoint;
private String _command;
private String _certificatePath;
private String _privateKeyPath;
public SEMsgPlugin(ConfigurationProperties properties)
{
_topic = getRequiredProperty(properties, PROPERTY_TOPIC);
_endpoint = getRequiredProperty(properties, PROPERTY_ENDPOINT);
_command = getRequiredProperty(properties, PROPERTY_COMMAND);
_certificatePath = getRequiredProperty(properties, PROPERTY_CERTIFICATE);
_privateKeyPath = getRequiredProperty(properties, PROPERTY_PRIVATE_KEY);
}
private static String getRequiredProperty(ConfigurationProperties properties, String key)
{
String value = properties.getValue(key);
if(value == null) {
throw new IllegalArgumentException("missing property: " + key);
}
return value;
}
@Override
public Future<Result> accept(final Subject subject,
final String requestPath, final String internalPath)
{
_executor.execute(new FireAndForgetTask(() -> sendMessage(subject, requestPath)));
return DEFER;
}
private void sendMessage(Subject subject, String path)
{
String authDn = buildAuthDnFor(subject);
try {
int rc = runCommand(authDn, path);
if(rc != 0) {
_log.error("call to command failed: rc={}", rc);
}
} catch (InterruptedException e) {
_log.debug("Interrupted while sending notification");
} catch (IOException e) {
_log.error("{}", e.getMessage());
}
}
private String buildAuthDnFor(Subject subject)
{
StringBuilder sb = new StringBuilder();
String dn = Subjects.getDn(subject);
if(dn != null) {
sb.append(dn);
for(FQAN fqan : Subjects.getFqans(subject)) {
sb.append(',').append(fqan);
}
} else {
sb.append("<UNKNOWN>");
}
return sb.toString();
}
private int runCommand(String authDn, String path) throws IOException, InterruptedException
{
List<String> args = newArrayList(_command, _endpoint, authDn,
"-t", _topic, path);
ProcessBuilder builder = new ProcessBuilder(args);
_log.debug("Command: {}", builder.command());
Map<String,String> envar = builder.environment();
envar.put("X509_USER_CERT", _certificatePath);
envar.put("X509_USER_KEY", _privateKeyPath);
Process process = builder.start();
return process.waitFor();
}
}