org.apache.brooklyn.location.jclouds.JcloudsLocationResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.brooklyn.location.jclouds.JcloudsLocationResolver.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.brooklyn.location.jclouds;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.NoSuchElementException;

import org.apache.brooklyn.api.location.LocationRegistry;
import org.apache.brooklyn.api.location.LocationResolver;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.core.location.BasicLocationRegistry;
import org.apache.brooklyn.core.location.LocationConfigKeys;
import org.apache.brooklyn.core.location.LocationConfigUtils;
import org.apache.brooklyn.core.location.internal.LocationInternal;
import org.jclouds.apis.ApiMetadata;
import org.jclouds.apis.Apis;
import org.jclouds.providers.ProviderMetadata;
import org.jclouds.providers.Providers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.brooklyn.util.text.Strings;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

@SuppressWarnings("rawtypes")
public class JcloudsLocationResolver implements LocationResolver {

    public static final Logger log = LoggerFactory.getLogger(JcloudsLocationResolver.class);

    private static final String JCLOUDS = "jclouds";

    public static final Map<String, ProviderMetadata> PROVIDERS = getProvidersMap();
    public static final Map<String, ApiMetadata> APIS = getApisMap();

    private static Map<String, ProviderMetadata> getProvidersMap() {
        Map<String, ProviderMetadata> result = Maps.newLinkedHashMap();
        for (ProviderMetadata p : Providers.all()) {
            result.put(p.getId(), p);
        }
        return ImmutableMap.copyOf(result);
    }

    private static Map<String, ApiMetadata> getApisMap() {
        Map<String, ApiMetadata> result = Maps.newLinkedHashMap();
        for (ApiMetadata api : Apis.all()) {
            result.put(api.getId(), api);
        }
        return ImmutableMap.copyOf(result);
    }

    public static final Collection<String> AWS_REGIONS = Arrays.asList(
            // from http://docs.amazonwebservices.com/general/latest/gr/rande.html as of Apr 2012.
            // it is suggested not to maintain this list here, instead to require aws-ec2 explicitly named.
            "eu-west-1", "us-east-1", "us-west-1", "us-west-2", "ap-southeast-1", "ap-northeast-1", "sa-east-1");

    private ManagementContext managementContext;

    @Override
    public void init(ManagementContext managementContext) {
        this.managementContext = checkNotNull(managementContext, "managementContext");
    }

    protected class JcloudsSpecParser {
        String providerOrApi;
        String parameter;

        public JcloudsSpecParser parse(String spec, boolean dryrun) {
            JcloudsSpecParser result = new JcloudsSpecParser();
            int split = spec.indexOf(':');
            if (split < 0) {
                if (spec.equalsIgnoreCase(getPrefix())) {
                    if (dryrun)
                        return null;
                    throw new IllegalArgumentException("Cannot use '" + spec
                            + "' as a location ID; it is insufficient. " + "Try jclouds:aws-ec2 (for example).");
                }
                result.providerOrApi = spec;
                result.parameter = null;
            } else {
                result.providerOrApi = spec.substring(0, split);
                result.parameter = spec.substring(split + 1);
                int numJcloudsPrefixes = 0;
                while (result.providerOrApi.equalsIgnoreCase(getPrefix())) {
                    //strip any number of jclouds: prefixes, for use by static "resolve" method
                    numJcloudsPrefixes++;
                    result.providerOrApi = result.parameter;
                    result.parameter = null;
                    split = result.providerOrApi.indexOf(':');
                    if (split >= 0) {
                        result.parameter = result.providerOrApi.substring(split + 1);
                        result.providerOrApi = result.providerOrApi.substring(0, split);
                    }
                }
                if (!dryrun && numJcloudsPrefixes > 1) {
                    log.warn("Use of deprecated location spec '" + spec
                            + "'; in future use a single \"jclouds\" prefix");
                }
            }

            if (result.parameter == null && AWS_REGIONS.contains(result.providerOrApi)) {
                // treat amazon as a default
                result.parameter = result.providerOrApi;
                result.providerOrApi = "aws-ec2";
                if (!dryrun)
                    log.warn("Use of deprecated location '" + result.parameter
                            + "'; in future refer to with explicit " + "provider '" + result.providerOrApi + ":"
                            + result.parameter + "'");
            }

            return result;
        }

