Package org.nimbustools.metadataserver.defaults

Source Code of org.nimbustools.metadataserver.defaults.DefaultMetadataServer

/*
* Copyright 1999-2008 University of Chicago
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package org.nimbustools.metadataserver.defaults;

import org.nimbustools.api.services.rm.Manager;
import org.nimbustools.api.services.metadata.MetadataServer;
import org.nimbustools.api.services.metadata.MetadataServerException;
import org.nimbustools.api.services.metadata.MetadataServerUnauthorizedException;
import org.nimbustools.api.repr.vm.VM;
import org.nimbustools.api.repr.vm.VMFile;
import org.nimbustools.api.repr.vm.NIC;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.safehaus.uuid.UUIDGenerator;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URI;
import java.util.*;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

/**
* See: http://docs.amazonwebservices.com/AWSEC2/2008-08-08/DeveloperGuide/index.html?AESDG-chapter-instancedata.html
*/
@SuppressWarnings("unchecked")
public class DefaultMetadataServer implements MetadataServer {

    // -------------------------------------------------------------------------
    // STATIC VARIABLES
    // -------------------------------------------------------------------------

    private static final Log logger =
           LogFactory.getLog(MetadataRequestHandler.class.getName());

    protected static final String CONTACT_SOCKET_PREFIX = "contact.socket";

    // -------------------------------------------------------------------------
    // INSTANCE VARIABLES
    // -------------------------------------------------------------------------

    protected Manager manager;
    protected String customizationPath;
    protected String listenSocket = null;
    protected boolean enabled;
    protected boolean listening;
    protected HTTPListener listener;
    protected String[] localNets;
    protected String[] publicNets;

    protected final Cache cache;

    protected Properties properties;
   
    private final UUIDGenerator uuidGen;
    private Set<URL> listenSockets;
    private Map<String, URL> networkContacts;
    private URL defaultContact;


    // -------------------------------------------------------------------------
    // CONSTRUCTOR
    // -------------------------------------------------------------------------

    public DefaultMetadataServer(CacheManager cacheManager) {
        if (cacheManager == null) {
            throw new IllegalArgumentException("cacheManager may not be null");
        }

        this.cache = cacheManager.getCache("metadataServerCache");
        if (this.cache == null) {
            throw new IllegalArgumentException(
                    "cacheManager does not provide 'metadataServerCache'");
        }
       
        this.uuidGen = UUIDGenerator.getInstance();
    }

   
    // -------------------------------------------------------------------------
    // PROPERTIES
    // -------------------------------------------------------------------------

    public void setCustomizationPath(String path) {
        this.customizationPath = path;
    }

