org.galicaster.dashboard.DashboardService.java Source code

Java tutorial

Introduction

Here is the source code for org.galicaster.dashboard.DashboardService.java

Source

/**
 * Copyright (c) 2013, Teltek Video Research <galicaster@teltek.es>
 *
 * This work is licensed under the Creative Commons Attribution-
 * NonCommercial-ShareAlike 3.0 Unported License. To view a copy of 
 * this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ 
 * or send a letter to Creative Commons, 171 Second Street, Suite 300, 
 * San Francisco, California, 94105, USA.
 *
 */

package org.galicaster.dashboard;

import static org.opencastproject.security.api.SecurityConstants.GLOBAL_ADMIN_ROLE;

import org.opencastproject.capture.admin.api.Agent;
import org.opencastproject.capture.admin.api.CaptureAgentStateService;
import org.opencastproject.security.api.DefaultOrganization;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.JaxbUser;
import org.opencastproject.security.api.JaxbRole;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.workspace.api.Workspace;

import org.apache.commons.io.IOUtils;
import org.galicaster.dashboard.snapshot.GstreamerSnapshotTaker;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Monitor the Galicaster capture agents attached to a Matterhorn core
 */
public class DashboardService {

    private static final Logger logger = LoggerFactory.getLogger(DashboardService.class);
    //private static final Set<String> goodStates = new HashSet<String>(asList(new String[]{AgentState.IDLE, AgentState.CAPTURING, AgentState.UPLOADING}));

    /** Definition of the different functioning modes for the dashboard */
    public enum Mode {
        /** The agents push their preview images by themselves */
        PUSH,
        /** The dashboard periodically polls the agents for their preview images, via VNC */
        PULL;
    }

    /**
     *  PROPERTY NAMES
     */

    /** Name of the property that indicates the ping timeout when checking if a certain agent is active (in milliseconds) */
    private static final String PING_TIMEOUT_PROPERTY = "ping.timeout";

    /** Name of the property that indicates the timeout used when taking a screenshot from an agent in pull mode (in seconds) */
    private static final String SNAPSHOT_TIMEOUT_PROPERTY = "snapshot.timeout";

    /** Name of the property that indicates the period between two subsequent updates of an agent's screenshot */
    private static final String SNAPSHOT_PERIOD_PROPERTY = "snapshot.period";

    /** Name of the property that indicates the maximum number of snapshot-extracting threads allowed to run at the same time */
    private static final String MAX_CONCURRENT_PROPERTY = "max.concurrent";

    /** Name of the property that indicates the global default VNC password for the agents */
    private static final String DEFAULT_PASSWORD_PROPERTY = "default.vnc.password";

    /** Name of the property that indicates the functioning mode of the dashboard (push or pull) */
    private static final String MODE_PROPERTY = "mode";

    /**
     *  PROPERTY DEFAULT VALUES & RELATED DEFINITIONS
     */

    /** Default functioning mode */
    private static final Mode MODE_DEFAULT = Mode.PUSH;

    /** Maximum time (in milliseconds) that the service will wait for an agent to respond to ping */
    private static final int PING_TIMEOUT_DEFAULT = 5000; //ms

    /** Maximum time (in seconds) that the service will wait for the pipeline to take a snapshot */
    private static final int SNAPSHOT_TIMEOUT_DEFAULT = 10; //seconds

    /** Time between two subsequent updates of an agent's snapshot */
    private static final long SNAPSHOT_PERIOD_DEFAULT = 60; //seconds

    /** Maximum number of snapshot-extracting threads allowed to run that the same time */
    private static final int MAX_CONCURRENT_DEFAULT = 5;

    /** Default to the default VNC password for the agents without a specific one specified in the config file */
    private static final String DEFAULT_PASSWORD_DEFAULT = "";

    /**
     *  OTHER DEFINITIONS
     */

    /** This regular expression matches property names of the form "agent.[name].vnc.password", where
     * [name] is any agent name. The [name] contents are captured in group 1.
     */
    private static final String PASSWORD_REGEXP = "agent\\.(.+?)\\.vnc\\.password";

    /** Collection name in the workspace where the agent pictures are stored */
    private static final String COLLECTION_NAME = "dashboard-files";

