org.apache.brooklyn.cloudfoundry.location.CloudFoundryLocation.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.brooklyn.cloudfoundry.location.CloudFoundryLocation.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.cloudfoundry.location;

import static org.apache.brooklyn.cloudfoundry.entity.CloudFoundryAppFromManifest.CONFIGURATION_CONTENTS;
import static org.apache.brooklyn.cloudfoundry.entity.CloudFoundryAppFromManifest.CONFIGURATION_URL;
import static org.apache.brooklyn.util.text.Strings.isBlank;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.location.MachineLocation;
import org.apache.brooklyn.api.location.MachineProvisioningLocation;
import org.apache.brooklyn.api.location.NoMachinesAvailableException;
import org.apache.brooklyn.cloudfoundry.entity.CloudFoundryAppFromManifest;
import org.apache.brooklyn.cloudfoundry.entity.VanillaCloudFoundryApplication;
import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
import org.apache.brooklyn.core.location.AbstractLocation;
import org.apache.brooklyn.core.location.cloud.CloudLocationConfig;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
import org.apache.brooklyn.util.exceptions.PropagatedRuntimeException;
import org.apache.brooklyn.util.yaml.Yamls;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v2.info.GetInfoRequest;
import org.cloudfoundry.client.v2.info.GetInfoResponse;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.applications.ApplicationDetail;
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
import org.cloudfoundry.operations.applications.DeleteApplicationRequest;
import org.cloudfoundry.operations.applications.GetApplicationRequest;
import org.cloudfoundry.operations.applications.PushApplicationRequest;
import org.cloudfoundry.operations.applications.RestartApplicationRequest;
import org.cloudfoundry.operations.services.BindServiceInstanceRequest;
import org.cloudfoundry.operations.services.CreateServiceInstanceRequest;
import org.cloudfoundry.operations.services.DeleteServiceInstanceRequest;
import org.cloudfoundry.operations.services.ServiceInstanceSummary;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.UrlResource;

import com.google.common.base.Charsets;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

