org.jclouds.profitbricks.compute.ProfitBricksComputeServiceAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.jclouds.profitbricks.compute.ProfitBricksComputeServiceAdapter.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.jclouds.profitbricks.compute;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.util.concurrent.Futures.allAsList;
import static com.google.common.util.concurrent.Futures.getUnchecked;
import static java.lang.String.format;
import static org.jclouds.Constants.PROPERTY_USER_THREADS;
import static org.jclouds.profitbricks.config.ProfitBricksComputeProperties.POLL_PREDICATE_DATACENTER;

import java.util.List;
import java.util.concurrent.Callable;

import javax.annotation.Resource;
import javax.inject.Named;
import javax.inject.Singleton;

import org.jclouds.compute.ComputeServiceAdapter;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.HardwareBuilder;
import org.jclouds.compute.domain.Processor;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.Volume;
import org.jclouds.compute.domain.internal.VolumeImpl;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.compute.util.ComputeServiceUtils;
import org.jclouds.domain.Location;
import org.jclouds.domain.LocationScope;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.logging.Logger;
import org.jclouds.profitbricks.ProfitBricksApi;
import org.jclouds.profitbricks.compute.concurrent.ProvisioningJob;
import org.jclouds.profitbricks.compute.concurrent.ProvisioningManager;
import org.jclouds.profitbricks.compute.function.ProvisionableToImage;
import org.jclouds.profitbricks.compute.strategy.TemplateWithDataCenter;
import org.jclouds.profitbricks.domain.AvailabilityZone;
import org.jclouds.profitbricks.domain.DataCenter;
import org.jclouds.profitbricks.domain.Image;
import org.jclouds.profitbricks.domain.Provisionable;
import org.jclouds.profitbricks.domain.Server;
import org.jclouds.profitbricks.domain.Snapshot;
import org.jclouds.profitbricks.domain.Storage;
import org.jclouds.profitbricks.features.ServerApi;
import org.jclouds.profitbricks.util.Passwords;
import org.jclouds.rest.ResourceNotFoundException;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.inject.Inject;

