org.hexlogic.CooptoPluginFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.hexlogic.CooptoPluginFactory.java

Source

/*   This file is part of project "Coopto", a computer software plugin for       *
 *  utilizing Docker in VMware vRealize Orchestrator.                     *
 *                                                            *
 *   Copyright (C) 2014-2015  Robert Szymczak   (rszymczak@fum.de)            *
 *                                                            *
 *                                                            *
 *   This program is free software: you can redistribute it and/or modify      *
 *   it under the terms of the GNU Lesser General Public License as published    *
 *   by the Free Software Foundation, either version 3 of the License, or      *
 *   (at your option) any later version.                                 *
 *                                                            *
 *   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 Lesser General Public License for more details.               *
 *                                                            *
 *   You should have received a copy of the GNU Lesser General Public License   *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.      */
package org.hexlogic;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.hexlogic.model.DockerContainer;
import org.hexlogic.model.DockerImage;
import org.hexlogic.model.DockerNode;
import org.hexlogic.model.DockerNodeService;
import org.springframework.beans.factory.annotation.Autowired;

import ch.dunes.vso.sdk.api.QueryResult;
import ch.dunes.vso.sdk.endpoints.IEndpointConfiguration;

import com.vmware.o11n.plugin.sdk.spring.AbstractSpringPluginFactory;
import com.vmware.o11n.plugin.sdk.spring.InventoryRef;

public final class CooptoPluginFactory extends AbstractSpringPluginFactory {
    private static final Logger log = LogManager.getLogger(CooptoPluginFactory.class);
    private static List<DockerNode> nodes = Collections.synchronizedList(new ArrayList<DockerNode>());

    public CooptoPluginFactory() {
        log.setLevel(Level.DEBUG);
    }

    @Autowired
    private DockerNodeService service;

    // find a specific item, defined by InventoryRef (vco-type, id)
    @Override
    public Object find(InventoryRef ref) {
        log.debug("running find() with id=" + ref.getId() + " and type=" + ref.getType() + ".");
        if (ref.isOfType(DockerNode.TYPE)) {
            try {
                for (int i = 0; i < 2; i++) {
                    // try to find in cache - must be in synchronized block
                    synchronized (nodes) {
                        Iterator<DockerNode> itr = nodes.iterator();
                        while (itr.hasNext()) {
                            DockerNode node = itr.next();
                            log.debug("Checking cache. Current node: " + node.getId() + ".");
                            if (node.getId().equals(ref.getId())) {
                                log.debug("Found node '" + ref.getId() + "' in cache.");
                                return node;
                            }
                        }
                    }

                    // if not found in cache, refresh and try again one more time
                    if (i < 1) {
                        log.warn("Unable to find node '" + ref.getId() + "' in cache.");
                        this.rebuildCache();
                    } else {
                        log.warn("Unable to find node '" + ref.getId() + "' after reloading cache.");
                    }
                }
            } catch (Exception e) {
                final StringWriter sw = new StringWriter();
                final PrintWriter pw = new PrintWriter(sw, true);
                e.printStackTrace(pw);
                log.error("Error: " + sw.getBuffer().toString());
            }
        } else if (ref.isOfType(DockerImage.TYPE)) {
            try {
                // try to find in cache - must be in synchronized block
                synchronized (nodes) {
                    Iterator<DockerNode> itr = nodes.iterator();
                    while (itr.hasNext()) {
                        DockerNode node = itr.next();
                        DockerImage image = node.getImage(ref.getId());
                        if (image != null) {
                            log.debug("Found " + ref.getType() + " '" + ref.getId() + "' in cache.");
                            return image;
                        }
                    }
                }
                // getImage will also run reloadImages on it's node if not found, thus we don't have to handle that here. If it's not returned by getImage, it dosn't exist.
                log.warn("Unable to find " + ref.getType() + " '" + ref.getId() + "'.");
                //TODO dunno what should be returned if the item was not found. currently we end up with a [EMPTY_NODE] item in the inventory, even after we already called notifyDeleted in order to inform the inventory about the deleted object
            } catch (Exception e) {
                final StringWriter sw = new StringWriter();
                final PrintWriter pw = new PrintWriter(sw, true);
                e.printStackTrace(pw);
                log.error("Error: " + sw.getBuffer().toString());
            }
        } else if (ref.isOfType(DockerContainer.TYPE)) {
            try {
                // try to find in cache - must be in synchronized block
                synchronized (nodes) {
                    Iterator<DockerNode> itr = nodes.iterator();
                    while (itr.hasNext()) {
                        DockerNode node = itr.next();
                        DockerContainer container = node.getContainer(ref.getId());
                        if (container != null) {
                            log.debug("Found " + ref.getType() + " '" + ref.getId() + "' in cache.");
                            return container;
                        }
                    }
                }
                // getContainer will also run reloadContainers on it's node if not found, thus we don't have to handle that here. If it's not returned by getContainer, it dosn't exist.
                log.warn("Unable to find " + ref.getType() + " '" + ref.getId() + "'.");
                //TODO dunno what should be returned if the item was not found. currently we end up with a [EMPTY_NODE] item in the inventory, even after we already called notifyDeleted in order to inform the inventory about the deleted object
            } catch (Exception e) {
                final StringWriter sw = new StringWriter();
                final PrintWriter pw = new PrintWriter(sw, true);
                e.printStackTrace(pw);
                log.error("Error: " + sw.getBuffer().toString());
            }
        }

        return null;
    }