    public void setListenSocket(String listenSocket) {
        this.listenSocket = listenSocket;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void setManager(Manager manager) {
        this.manager = manager;
    }

    public void setLocalNets(String localNetsStr) {
        if (localNetsStr == null || localNetsStr.trim().length() == 0) {
            this.localNets = null;
            return;
        }

        final String[] parts = localNetsStr.split(",");
        final ArrayList partsArray = new ArrayList(parts.length);
        for (String part : parts) {
            if (part != null && part.trim().length() > 0) {
                partsArray.add(part.trim());
            }
        }
        this.localNets = (String[]) partsArray.toArray(new String[0]);
    }

    public void setPublicNets(String publicNetsStr) {
        if (publicNetsStr == null || publicNetsStr.trim().length() == 0) {
            this.publicNets = null;
            return;
        }
       
        final String[] parts = publicNetsStr.split(",");
        final ArrayList partsArray = new ArrayList(parts.length);
        for (String part : parts) {
            if (part != null && part.trim().length() > 0) {
                partsArray.add(part.trim());
            }
        }
        this.publicNets = (String[]) partsArray.toArray(new String[0]);
    }

    // -------------------------------------------------------------------------
    // implements MetadataServer
    // -------------------------------------------------------------------------

    public String getResponse(String target, String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        // If developers try to access directly , these checks could trigger.
        // Possible use cases in future?  If so, the intialize method will
        // need to be changed to bring the server up even if the HTTP server
        // fails to start up for whatever reason.
       
        if (!this.enabled) {
            throw new MetadataServerException("metadata server not enabled");
        }
        if (!this.listening) {
            throw new MetadataServerException(
                    "metadata server did not initialize correctly, sorry.");
        }

        logger.debug("considering target: '" + target + "', client: " +
                         remoteAddress);

        try {
            return this.dispatch(target, remoteAddress);
        } catch (MetadataServerUnauthorizedException e) {
            logger.error("UNAUTHORIZED call to metadata server, message: " +
                    e.getMessage());
            throw e;
        } catch (MetadataServerException e) {
            logger.error("Problem with metadata server, message: " +
                    e.getMessage() + " ||| Client visible message: " +
                    e.getClientVisibleMessage());
            throw e;
        } catch (Throwable t) {
            final String err = "Unhandled problem dispatching metadata " +
                        "request: " + t.getMessage();
            if (logger.isDebugEnabled()) {
                logger.error(err, t);
            } else {
                logger.error(err);
            }
            throw new MetadataServerException(err, t);
        }
    }

    public String getCustomizationPath() {
        return this.customizationPath;
    }

    public synchronized String getContactURL(NIC[] nics) {
       
        if (!this.listening) {
            logger.warn("contact URL requested but not listening?");
            return null;
        }

        if (this.listener == null) {
            logger.warn("listening but no listener??");
            return null;
        }

        // search for the first NIC with a matching contact address
        if (!this.networkContacts.isEmpty() && nics != null) {
            for (NIC nic : nics) {
                final String network = nic.getNetworkName();
                if (network == null) {
                    continue;
                }
                final URL url = this.networkContacts.get(network);
                if (url != null) {
                    return url.toString();
                }
            }
        }

        if (defaultContact != null) {
            return defaultContact.toString();
        }

        return null;
    }

    public synchronized boolean isListening() {
        return this.listening;
    }

    public boolean isEnabled() {
        return this.enabled;
    }

    public synchronized void initServerAndListen() throws Exception {

        if (!this.enabled) {
            logger.info("metadata server not enabled");
            return;
        }

        initialize();
        start();
    }

    public synchronized void initialize() throws Exception {

        if (!this.enabled) {
            logger.info("metadata server not enabled");
            return;
        }

        if (this.listening) {
            throw new Exception("already listening");
        }

        this.intakeProperties();

        final MetadataRequestHandler handler = new MetadataRequestHandler(this);
        this.listener = new HTTPListener(this.listenSockets);
        this.listener.initServer(handler);
    }

    public synchronized void start() throws Exception {
        this.listener.start();
        this.listening = true;
    }

    public synchronized void stop() throws Exception {
        if (!this.listening) {
            return;
        }

        this.listener.stop();
        this.listening = false;
    }

    private void intakeProperties() throws Exception {
        if (this.properties == null) {
            throw new Exception("properties not provided, don't know where to listen");
        }

        URL defaultContact = null;
        final Map<String, URL> networkContacts = new HashMap();

        for (Object propNameObject : properties.keySet()) {
            final String propName = (String) propNameObject;

            if (propName.startsWith(CONTACT_SOCKET_PREFIX)) {
                String value = this.properties.getProperty(propName);

                if (value == null) {
                    continue;
                }
                value = value.trim();
                if (value.length() == 0) {
                    continue;
                }

                // hardcoding http here on purpose, to make it obvious that https is
                // not supported if someone tries to put more than a host+port in the
                // configuration
                final URL url;
                try {
                    url = new URL("http://" + value);
                } catch (MalformedURLException e) {
                    throw new Exception("Invalid host:port value in "+ propName +
                            " property: " + e.getMessage());
                }

                if (propName.length() == CONTACT_SOCKET_PREFIX.length()) {
                    defaultContact = url;
                } else if (propName.charAt(CONTACT_SOCKET_PREFIX.length()) == '.') {
                    final String network = propName.substring(CONTACT_SOCKET_PREFIX.length()+1);

                    if (network.length() == 0) {
                        throw new Exception("Missing network name in property: " + propName);
                    }
                    networkContacts.put(network, url);
                }
            }
        }

        final Set<URL> listenSockets;
        if (defaultContact != null && defaultContact.getHost().equals("0.0.0.0")) {
            if (networkContacts.isEmpty()) {
                throw new Exception("if the metadata server listens on all interfaces (0.0.0.0), " +
                        "you must specify contact addresses to be given to VMs. For example, \""+
                CONTACT_SOCKET_PREFIX + ".public\"");
            }
            listenSockets = java.util.Collections.singleton(defaultContact);

        } else {
            listenSockets = new HashSet<URL>(networkContacts.values());
            if (defaultContact != null) {
                listenSockets.add(defaultContact);
            }
        }
        this.listenSockets = listenSockets;
        this.defaultContact = defaultContact;
        this.networkContacts = networkContacts;
    }

    // -------------------------------------------------------------------------
    // DISPATCH
    // -------------------------------------------------------------------------

    protected final String[] API_VERSIONS =
            {"latest", "1.0", "2007-01-19", "2007-03-01", "2008-08-08"};

    protected String dispatch(String target, String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        if (target.equals("/")) {
            return this.topIndex();
        }

        String subtarget = null;

        for (String version : API_VERSIONS) {
            version = "/" + version;
            if (target.startsWith(version)) {
                if (target.length() == version.length()) {
                    subtarget = "";
                    break;
                } else if (target.charAt(version.length()) == '/') {
                    subtarget = target.substring(version.length() + 1);
                    break;
                }
            }
        }
       
        if (subtarget == null) {
            final StringBuilder sb = new StringBuilder();
            sb.append("Unrecognized path: '").append(target).
                    append("'. Expected first subdirectory in path to be one of: ");
            for (int i = 0; i < API_VERSIONS.length; i++) {
                String version = API_VERSIONS[i];
                if (i == API_VERSIONS.length-1 && API_VERSIONS.length > 1) {
                    sb.append(", or ");
                } else if (i > 0) {
                    sb.append(", ");
                }
                sb.append("'").append(version).append("'");
            }
            final String err = sb.toString();
            throw new MetadataServerException(err, err);
        }
       
        return this.dispatch2(target, subtarget, remoteAddress);
    }

    protected String dispatch2(String target,
                               String subtarget,
                               String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

    if (subtarget.equals("") || subtarget.equals("/")) {
        return "meta-data\nuser-data\n";
       
    } else if (subtarget.startsWith("meta-data/")) {
            return dispatchMetaData(target,
                                    subtarget.substring(10),
                                    remoteAddress);
        } else if (subtarget.startsWith("user-data")) {
            return dispatchUserData(target,
                                    subtarget.substring(9),
                                    remoteAddress);
        } else {
            final String err = "Unrecognized URL: '" + target + "'.  " +
                    "Expected second subdirectory in path to be either " +
                    "'meta-data/' or 'user-data'.";
            throw new MetadataServerException(err, err);
        }
    }

    protected String dispatchUserData(String target,
                                      String subsubtarget,
                                      String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        return this.userData(remoteAddress);
    }

    protected String dispatchMetaData(String target,
                                      String subsubtarget,
                                      String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        if (subsubtarget.equals("") || subsubtarget.equals("/")) {
            return "ami-id\nami-launch-index\nlocal-hostname\nlocal-ipv4\n" +
                    "public-hostname\npublic-ipv4\n";

        } else if (subsubtarget.startsWith("ami-id")) {
            return this.amiID(remoteAddress);
        } else if (subsubtarget.startsWith("ami-launch-index")) {
            return this.amiLaunchIndex(remoteAddress);
        } else if (subsubtarget.startsWith("local-hostname")) {
            return this.localHostname(remoteAddress);
        } else if (subsubtarget.startsWith("local-ipv4")) {
            return this.localIPV4(remoteAddress);
        } else if (subsubtarget.startsWith("public-hostname")) {
            return this.publicHostname(remoteAddress);
        } else if (subsubtarget.startsWith("public-ipv4")) {
            return this.publicIPV4(remoteAddress);
        } else {
            throw unimplemented(target, remoteAddress);
        }
    }

    private static String getUserErr(String method) {
        final String USER_UNIMPL_METHOD = "You tried to use ";
        final String USER_UNIMPL_METHOD2 = ", but that is not " +
            "implemented yet.  Usually these can be implemented quickly, " +
            "inquire on the mailing list.";
        return USER_UNIMPL_METHOD + method + USER_UNIMPL_METHOD2;
    }

    private static String getLogErr(String method, String remoteAddress) {
        return "USER (@ ip: '" + remoteAddress +
                "') TRIED UNIMPLEMENTED: " + method;
    }

    private static MetadataServerException unimplemented(String method, String remoteAddress) {
        return new MetadataServerException(getLogErr(method, remoteAddress),
                                           getUserErr(method));
    }


    // -------------------------------------------------------------------------
    // ASSOCIATE IP ADDRESS
    // -------------------------------------------------------------------------


    /**
     * The usual pattern for access is a burst of request at each VM's boot.
     *
     * In the future this lookup should lock on IP address and not whole
     * metadata server instance.
     *
     * @param ip remote client's IP
     * @return VM instance, never null
     * @throws MetadataServerException problem
     * @throws MetadataServerUnauthorizedException could not associate VM
     */
    public synchronized VM getCachedAndValidatedVM(String ip)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        final Element el = this.cache.get(ip);
        final VM vm;
        if (el == null) {
            vm = this.getVM(ip);
            this.validateVM(vm, ip);
            this.cache.put(new Element(ip, vm));
        } else {
            vm = (VM) el.getObjectValue();
        }
        return vm;
    }