@Singleton
public class ProfitBricksComputeServiceAdapter
        implements ComputeServiceAdapter<Server, Hardware, Provisionable, Location> {

    @Resource
    @Named(ComputeServiceConstants.COMPUTE_LOGGER)
    protected Logger logger = Logger.NULL;

    private final ProfitBricksApi api;
    private final Predicate<String> waitDcUntilAvailable;
    private final ListeningExecutorService executorService;
    private final ProvisioningJob.Factory jobFactory;
    private final ProvisioningManager provisioningManager;

    private static final Integer DEFAULT_LAN_ID = 1;

    @Inject
    ProfitBricksComputeServiceAdapter(ProfitBricksApi api,
            @Named(POLL_PREDICATE_DATACENTER) Predicate<String> waitDcUntilAvailable,
            @Named(PROPERTY_USER_THREADS) ListeningExecutorService executorService,
            ProvisioningJob.Factory jobFactory, ProvisioningManager provisioningManager) {
        this.api = api;
        this.waitDcUntilAvailable = waitDcUntilAvailable;
        this.executorService = executorService;
        this.jobFactory = jobFactory;
        this.provisioningManager = provisioningManager;
    }

    @Override
    public NodeAndInitialCredentials<Server> createNodeWithGroupEncodedIntoName(String group, String name,
            Template template) {
        checkArgument(template instanceof TemplateWithDataCenter,
                "This implementation requires a TemplateWithDataCenter");
        return createNodeWithGroupEncodedIntoName(group, name, TemplateWithDataCenter.class.cast(template));
    }

    protected NodeAndInitialCredentials<Server> createNodeWithGroupEncodedIntoName(String group, String name,
            TemplateWithDataCenter template) {
        checkArgument(template.getLocation().getScope() == LocationScope.ZONE,
                "Template must use a ZONE-scoped location");
        final String dataCenterId = template.getDataCenter().id();

        Hardware hardware = template.getHardware();

        TemplateOptions options = template.getOptions();
        final String loginUser = isNullOrEmpty(options.getLoginUser()) ? "root" : options.getLoginUser();
        final String password = options.hasLoginPassword() ? options.getLoginPassword() : Passwords.generate();

        final org.jclouds.compute.domain.Image image = template.getImage();

        // provision all storages based on hardware
        List<? extends Volume> volumes = hardware.getVolumes();
        List<String> storageIds = Lists.newArrayListWithExpectedSize(volumes.size());

        int i = 1;
        for (final Volume volume : volumes)
            try {
                logger.trace("<< provisioning storage '%s'", volume);
                final Storage.Request.CreatePayload.Builder storageBuilder = Storage.Request.creatingBuilder();
                if (i == 1) {
                    storageBuilder.mountImageId(image.getId());
                    // we don't need to pass password to the API if we're using a snapshot
                    Provisionable.Type provisionableType = Provisionable.Type
                            .fromValue(image.getUserMetadata().get(ProvisionableToImage.KEY_PROVISIONABLE_TYPE));
                    if (provisionableType == Provisionable.Type.IMAGE)
                        storageBuilder.imagePassword(password);
                }
                storageBuilder.dataCenterId(dataCenterId).name(format("%s-disk-%d", name, i++))
                        .size(volume.getSize());

                String storageId = (String) provisioningManager
                        .provision(jobFactory.create(dataCenterId, new Supplier<Object>() {

                            @Override
                            public Object get() {
                                return api.storageApi().createStorage(storageBuilder.build());
                            }
                        }));

                storageIds.add(storageId);
                logger.trace(">> provisioning complete for storage. returned id='%s'", storageId);
            } catch (Exception ex) {
                if (i - 1 == 1) // if first storage (one with image) provisioning fails; stop method
                    throw Throwables.propagate(ex);
                logger.warn(ex, ">> failed to provision storage. skipping..");
            }

        int lanId = DEFAULT_LAN_ID;
        if (options.getNetworks() != null)
            try {
                String networkId = Iterables.get(options.getNetworks(), 0);
                lanId = Integer.parseInt(networkId);
            } catch (Exception ex) {
                logger.warn("no valid network id found from options. using default id='%d'", DEFAULT_LAN_ID);
            }

        Double cores = ComputeServiceUtils.getCores(hardware);

        // provision server and connect boot storage (first provisioned)
        String serverId = null;
        try {
            String storageBootDeviceId = Iterables.get(storageIds, 0); // must have atleast 1
            final Server.Request.CreatePayload serverRequest = Server.Request.creatingBuilder()
                    .dataCenterId(dataCenterId).name(name).bootFromStorageId(storageBootDeviceId)
                    .cores(cores.intValue()).ram(hardware.getRam()).availabilityZone(AvailabilityZone.AUTO)
                    .hasInternetAccess(true).lanId(lanId).build();
            logger.trace("<< provisioning server '%s'", serverRequest);

            serverId = (String) provisioningManager
                    .provision(jobFactory.create(dataCenterId, new Supplier<Object>() {

                        @Override
                        public Object get() {
                            return api.serverApi().createServer(serverRequest);
                        }
                    }));
            logger.trace(">> provisioning complete for server. returned id='%s'", serverId);

        } catch (Exception ex) {
            logger.error(ex, ">> failed to provision server. rollbacking..");
            destroyStorages(storageIds, dataCenterId);
            throw Throwables.propagate(ex);
        }

        // connect the rest of storages to server; delete if fails
        final int storageCount = storageIds.size();
        for (int j = 1; j < storageCount; j++) { // skip first; already connected
            String storageId = storageIds.get(j);
            try {
                logger.trace("<< connecting storage '%s' to server '%s'", storageId, serverId);
                final Storage.Request.ConnectPayload request = Storage.Request.connectingBuilder()
                        .storageId(storageId).serverId(serverId).build();

                provisioningManager.provision(jobFactory.create(group, new Supplier<Object>() {

                    @Override
                    public Object get() {
                        return api.storageApi().connectStorageToServer(request);
                    }
                }));

                logger.trace(">> storage connected.");
            } catch (Exception ex) {
                // delete unconnected storage
                logger.warn(ex, ">> failed to connect storage '%s'. deleting..", storageId);
                destroyStorage(storageId, dataCenterId);
            }
        }

        // Last paranoid check
        waitDcUntilAvailable.apply(dataCenterId);

        LoginCredentials serverCredentials = LoginCredentials.builder().user(loginUser).password(password).build();

        Server server = getNode(serverId);

        return new NodeAndInitialCredentials<Server>(server, serverId, serverCredentials);
    }

    @Override
    public Iterable<Hardware> listHardwareProfiles() {
        // Max [cores=48] [disk size per storage=2048GB] [ram=200704 MB]
        List<Hardware> hardwares = Lists.newArrayList();
        for (int core = 1; core <= 48; core++)
            for (int ram : new int[] { 1024, 2 * 1024, 4 * 1024, 8 * 1024, 10 * 1024, 16 * 1024, 24 * 1024,
                    28 * 1024, 32 * 1024 })
                for (float size : new float[] { 10, 20, 30, 50, 80, 100, 150, 200, 250, 500 }) {
                    String id = String.format("cpu=%d,ram=%s,disk=%f", core, ram, size);
                    hardwares.add(new HardwareBuilder().ids(id).ram(ram).hypervisor("kvm").name(id)
                            .processor(new Processor(core, 1d)).volume(new VolumeImpl(size, true, true)).build());
                }
        return hardwares;
    }

    @Override
    public Iterable<Provisionable> listImages() {
        // fetch images..
        ListenableFuture<List<Image>> images = executorService.submit(new Callable<List<Image>>() {

            @Override
            public List<Image> call() throws Exception {
                logger.trace("<< fetching images..");
                // Filter HDD types only, since JClouds doesn't have a concept of "CD-ROM" anyway
                Iterable<Image> filteredImages = Iterables.filter(api.imageApi().getAllImages(),
                        new Predicate<Image>() {

                            @Override
                            public boolean apply(Image image) {
                                return image.type() == Image.Type.HDD;
                            }
                        });
                logger.trace(">> images fetched.");

                return ImmutableList.copyOf(filteredImages);
            }

        });
        // and snapshots at the same time
        ListenableFuture<List<Snapshot>> snapshots = executorService.submit(new Callable<List<Snapshot>>() {

            @Override
            public List<Snapshot> call() throws Exception {
                logger.trace("<< fetching snapshots");
                List<Snapshot> remoteSnapshots = api.snapshotApi().getAllSnapshots();
                logger.trace(">> snapshots feched.");

                return remoteSnapshots;
            }

        });

        return Iterables.concat(getUnchecked(images), getUnchecked(snapshots));
    }

    @Override
    public Provisionable getImage(String id) {
        // try search images
        logger.trace("<< searching for image with id=%s", id);
        try {
            Image image = api.imageApi().getImage(id);
            if (image != null) {
                logger.trace(">> found image [%s].", image.name());
                return image;
            }
        } catch (Exception ex) {
            logger.warn(ex, ">> unexpected error getting image. Trying to get as a snapshot...");
        }

        // try search snapshots
        logger.trace("<< not found from images. searching for snapshot with id=%s", id);
        Snapshot snapshot = api.snapshotApi().getSnapshot(id);
        if (snapshot != null) {
            logger.trace(">> found snapshot [%s]", snapshot.name());
            return snapshot;
        }
        throw new ResourceNotFoundException("No image/snapshot with id '" + id + "' was found");
    }

    @Override
    public Iterable<Location> listLocations() {
        // Will never be called
        throw new UnsupportedOperationException("Locations are configured in jclouds properties");
    }

    @Override
    public Server getNode(String id) {
        logger.trace("<< searching for server with id=%s", id);

        Server server = api.serverApi().getServer(id);
        if (server != null)
            logger.trace(">> found server [%s]", server.name());
        return server;
    }

    @Override
    public void destroyNode(String nodeId) {
        ServerApi serverApi = api.serverApi();
        Server server = serverApi.getServer(nodeId);
        if (server != null) {
            String dataCenterId = server.dataCenter().id();
            for (Storage storage : server.storages())
                destroyStorage(storage.id(), dataCenterId);

            try {
                destroyServer(nodeId, dataCenterId);
            } catch (Exception ex) {
                logger.warn(ex, ">> failed to delete server with id=%s", nodeId);
            }
        }
    }

    @Override
    public void rebootNode(final String id) {
        // Fail pre-emptively if not found
        final Server node = getRequiredNode(id);
        final DataCenter dataCenter = node.dataCenter();
        provisioningManager.provision(jobFactory.create(dataCenter.id(), new Supplier<Object>() {

            @Override
            public Object get() {
                api.serverApi().resetServer(id);

                return node;
            }
        }));
    }

    @Override
    public void resumeNode(final String id) {
        final Server node = getRequiredNode(id);
        if (node.status() == Server.Status.RUNNING)
            return;

        final DataCenter dataCenter = node.dataCenter();
        provisioningManager.provision(jobFactory.create(dataCenter.id(), new Supplier<Object>() {

            @Override
            public Object get() {
                api.serverApi().startServer(id);

                return node;
            }
        }));
    }

    @Override
    public void suspendNode(final String id) {
        final Server node = getRequiredNode(id);
        // Intentionally didn't include SHUTDOWN (only achieved via UI; soft-shutdown). 
        // A SHUTOFF server is no longer billed, so we execute method for all other status
        if (node.status() == Server.Status.SHUTOFF)
            return;

        final DataCenter dataCenter = node.dataCenter();
        provisioningManager.provision(jobFactory.create(dataCenter.id(), new Supplier<Object>() {

            @Override
            public Object get() {
                api.serverApi().stopServer(id);

                return node;
            }
        }));
    }

    @Override
    public Iterable<Server> listNodes() {
        logger.trace(">> fetching all servers..");
        List<Server> servers = api.serverApi().getAllServers();
        logger.trace(">> servers fetched.");
        return servers;
    }

    @Override
    public Iterable<Server> listNodesByIds(final Iterable<String> ids) {
        // Only fetch the requested nodes. Do it in parallel.
        ListenableFuture<List<Server>> futures = allAsList(
                transform(ids, new Function<String, ListenableFuture<Server>>() {

                    @Override
                    public ListenableFuture<Server> apply(final String input) {
                        return executorService.submit(new Callable<Server>() {

                            @Override
                            public Server call() throws Exception {
                                return getNode(input);
                            }
                        });
                    }
                }));

        return getUnchecked(futures);
    }

    private void destroyServer(final String serverId, final String dataCenterId) {
        try {
            logger.trace("<< deleting server with id=%s", serverId);
            provisioningManager.provision(jobFactory.create(dataCenterId, new Supplier<Object>() {

                @Override
                public Object get() {
                    api.serverApi().deleteServer(serverId);

                    return serverId;
                }
            }));
            logger.trace(">> server '%s' deleted.", serverId);
        } catch (Exception ex) {
            logger.warn(ex, ">> failed to delete server with id=%s", serverId);
        }
    }

    private void destroyStorages(List<String> storageIds, String dataCenterId) {
        for (String storageId : storageIds)
            destroyStorage(storageId, dataCenterId);
    }

    private void destroyStorage(final String storageId, final String dataCenterId) {
        try {
            logger.trace("<< deleting storage with id=%s", storageId);
            provisioningManager.provision(jobFactory.create(dataCenterId, new Supplier<Object>() {

                @Override
                public Object get() {
                    api.storageApi().deleteStorage(storageId);

                    return storageId;
                }
            }));
            logger.trace(">> storage '%s' deleted.", storageId);
        } catch (Exception ex) {
            logger.warn(ex, ">> failed to delete storage with id=%s", storageId);
        }
    }

    private Server getRequiredNode(String nodeId) {
        Server node = getNode(nodeId);
        if (node == null)
            throw new ResourceNotFoundException("Node with id'" + nodeId + "' was not found.");
        return node;
    }
}