    // find all items of a specific vco-type (optional, provide a query (guess OGNL?))
    @Override
    public QueryResult findAll(String type, String query) {
        log.debug("running findAll() with type=" + type + " and query=" + query + ".");
        QueryResult qr = new QueryResult();

        if (type.equals(DockerNode.TYPE)) {
            try {
                // try to load from EndpointConfiguration if empty
                if (nodes.isEmpty()) {
                    log.warn("Unable to find " + type + " in cache.");
                    this.rebuildCache();
                } else {
                    log.debug("Found " + type + " in cache.");
                }

                // try to find in cache - must be in synchronized block
                synchronized (nodes) {
                    Iterator<DockerNode> itr = nodes.iterator();
                    while (itr.hasNext()) {
                        DockerNode node = itr.next();
                        if (node != null) {
                            qr.addElement(node);
                        }
                    }
                }
            } catch (Exception e) {
                final StringWriter sw = new StringWriter();
                final PrintWriter pw = new PrintWriter(sw, true);
                e.printStackTrace(pw);
                log.error("Error: " + sw.getBuffer().toString());
            }
        } else if (type.equals(DockerImage.TYPE)) {
            try {
                // try to find in cache - must be in synchronized block
                synchronized (nodes) {
                    Iterator<DockerNode> itr = nodes.iterator();
                    while (itr.hasNext()) {
                        DockerNode node = itr.next();
                        if (node != null) {
                            List<DockerImage> children = node.getImages();
                            qr.addElements(children);
                        }
                    }
                }
                log.debug("Found children: " + qr);
            } catch (Exception e) {
                final StringWriter sw = new StringWriter();
                final PrintWriter pw = new PrintWriter(sw, true);
                e.printStackTrace(pw);
                log.error("Error: " + sw.getBuffer().toString());
            }
        } else if (type.equals(DockerContainer.TYPE)) {
            try {
                // try to find in cache - must be in synchronized block
                synchronized (nodes) {
                    Iterator<DockerNode> itr = nodes.iterator();
                    while (itr.hasNext()) {
                        DockerNode node = itr.next();
                        if (node != null) {
                            List<DockerContainer> container = node.getContainers();
                            qr.addElements(container);
                        }
                    }
                }
                log.debug("Found children: " + qr);
            } catch (Exception e) {
                final StringWriter sw = new StringWriter();
                final PrintWriter pw = new PrintWriter(sw, true);
                e.printStackTrace(pw);
                log.error("Error: " + sw.getBuffer().toString());
            }
        }
        return qr;
    }

    // Return all children of all objects of a specific type that are related by the given relationName
    @Override
    public List<?> findChildrenInRootRelation(String type, String relationName) {
        log.debug("running findChildrenInRootRelation() with type=" + type + " and relationName=" + relationName
                + ".");

        if (type.equals(CooptoModuleBuilder.ROOT)) {
            if (relationName.equals(CooptoModuleBuilder.NODERELATION)) {
                return findAll(DockerNode.TYPE, null).getElements();
            } else {
                throw new IndexOutOfBoundsException("Unknown relation name: " + relationName);
            }
        } else {
            return Collections.emptyList();
        }
    }