    /**
     * @param ipAddress remote client's IP
     * @return VM instance, never null
     * @throws MetadataServerException problem
     * @throws MetadataServerUnauthorizedException could not associate VM
     */
    public VM getValidatedVM(String ipAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {
       
        final VM vm = this.getVM(ipAddress);
        this.validateVM(vm, ipAddress);
        return vm;
    }

    /**
     * @param ipAddress remote client's IP
     * @return VM instance, never null
     * @throws MetadataServerException problem
     * @throws MetadataServerUnauthorizedException could not associate VM
     */
    public VM getVM(String ipAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        final VM[] vms;
        try {
            vms = this.manager.getAllByIPAddress(ipAddress);
        } catch (Throwable t) {

            final String uuid =
                    this.uuidGen.generateRandomBasedUUID().toString();

            String userError = "There has been an internal server error.  " +
                    "Contact the administrator with this lookup key for more " +
                    "information: '" + uuid + "'";
            String fullError = "Problem querying manager for IP '" +
                    ipAddress + "', ERROR UUID='" + uuid + "': " + t.getMessage();
            throw new MetadataServerException(fullError, userError, t);
        }

        if (vms == null || vms.length == 0) {
            final String err =
                    "Could not associate IP with a VM: " + ipAddress;
            throw new MetadataServerUnauthorizedException(err);
        } else if (vms.length > 1) {
            final String err =
                    "IP is associated with more than one VM! IP: " + ipAddress;
            throw new MetadataServerException(err, err);
        }

        return vms[0];
    }

    protected void validateVM(VM vm,
                              String ip) throws MetadataServerException {
        if (vm == null) {
            throw this.validationProblem("manager implementation is invalid, " +
                    "returned a null VM", ip);
        }

        VMFile[] vmFiles = vm.getVMFiles();
        if (vmFiles == null || vmFiles.length == 0) {
            throw this.validationProblem("manager implementation is invalid, " +
                    "returned a VM with null or zero vmFiles", ip);
        }

        boolean foundRootfile = false;
        for (VMFile file: vmFiles) {
           
            if (file == null) {
                throw this.validationProblem("manager implementation is " +
                        "invalid, returned a VM with a null value in " +
                        "vmFiles array", ip);
            }
           
            final URI uri = file.getURI();
            if (uri == null && file.getBlankSpaceSize() < 1) {
                throw this.validationProblem("manager implementation is " +
                        "invalid, returned a VM with a null URI value in " +
                        "vmFiles array", ip);
            }
            if (file.isRootFile()) {
                if (uri == null) {
                    throw this.validationProblem("manager implementation is " +
                            "invalid, returned a VM with a null URI value " +
                            "for root disk in vmFiles array", ip);
                }
                foundRootfile = true;
            }
        }

        if (!foundRootfile) {
            throw this.validationProblem("manager implementation is " +
                            "invalid, returned a VM with no " +
                            "root disk file in vmFiles array", ip);
        }
    }

    private MetadataServerException validationProblem(String issue,
                                                      String ipAddress) {
        final String uuid =
                    this.uuidGen.generateRandomBasedUUID().toString();

        String userError = "There has been an internal server error.  " +
                "Contact the administrator with this lookup key for more " +
                "information: '" + uuid + "'";
        String fullError = "Problem querying manager for IP '" +
                ipAddress + "', ERROR UUID='" + uuid + "': " + issue;
        return new MetadataServerException(fullError, userError);
    }


    // -------------------------------------------------------------------------
    // 1.0 API (FLAT)
    // -------------------------------------------------------------------------

    /**
     * "/"
     *
     * @return APIs supported.  Sending back lies so that recent tooling works.
     */
    protected String topIndex() {
       
        /* NOT actually supporting these later protocols but providing them
           as a passthrough to 1.0 */
        StringBuilder sb = new StringBuilder();
        for (String version : API_VERSIONS) {
            sb.append(version).append("\n");
        }
        return sb.toString();
    }

    /*
     * "/user-data"
     */
    protected String userData(String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        VM vm = this.getCachedAndValidatedVM(remoteAddress);
        final String data = vm.getMdUserData();
        if (data == null) {
            return "";
        } else {
            return data;
        }
    }

    /*
     * "/meta-data/ami-id"
     */
    protected String amiID(String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        VM vm = this.getCachedAndValidatedVM(remoteAddress);
        VMFile[] files = vm.getVMFiles();
        for (VMFile file: files) {
            if (file.isRootFile()) {
                return this.justFilename(file.getURI().toASCIIString());
            }
        }
        throw this.validationProblem("Should be impossible because we used " +
                                        "get*ValidatedVM", remoteAddress);
    }

    protected String justFilename(String imageURI) {
        if (imageURI == null) {
            throw new IllegalArgumentException("imageURI may not be null");
        }
        final int idx = imageURI.lastIndexOf('/');
        if (idx < 0) {
            return imageURI;
        } else {
            return imageURI.substring(idx+1);
        }
    }

    /*
     * "/meta-data/ami-launch-index"
     */
    protected String amiLaunchIndex(String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {
       
        final VM vm = this.getCachedAndValidatedVM(remoteAddress);
        return Integer.toString(vm.getLaunchIndex());
    }

    /*
     * "/meta-data/local-ipv4"
     */
    protected String localIPV4(String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        final VM vm = this.getCachedAndValidatedVM(remoteAddress);
        final NIC nic = this.getLocalNIC(vm);
        if (nic != null) {
            final String ip = nic.getIpAddress();
            if (ip != null) {
                return ip.trim();
            }
        }
        return "";
    }

    /*
     * "/meta-data/public-ipv4"
     */
    protected String publicIPV4(String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        final VM vm = this.getCachedAndValidatedVM(remoteAddress);
        final NIC nic = this.getPublicNIC(vm);
        if (nic != null) {
            final String ip = nic.getIpAddress();
            if (ip != null) {
                return ip.trim();
            }
        }
        return "";
    }

    /*
     * "/meta-data/local-hostname"
     */
    protected String localHostname(String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {

        final VM vm = this.getCachedAndValidatedVM(remoteAddress);
        final NIC nic = this.getLocalNIC(vm);
        if (nic != null) {
            final String hostname = nic.getHostname();
            if (hostname != null) {
                return hostname.trim();
            }
        }
        return "";
    }

    /*
     * "/meta-data/public-hostname"
     */
    protected String publicHostname(String remoteAddress)
            throws MetadataServerException,
                   MetadataServerUnauthorizedException {
       
        final VM vm = this.getCachedAndValidatedVM(remoteAddress);
        final NIC nic = this.getPublicNIC(vm);
        if (nic != null) {
            final String hostname = nic.getHostname();
            if (hostname != null) {
                return hostname.trim();
            }
        }
        return "";
    }

    private NIC getLocalNIC(VM vm) {
        return findNIC(this.localNets, vm);
    }

    private NIC getPublicNIC(VM vm) {
        return findNIC(this.publicNets, vm);
    }

    private static NIC findNIC(String[] networkNames, VM vm) {
        if (networkNames == null || networkNames.length == 0) {
            return null;
        }
        final NIC[] nics = vm.getNics();
        if (nics == null || nics.length == 0) {
            return null;
        }
        for (String netname : networkNames) {
            final NIC nic = getParticularNetworkNIC(netname, nics);
            if (nic != null) {
                return nic;
            }
        }
        return null;
    }

    private static NIC getParticularNetworkNIC(String netname, NIC[] nics) {
        if (netname == null || netname.trim().length() == 0) {
            return null;
        }
        for (NIC nic : nics) {
            if (nic != null) {
                final String oneName = nic.getNetworkName();
                if (oneName != null
                        && oneName.trim().equals(netname.trim())) {
                    return nic;
                }
            }
        }
        return null;
    }
}
TOP

Related Classes of org.nimbustools.metadataserver.defaults.DefaultMetadataServer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.