    /** Directory to temporarily store the extracted images */
    private static final File tempDir = new File(System.getProperty("java.io.tmpdir"));

    /** Contains the VNC passwords specified in the config file */
    private Properties agentPasswords;

    private ScheduledExecutorService periodicSnapshots;

    /** The default password, as set up in the configuration file, if present */
    private String defaultPassword;

    /** The system account to use for running asynchronous events */
    protected String systemAccount = null;

    /** The capture agent state service */
    protected CaptureAgentStateService captureAgentStateService;

    /** A reference to the workspace service */
    private Workspace workspace;

    /** A reference to the security service */
    private SecurityService securityService;

    /** The working mode of this dashboard */
    private Mode mode = null;

    /** The number of milliseconds to wait for a ping response before failing */
    private int pingTimeout;

    /** The number of seconds to wait when taking a screenshot before failing */
    private int snapshotTimeout;

    /** The number of seconds between two consecutive screenshots from an agent */
    private long snapshotPeriod;

    /** The max number of concurrent screenshot-taking threads */
    private int maxConcurrentSnapshots;

    @SuppressWarnings("unchecked")
    protected void activate(ComponentContext cc) throws URISyntaxException {

        // Initialize the object to hold the agent's properties
        agentPasswords = new Properties();

        if ((cc == null) || (cc.getProperties() == null)) {

            mode = MODE_DEFAULT;
            logger.debug("Setting default functioning mode: {}", mode.toString());

            pingTimeout = PING_TIMEOUT_DEFAULT;
            logger.debug("Setting default ping timeout: {}", pingTimeout);

            snapshotTimeout = SNAPSHOT_TIMEOUT_DEFAULT;
            logger.debug("Setting default snapshot timeout: {}", snapshotTimeout);

            snapshotPeriod = SNAPSHOT_PERIOD_DEFAULT;
            logger.debug("Setting default snapshot period: {}", snapshotPeriod);

            maxConcurrentSnapshots = MAX_CONCURRENT_DEFAULT;
            logger.debug("Setting default maximum concurrent snapshots: {}", maxConcurrentSnapshots);

            defaultPassword = DEFAULT_PASSWORD_DEFAULT;
            logger.debug("Setting default VNC password default: {}", DEFAULT_PASSWORD_DEFAULT);

        } else {

            // Get the component properties
            Dictionary<String, Object> properties = cc.getProperties();

            // Get the functioning mode.
            String strMode = (String) properties.get(MODE_PROPERTY);
            if (strMode != null) {
                try {
                    mode = Mode.valueOf(strMode.toUpperCase());
                } catch (IllegalArgumentException iae) {
                    logger.warn("Invalid functioning mode found in configuration file: {}", strMode);
                    mode = MODE_DEFAULT;
                    logger.debug("Setting default functioning mode: {}", mode.toString());
                }
            }

            // Read the properties that control the "snapshot taking" process
            try {
                pingTimeout = Integer.parseInt((String) properties.get(PING_TIMEOUT_PROPERTY));
            } catch (Exception e) {
                pingTimeout = PING_TIMEOUT_DEFAULT;
            }

            try {
                snapshotTimeout = Integer.parseInt((String) properties.get(SNAPSHOT_TIMEOUT_PROPERTY));
            } catch (Exception e) {
                pingTimeout = SNAPSHOT_TIMEOUT_DEFAULT;
            }

            try {
                snapshotPeriod = Long.parseLong((String) properties.get(SNAPSHOT_PERIOD_PROPERTY));
            } catch (Exception e) {
                snapshotPeriod = SNAPSHOT_PERIOD_DEFAULT;
            }

            try {
                maxConcurrentSnapshots = Integer.parseInt((String) properties.get(MAX_CONCURRENT_PROPERTY));
            } catch (Exception e) {
                maxConcurrentSnapshots = MAX_CONCURRENT_DEFAULT;
            }

            // Get the default VNC password, if it is defined in the config file
            defaultPassword = (String) properties.get(DEFAULT_PASSWORD_PROPERTY);
            if (defaultPassword == null)
                defaultPassword = DEFAULT_PASSWORD_DEFAULT;

            // Get the passwords per agent, if specified in the config file
            Pattern propertyPattern = Pattern.compile(PASSWORD_REGEXP);

            for (Enumeration<String> keys = properties.keys(); keys.hasMoreElements();) {
                String key = keys.nextElement();
                Matcher match = propertyPattern.matcher(key);

                if (match.matches()) {
                    agentPasswords.setProperty(match.group(1), (String) properties.get(match.group()));
                }
            }
        }

        // If the mode is PULL, set the security information and start the snapshot threads accordingly
        if (mode == Mode.PULL) {

            // Set security information
            this.systemAccount = cc.getBundleContext().getProperty("org.opencastproject.security.digest.user");

            DefaultOrganization defaultOrg = new DefaultOrganization();
            securityService.setOrganization(defaultOrg);
            securityService
                    .setUser(new JaxbUser(systemAccount, defaultOrg, new JaxbRole(GLOBAL_ADMIN_ROLE, defaultOrg)));

            // Start the threads that take the snapshots
            periodicSnapshots = Executors.newScheduledThreadPool(maxConcurrentSnapshots);

            Map<String, Agent> knownAgents = captureAgentStateService.getKnownAgents();
            Random randGen = new Random();
            for (Map.Entry<String, Agent> entry : knownAgents.entrySet()) {
                SnapshotWithDeadline task = new SnapshotWithDeadline(entry.getValue(), tempDir,
                        agentPasswords.getProperty(entry.getKey(), defaultPassword), snapshotTimeout,
                        TimeUnit.SECONDS);

                periodicSnapshots.scheduleAtFixedRate(task, randGen.nextLong() % snapshotPeriod, snapshotPeriod,
                        TimeUnit.SECONDS);
            }
        }

        logger.info("Galicaster Service activated");
    }