    // Return all children of a specific object - defined by it's InventoryRef - that are related by the given relationName
    @Override
    public List<?> findChildrenInRelation(InventoryRef parent, String relationName) {
        log.debug("running findChildrenInRelation() with parent=" + parent + " and relationName=" + relationName
                + ".");

        if (parent.isOfType(CooptoModuleBuilder.ROOT)) {
            log.debug("parent is of type " + CooptoModuleBuilder.ROOT);
            if (relationName.equals(CooptoModuleBuilder.NODERELATION)) {
                log.debug("relation is " + CooptoModuleBuilder.NODERELATION);
                return findAll(DockerNode.TYPE, null).getElements();
            } else {
                throw new IndexOutOfBoundsException("Unknown relation name: " + relationName);
            }
        } else if (parent.isOfType(DockerNode.TYPE)) {
            log.debug("parent is of type " + DockerNode.TYPE + ". Searching...");

            try {
                // try to find in cache - must be in synchronized block
                synchronized (nodes) {
                    Iterator<DockerNode> itr = nodes.iterator();
                    while (itr.hasNext()) {
                        DockerNode node = itr.next();
                        if (node.getId().equals(parent.getId())) {
                            log.debug("Found parent " + DockerNode.TYPE + " with id " + parent.getId());
                            if (relationName.equals(DockerNode.IMAGERELATION)) {
                                log.debug("Relation is " + DockerNode.IMAGERELATION);
                                if (node != null) {
                                    return node.getImages();
                                }
                            } else if (relationName.equals(DockerNode.CONTAINERRELATION)) {
                                log.debug("Relation is " + DockerNode.CONTAINERRELATION);
                                if (node != null) {
                                    return node.getContainers();
                                }
                            } else {
                                throw new IndexOutOfBoundsException("Unknown relation name: " + relationName);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                final StringWriter sw = new StringWriter();
                final PrintWriter pw = new PrintWriter(sw, true);
                e.printStackTrace(pw);
                log.error("Error: " + sw.getBuffer().toString());
            }
        }
        log.warn("Warning: no such parent object or relation was found");
        return Collections.emptyList();
    }

    /* We cannot instantiate our parent class or we'll loose @autowired functionality which we need for multiple services to work
     * Because only the finder really needs the java object, we build it here using createScriptingObject and only save the properties
     * of the object.
     */
    private DockerNode makeDockerNode(IEndpointConfiguration config) {

        String id = config.getId();
        String displayName = config.getString(DockerNode.DISPLAYNAME);
        String hostName = config.getString(DockerNode.HOSTNAME);
        int portNumber = config.getAsInteger(DockerNode.HOSTPORT).intValue();
        String apiVersion = config.getString(DockerNode.APIVERSION);

        // The same docker host may be added multiple times to the inventory - we have no way to prevent that - but the will be a different one
        log.debug("Running makeDockerNode() for node with vco-id:" + id);

        if (id != null && !id.isEmpty()) {
            if (displayName != null && !displayName.isEmpty()) {
                if (hostName != null && !hostName.isEmpty()) {
                    log.debug("Starting node scripting object creation...");
                    // No need to test port and API version since they will fallback to default if not set
                    DockerNode object = createScriptingObject(DockerNode.class);
                    object.setId(id);
                    object.setDisplayName(displayName);
                    object.setHostName(hostName);
                    object.setHostPortNumber(portNumber);
                    object.setDockerApiVersion(apiVersion);
                    log.debug("Finished node scripting object creation.");

                    log.debug("Running init methods of the new node scripting object...");
                    if (object.isOnline()) {
                        object.initImages(object.getShowRelatedImages());
                        object.initContainers(object.getShowStoppedContainers());
                        // get additional realtime info
                        object.reloadNode();
                    } else {
                        log.warn("Warning: node '" + displayName + "' is offline.");
                        object.setStatus(DockerNode.STATUS_OFFLINE);
                        object.clearImages();
                        object.clearContainer();
                    }

                    log.debug("Finished running init methods of the new node scripting object.");
                    return object;
                } else {
                    log.error("Unable to makeDockerHost. Reason: no hostName was provided.");
                }
            } else {
                log.error("Unable to makeDockerHost. Reason: no displayName was provided.");
            }
        } else {
            log.error("Unable to makeDockerHost. Reason: no id was provided.");
        }

        return null;
    }

    // invalidateAll may be called from within the plugin or from the vCO client when clicking "refresh" in the inventory
    // No other factory methods are called after invalidateAll was called
    @Override
    public void invalidateAll() {
        log.debug("Running INVALIDATEALL..");
        this.rebuildCache();
        super.invalidateAll();
        log.debug("Finished running INVALIDATEALL.");
    }

    // invalidate may be called from within the plugin or from the vCO client when right-clicking an inventory object and selecting "refresh"
    // the following factory methods are called when invalidate was called:
    // find() with the given type and id
    // findChildrenInRelation() with the given type and id as parent (will be called once for every relation that is found on the parent object)
    @Override
    public void invalidate(String type, String id) {
        log.debug("Running INVALIDATE for type '" + type + "' and id '" + id + "'...");
        if (type.equals(DockerNode.TYPE)) {
            // reload the node configuration from EndpointConfiguration
            log.debug("Found node with id '" + id + "'.");
            rebuildCache(id);
        } else if (type.equals(DockerImage.TYPE)) {
            // try to find in cache - must be in synchronized block
            synchronized (nodes) {
                Iterator<DockerNode> itr = nodes.iterator();
                while (itr.hasNext()) {
                    DockerNode node = itr.next();
                    if (node.getImage(id) != null) {
                        log.debug("Found image on node with id '" + node.getId() + "'.");
                        node.reloadImages();
                    }
                }
            }
        } else if (type.equals(DockerContainer.TYPE)) {
            // try to find in cache - must be in synchronized block
            synchronized (nodes) {
                Iterator<DockerNode> itr = nodes.iterator();
                while (itr.hasNext()) {
                    DockerNode node = itr.next();
                    if (node.getContainer(id) != null) {
                        log.debug("Found container on node with id '" + node.getId() + "'.");
                        node.reloadContainers();
                    }
                }
            }
        }
        super.invalidate(type, id);
        log.debug("Finished running INVALIDATE for type '" + type + "' and id '" + id + "'.");
    }

    private void rebuildCache() {
        log.debug("Rebuilding full cache...");
        log.debug("Current cachesize: " + nodes.size() + ".");
        /* Do not just simply clear the cache because e.g. find() requests executed while the new cache is being build 
         * would result in cache misses and trigger a cache rebuild, causing multiple inventory-objects to be created.
         * 
         * Instead, build the new cache into a newNode array, and replace it in a synchronized way
         */
        ArrayList<DockerNode> newNodes = new ArrayList<DockerNode>();

        // Load node data from EndpointConfiguration
        for (IEndpointConfiguration config : service.getNodes()) {
            if (config != null) {
                // create scripting objects, reload child objects & add to cache
                newNodes.add(this.makeDockerNode(config));
            }
        }

        // replace old list - must be in synchronized block
        synchronized (nodes) {
            nodes = Collections.synchronizedList(newNodes);
        }
        log.debug("New cachesize: " + nodes.size() + ".");
        log.debug("Finished full cache rebuild.");
    }

    private void rebuildCache(String nodeId) {
        log.debug("Rebuilding cache for node '" + nodeId + "'.");
        log.debug("Current cachesize: " + nodes.size() + ".");
        // Remove the node which should be refreshed if present in the cache - must be in synchronized block
        synchronized (nodes) {
            // Do not use for-loops for this, since it will cause ConcurrentModificationExceptions to be thrown!
            Iterator<DockerNode> itr = nodes.iterator();
            while (itr.hasNext()) {
                DockerNode node = itr.next();
                if (node.getId().equals(nodeId)) {
                    // remove this node from nodes list without causing ConcurrentModificationExceptions by using iterator.remove()
                    itr.remove();

                    log.debug("Cleared cache for node ' " + nodeId + "'.");
                }
            }
        }

        // Reload the node from the EndpointConfiguration and add it to our synchronized list
        DockerNode node = this.makeDockerNode(service.getNode(nodeId));

        nodes.add(node);
        log.debug("New cachesize: " + nodes.size() + ".");
        log.debug("Finished cache rebuild for node '" + nodeId + "'.");
    }

}