        public boolean isProvider() {
            return PROVIDERS.containsKey(providerOrApi);
        }

        public boolean isApi() {
            return APIS.containsKey(providerOrApi);
        }

        public String getProviderOrApi() {
            return providerOrApi;
        }

        public String getParameter() {
            return parameter;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public JcloudsLocation newLocationFromString(Map locationFlags, String spec, LocationRegistry registry) {
        Map globalProperties = registry.getProperties();

        JcloudsSpecParser details = new JcloudsSpecParser().parse(spec, false);
        String namedLocation = (String) locationFlags.get(LocationInternal.NAMED_SPEC_NAME.getName());

        boolean isProvider = details.isProvider();
        String providerOrApi = details.providerOrApi;
        // gce claims to be an api ... perhaps just a bug? email sent to jclouds dev list, 28 mar 2014
        isProvider = isProvider || "google-compute-engine".equals(providerOrApi);

        if (Strings.isEmpty(providerOrApi)) {
            throw new IllegalArgumentException("Cloud provider/API type not specified in spec \"" + spec + "\"");
        }
        if (!isProvider && !details.isApi()) {
            throw new NoSuchElementException(
                    "Cloud provider/API type " + providerOrApi + " is not supported by jclouds");
        }

        // For everything in brooklyn.properties, only use things with correct prefix (and remove that prefix).
        // But for everything passed in via locationFlags, pass those as-is.
        // TODO Should revisit the locationFlags: where are these actually used? Reason accepting properties without
        //      full prefix is that the map's context is explicitly this location, rather than being generic properties.
        Map allProperties = getAllProperties(registry, globalProperties);
        String regionOrEndpoint = details.parameter;
        if (regionOrEndpoint == null && isProvider)
            regionOrEndpoint = (String) locationFlags.get(LocationConfigKeys.CLOUD_REGION_ID.getName());
        Map jcloudsProperties = new JcloudsPropertiesFromBrooklynProperties().getJcloudsProperties(providerOrApi,
                regionOrEndpoint, namedLocation, allProperties);
        jcloudsProperties.putAll(locationFlags);

        if (regionOrEndpoint != null) {
            // apply the regionOrEndpoint (e.g. from the parameter) as appropriate -- but only if it has not been overridden
            if (isProvider) {
                // providers from ServiceLoader take a location (endpoint already configured), and optionally a region name
                // NB blank might be supplied if spec string is "mycloud:" -- that should be respected, 
                // whereas no parameter/regionName ie null value -- "mycloud" -- means don't set
                if (Strings.isBlank(
                        Strings.toString(jcloudsProperties.get(JcloudsLocationConfig.CLOUD_REGION_ID.getName()))))
                    jcloudsProperties.put(JcloudsLocationConfig.CLOUD_REGION_ID.getName(), regionOrEndpoint);
            } else {
                // other "providers" are APIs so take an _endpoint_ (but not a location);
                // see note above re null here
                if (Strings.isBlank(
                        Strings.toString(jcloudsProperties.get(JcloudsLocationConfig.CLOUD_ENDPOINT.getName()))))
                    jcloudsProperties.put(JcloudsLocationConfig.CLOUD_ENDPOINT.getName(), regionOrEndpoint);
            }
        }

        return managementContext.getLocationManager()
                .createLocation(LocationSpec.create(getLocationClass()).configure(LocationConfigUtils
                        .finalAndOriginalSpecs(spec, jcloudsProperties, globalProperties, namedLocation))
                        .configure(jcloudsProperties));
    }

    @SuppressWarnings("unchecked")
    private Map getAllProperties(LocationRegistry registry, Map<?, ?> properties) {
        Map<Object, Object> allProperties = Maps.newHashMap();
        if (registry != null)
            allProperties.putAll(registry.getProperties());
        allProperties.putAll(properties);
        return allProperties;
    }

    @Override
    public String getPrefix() {
        return JCLOUDS;
    }

    protected Class<? extends JcloudsLocation> getLocationClass() {
        return JcloudsLocation.class;
    }

    @Override
    public boolean accepts(String spec, LocationRegistry registry) {
        if (BasicLocationRegistry.isResolverPrefixForSpec(this, spec, true))
            return true;
        JcloudsSpecParser details = new JcloudsSpecParser().parse(spec, true);
        if (details == null)
            return false;
        if (details.isProvider() || details.isApi())
            return true;
        return false;
    }

}