    protected void deactivate(ComponentContext cc) {
        periodicSnapshots.shutdownNow();
        agentPasswords = null;
        logger.info("Galicaster Service deactivated");
    }

    /**
     * Sets the capture agent state service
     * 
     * @param captureAgentStateService
     *          the captureAgentStateService to set
     */
    protected void setCaptureAgentStateService(CaptureAgentStateService captureAgentStateService) {
        this.captureAgentStateService = captureAgentStateService;
    }

    /**
     * Sets the workspace
     *
     * @param workspace
     *         an instance of the workspace
     */
    protected void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }

    /**
     * @param securityService
     *          the securityService to set
     */
    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }

    /**
     * Check the specified agent is active and reachable
     * @param agentName the name of the agent to check
     * @throws IOException if a network error occurs
     * @throws NotFoundException if the agent is not registered
     */
    public boolean isActive(String agentName) throws NotFoundException {
        Agent agent = captureAgentStateService.getAgent(agentName);

        InetAddress addr;
        try {
            addr = InetAddress.getByName(agent.getUrl());
            boolean isReachable = addr.isReachable(pingTimeout);
            boolean isInGoodState = true;
            //boolean isInGoodState = goodStates.contains(agent.getState());
            logger.error("Agent {} is {}reachable{}.", new String[] { agent.getName(), isReachable ? "" : "not ", ""
                    //isReachable ? " and reporting state " + agent.getState() : ""
            });

            return isReachable && isInGoodState;
        } catch (UnknownHostException e) {
            // Host does not exist
        } catch (IOException e) {
            // A network error occurred, so the agent is unreachable too
        }

        return false;
    }

    public File getSnapshot(String agentName) throws NotFoundException {
        Agent agent = captureAgentStateService.getAgent(agentName);

        if (agent != null) {
            String fileName = agentName + GstreamerSnapshotTaker.IMAGE_EXTENSION;

            try {
                return workspace.get(workspace.getCollectionURI(COLLECTION_NAME, fileName));
            } catch (IllegalArgumentException e) {
                logger.warn("Couldn't create a correct URI with file name '{}' and collection '{}'.", fileName,
                        COLLECTION_NAME);
                throw new NotFoundException("Couldn't create a correct URI with collection name '" + COLLECTION_NAME
                        + "' and file name '" + fileName + "'.", e);
            } catch (IOException e) {
                logger.warn("Error reading file '{}' from collection '{}'", fileName, COLLECTION_NAME);
                throw new NotFoundException(
                        "Error reading file '" + fileName + "' from collection '" + COLLECTION_NAME + "'.", e);
            }
        } else
            throw new NotFoundException("Agent " + agentName + " is not registered in the system");
    }

    public void setSnapshot(String agentName, InputStream fileStream) throws NotFoundException, IOException {
        Agent agent = captureAgentStateService.getAgent(agentName);

        if (agent != null) {
            String fileName = agentName + GstreamerSnapshotTaker.IMAGE_EXTENSION;

            try {
                workspace.putInCollection(COLLECTION_NAME, fileName, fileStream);
            } catch (IllegalArgumentException e) {
                logger.warn("Couldn't create a correct URI with file name '{}' and collection '{}'.", fileName,
                        COLLECTION_NAME);
                throw new NotFoundException("Couldn't create a correct URI with collection name '" + COLLECTION_NAME
                        + "' and file name '" + fileName + "'.", e);
            } catch (IOException ioe) {
                throw new IOException(
                        "Error reading file '" + fileName + "' from collection '" + COLLECTION_NAME + "'", ioe);
            }
        } else
            throw new NotFoundException("Agent " + agentName + " is not registered in the system");
    }

    private final class SnapshotWithDeadline implements Runnable {

        /** Service to run the snapshot thread in a controlled environment, so that it can be interrupted if it takes too long */
        private ExecutorService executor = Executors.newSingleThreadExecutor();
        Agent agent;
        String vncPassword;
        File destDir;
        long deadLine;
        TimeUnit timeUnits;

        public SnapshotWithDeadline(final Agent agent, final File destDir, final String vncPassword,
                final long deadLine, final TimeUnit timeUnits) {
            this.agent = agent;
            this.destDir = destDir;
            this.vncPassword = vncPassword;
            this.deadLine = deadLine;
            this.timeUnits = timeUnits;
        }

        @Override
        public void run() {
            if (agent != null) {

                String fileName = agent.getName() + GstreamerSnapshotTaker.IMAGE_EXTENSION;

                GstreamerSnapshotTaker cst = null;
                boolean deleteFileFromWorkspace = true;

                try {

                    try {
                        cst = new GstreamerSnapshotTaker(agent, destDir, vncPassword);
                    } catch (IOException ioe) {
                        throw new RuntimeException("Failed to initialize the snapshot taker", ioe);
                    }

                    File newFile = executor.submit(cst).get(deadLine, timeUnits);

                    FileInputStream fis = null;
                    try {
                        if (newFile.isFile()) {
                            fis = new FileInputStream(newFile);
                            workspace.putInCollection(COLLECTION_NAME, fileName, fis);
                            deleteFileFromWorkspace = false;
                        } else {
                            logger.error("'{}' is not a proper file.", newFile.getAbsolutePath());
                            throw new RuntimeException(newFile.getAbsolutePath() + " is not a proper file.");
                        }
                    } finally {
                        IOUtils.closeQuietly(fis);
                        if (!newFile.delete())
                            logger.warn("Couldn't delete temporary file: {}", newFile);
                    }
                } catch (InterruptedException ie) {
                    throw new RuntimeException("Unexpected interruption while waiting for job to finish", ie);
                } catch (ExecutionException ee) {
                    throw new RuntimeException(
                            "Retrieving desktop snapshot from capture agent \"" + agent.getName() + "\" failed");
                } catch (TimeoutException toe) {
                    throw new RuntimeException("Retrieving desktop snapshot from capture agent \"" + agent.getName()
                            + "\" took too long");
                } catch (IOException ioe) {
                    throw new RuntimeException("Failed to write new file to workspace", ioe);
                } catch (IllegalArgumentException iae) {
                    throw new RuntimeException("Failed to create a URI for the workspace file", iae);
                } finally {
                    cst.interrupt();
                    if (deleteFileFromWorkspace) {
                        try {
                            workspace.deleteFromCollection(COLLECTION_NAME, fileName);
                        } catch (NotFoundException e) {
                            logger.warn("Couldn't delete snapshot: '{}' does not exist in the workspace.",
                                    fileName);
                        } catch (IOException e) {
                            logger.warn("Got IOException when deleting '{}' from workspace: {}", fileName,
                                    e.getMessage());
                        }
                    }
                }
            }
        }
    }

}