public class CloudFoundryLocation extends AbstractLocation
        implements MachineProvisioningLocation<MachineLocation>, CloudFoundryLocationConfig {

    private static final Logger LOG = LoggerFactory.getLogger(CloudFoundryLocation.class);

    private CloudFoundryOperations cloudFoundryOperations;
    private CloudFoundryClient cloudFoundryClient;

    public CloudFoundryLocation() {
        super();
    }

    public CloudFoundryLocation(Map<?, ?> properties) {
        super(properties);
    }

    @Override
    public void init() {
        super.init();
    }

    protected CloudFoundryClient getCloudFoundryClient() {
        return getCloudFoundryClient(MutableMap.of());
    }

    protected CloudFoundryClient getCloudFoundryClient(Map<?, ?> flags) {
        ConfigBag conf = (flags == null || flags.isEmpty()) ? config().getBag()
                : ConfigBag.newInstanceExtending(config().getBag(), flags);
        return getCloudFoundryClient(conf);
    }

    protected CloudFoundryClient getCloudFoundryClient(ConfigBag config) {
        if (cloudFoundryClient == null) {
            CloudFoundryClientRegistry registry = getConfig(CF_CLIENT_REGISTRY);
            cloudFoundryClient = registry.getCloudFoundryClient(
                    ResolvingConfigBag.newInstanceExtending(getManagementContext(), config), true);
        }
        return cloudFoundryClient;
    }

    protected CloudFoundryOperations getCloudFoundryOperations() {
        return getCloudFoundryOperations(MutableMap.of());
    }

    protected CloudFoundryOperations getCloudFoundryOperations(Map<?, ?> flags) {
        ConfigBag conf = (flags == null || flags.isEmpty()) ? config().getBag()
                : ConfigBag.newInstanceExtending(config().getBag(), flags);
        return getCloudFoundryOperations(conf);
    }

    protected CloudFoundryOperations getCloudFoundryOperations(ConfigBag config) {
        if (cloudFoundryOperations == null) {
            CloudFoundryClientRegistry registry = getConfig(CF_CLIENT_REGISTRY);
            cloudFoundryOperations = registry.getCloudFoundryOperations(
                    ResolvingConfigBag.newInstanceExtending(getManagementContext(), config), true);
        }
        return cloudFoundryOperations;
    }

    @Override
    public MachineLocation obtain(Map<?, ?> flags) throws NoMachinesAvailableException {
        ConfigBag setupRaw = ConfigBag.newInstanceExtending(config().getBag(), flags);
        ConfigBag setup = ResolvingConfigBag.newInstanceExtending(getManagementContext(), setupRaw);

        cloudFoundryClient = getCloudFoundryClient(setup);
        return createCloudFoundryContainerLocation(setup);
    }

    private MachineLocation createCloudFoundryContainerLocation(ConfigBag setup) {
        Entity entity = lookUpEntityFromCallerContext(setup.get(CALLER_CONTEXT));

        PushApplicationRequest pushApplicationRequest;
        List<String> serviceInstanceNames;
        if (isVanillaCloudFoundryApplication(entity)) {
            pushApplicationRequest = createPushApplicationRequestFromVanillaCloudFoundryApplication(entity);
            serviceInstanceNames = createInstanceServices(
                    entity.config().get(VanillaCloudFoundryApplication.SERVICES));
        } else if (isCloudFoundryAppFromManifest(entity)) {
            Map<?, ?> manifestAsMap = getMapFromManifest(getManifestYamlFromEntity(entity));
            pushApplicationRequest = createPushApplicationRequestFromManifest(manifestAsMap);
            serviceInstanceNames = getServiceInstancesFromManifest(manifestAsMap);
        } else {
            throw new IllegalStateException("Can't deploy entity type different than "
                    + VanillaCloudFoundryApplication.class.getSimpleName());
        }

        pushApplication(pushApplicationRequest);

        String applicationName = pushApplicationRequest.getName();

        // bind services
        if (!serviceInstanceNames.isEmpty()) {
            bindServices(applicationName, serviceInstanceNames);
            restartApplication(applicationName);
        }

        LocationSpec<SshMachineLocation> locationSpec = buildLocationSpec(applicationName,
                setup.get(CALLER_CONTEXT));
        return getManagementContext().getLocationManager().createLocation(locationSpec);
    }

    private LocationSpec<SshMachineLocation> buildLocationSpec(String applicationName, Object callerContext) {
        ApplicationDetail applicationDetail = getApplicationDetail(applicationName);
        String address = Iterables.getOnlyElement(applicationDetail.getUrls());
        Integer port = getSshPort();
        String sshCode = getCloudFoundryOperations().advanced().sshCode().block();

        return LocationSpec.create(SshMachineLocation.class).configure("address", address)
                .configure(CloudFoundryLocationConfig.APPLICATION_NAME, applicationName)
                .configure(SshMachineLocation.PRIVATE_ADDRESSES, ImmutableList.of(address))
                .configure(CloudLocationConfig.USER, String.format("cf:%s/0", applicationDetail.getId()))
                .configure(SshMachineLocation.PASSWORD, sshCode).configure(SshMachineLocation.SSH_PORT, port)
                .configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true)
                .configure(BrooklynConfigKeys.ONBOX_BASE_DIR, "/tmp").configure(CALLER_CONTEXT, callerContext);
    }

    private void pushApplication(PushApplicationRequest pushApplicationRequest) {
        getCloudFoundryOperations().applications().push(pushApplicationRequest).block();
    }

    private Integer getSshPort() {
        // see https://docs.cloudfoundry.org/devguide/deploy-apps/ssh-apps.html#other-ssh-access
        GetInfoResponse info = getCloudFoundryClient().info().get(GetInfoRequest.builder().build()).block();
        String sshEndpoint = info.getApplicationSshEndpoint();
        return Integer.parseInt(Iterables.get(Splitter.on(":").split(sshEndpoint), 1));
    }

    private List getServiceInstancesFromManifest(Map<?, ?> manifestAsMap) {
        return (List) manifestAsMap.get("services");
    }

    private Entity lookUpEntityFromCallerContext(Object callerContext) {
        if (callerContext == null || !(callerContext instanceof Entity)) {
            throw new IllegalStateException("Invalid caller context: " + callerContext);
        }
        return (Entity) callerContext;
    }

    private PushApplicationRequest createPushApplicationRequestFromVanillaCloudFoundryApplication(Entity entity) {
        String applicationName = entity.config().get(VanillaCloudFoundryApplication.APPLICATION_NAME);
        String domainName = entity.config().get(VanillaCloudFoundryApplication.APPLICATION_DOMAIN);
        int memory = entity.config().get(VanillaCloudFoundryApplication.REQUIRED_MEMORY);
        int disk = entity.config().get(VanillaCloudFoundryApplication.REQUIRED_DISK);
        int instances = entity.config().get(VanillaCloudFoundryApplication.REQUIRED_INSTANCES);
        String artifact = entity.config().get(VanillaCloudFoundryApplication.ARTIFACT_PATH);
        Path artifactLocalPath = getArtifactLocalPath(artifact);
        String buildpack = entity.config().get(VanillaCloudFoundryApplication.BUILDPACK);
        return createPushApplicationRequest(applicationName, memory, disk, artifactLocalPath, buildpack, domainName,
                instances);
    }

    private PushApplicationRequest createPushApplicationRequestFromManifest(Map<?, ?> manifestAsMap) {
        String applicationName = (String) manifestAsMap.get("name");
        String buildpack = (String) manifestAsMap.get("buildpack");
        Integer memory = MoreObjects.firstNonNull((Integer) manifestAsMap.get("memory"), 256);
        Integer disk = MoreObjects.firstNonNull((Integer) manifestAsMap.get("disk"), 512);
        String path = (String) manifestAsMap.get("path");
        String domain = (String) manifestAsMap.get("domain");
        Integer instances = MoreObjects.firstNonNull((Integer) manifestAsMap.get("instances"), 1);
        Path artifactLocalPath = getArtifactLocalPath(path);
        return createPushApplicationRequest(applicationName, memory, disk, artifactLocalPath, buildpack, domain,
                instances);
    }

    private String getManifestYamlFromEntity(Entity entity) {
        String configurationUrl = entity.getConfig(CONFIGURATION_URL);
        String configurationContents = entity.config().get(CONFIGURATION_CONTENTS);

        // Exactly one of the two must have a value
        if (isBlank(configurationUrl) == isBlank(configurationContents))
            throw new IllegalArgumentException("Exactly one of the two must have a value: '"
                    + CONFIGURATION_URL.getName() + "' or '" + CONFIGURATION_CONTENTS.getName() + "'.");

        if (!isBlank(configurationUrl)) {
            InputStream inputStream = new ResourceUtils(entity).getResourceFromUrl(configurationUrl);
            return getStringFromInputStream(inputStream);
        } else if (!isBlank(configurationContents)) {
            return configurationContents;
        } else {
            throw new IllegalStateException("Cannot find configurationUrl nor configurationContents in the entity");
        }
    }

    private Map<?, ?> getMapFromManifest(String yaml) {
        return Yamls.getAs(Yamls.parseAll(yaml), Map.class);
    }

    private static String getStringFromInputStream(InputStream inputStream) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        String result;
        int length;
        try {
            while ((length = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, length);
            }
            result = byteArrayOutputStream.toString(Charsets.UTF_8.name());
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
        return result;
    }

    private Path getArtifactLocalPath(String artifact) {
        if (artifact == null)
            return null;
        try {
            return new UrlResource(artifact).getFile().toPath();
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public void release(MachineLocation machine) {
        String applicationName = machine.config().get(CloudFoundryLocationConfig.APPLICATION_NAME);

        List<ServiceInstanceSummary> serviceInstanceSummaries = getCloudFoundryOperations().services()
                .listInstances().collectList().block();
        List<String> instancesToBeDeleted = Lists.newArrayList();

        for (ServiceInstanceSummary serviceInstanceSummary : serviceInstanceSummaries) {
            for (String appName : serviceInstanceSummary.getApplications()) {
                if (applicationName.equalsIgnoreCase(appName)) {
                    instancesToBeDeleted.add(serviceInstanceSummary.getName());
                }
            }
        }

        getCloudFoundryOperations().applications()
                .delete(DeleteApplicationRequest.builder().name(applicationName).deleteRoutes(true).build())
                .block();
        // delete service instances bound to the application
        for (String name : instancesToBeDeleted) {
            getCloudFoundryOperations().services()
                    .deleteInstance(DeleteServiceInstanceRequest.builder().name(name).build()).block();
        }
    }

    protected boolean isVanillaCloudFoundryApplication(Entity entity) {
        return implementsInterface(entity, VanillaCloudFoundryApplication.class);
    }

    protected boolean isCloudFoundryAppFromManifest(Entity entity) {
        return implementsInterface(entity, CloudFoundryAppFromManifest.class);
    }

    private List<String> createInstanceServices(List<Map<String, Object>> services) {
        List<String> serviceInstanceNames = Lists.newArrayList();
        for (Map<String, Object> service : services) {
            for (Map.Entry<String, Object> stringObjectEntry : service.entrySet()) {
                String serviceInstanceName = ((Map<String, String>) stringObjectEntry.getValue())
                        .get("instanceName");
                serviceInstanceNames.add(serviceInstanceName);
                String planName = ((Map<String, String>) stringObjectEntry.getValue()).get("plan");
                Map<String, ?> parameters = (Map<String, ?>) ((Map<String, Object>) stringObjectEntry.getValue())
                        .get("parameters");
                try {
                    getCloudFoundryOperations().services().createInstance(CreateServiceInstanceRequest.builder()
                            .serviceName(stringObjectEntry.getKey()).serviceInstanceName(serviceInstanceName)
                            .planName(planName).parameters(parameters).build()).block();

                } catch (Exception e) {
                    LOG.error("Error creating the service {}, the error was {}", serviceInstanceName, e);
                    throw new PropagatedRuntimeException(e);
                }
            }
        }
        return serviceInstanceNames;
    }

    private PushApplicationRequest createPushApplicationRequest(String applicationName, int memory, int diskQuota,
            Path application, String buildpack, String domain, int instances) {
        return PushApplicationRequest.builder().name(applicationName).healthCheckType(ApplicationHealthCheck.NONE) // TODO is it needed?
                .randomRoute(true).buildpack(buildpack).application(application).instances(instances).domain(domain)
                .diskQuota(diskQuota).memory(memory).build();
    }

    private ApplicationDetail getApplicationDetail(String applicationName) {
        return getCloudFoundryOperations().applications()
                .get(GetApplicationRequest.builder().name(applicationName).build()).block();
    }

    private void bindServices(String applicationName, List<String> serviceInstanceNames) {
        for (String serviceInstanceName : serviceInstanceNames) {
            try {
                getCloudFoundryOperations().services().bind(BindServiceInstanceRequest.builder()
                        .applicationName(applicationName).serviceInstanceName(serviceInstanceName).build()).block();
            } catch (Exception e) {
                LOG.error("Error getting environment for application {} the error was ", applicationName, e);
                throw new PropagatedRuntimeException(e);
            }
        }
    }

    private void restartApplication(String applicationName) {
        getCloudFoundryOperations().applications()
                .restart(RestartApplicationRequest.builder().name(applicationName).build()).block();
    }

    private boolean implementsInterface(Entity entity, Class<?> type) {
        return Iterables.tryFind(Arrays.asList(entity.getClass().getInterfaces()), Predicates.assignableFrom(type))
                .isPresent();
    }

    @Override
    public MachineProvisioningLocation<MachineLocation> newSubLocation(Map<?, ?> map) {
        return null;
    }

    @Override
    public Map<String, Object> getProvisioningFlags(Collection<String> collection) {
        return null;
    }

}