All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jclouds.googlecomputeengine.compute.GoogleComputeEngineServiceAdapter Maven / Gradle / Ivy

/*
 * 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.googlecomputeengine.compute;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.contains;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.tryFind;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.CENTOS_PROJECT;
import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.DEBIAN_PROJECT;
import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.GCE_BOOT_DISK_SUFFIX;
import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.GCE_DELETE_BOOT_DISK_METADATA_KEY;
import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.GCE_IMAGE_METADATA_KEY;
import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_INTERVAL;
import static org.jclouds.googlecomputeengine.GoogleComputeEngineConstants.OPERATION_COMPLETE_TIMEOUT;
import static org.jclouds.googlecomputeengine.domain.Instance.NetworkInterface.AccessConfig.Type;
import static org.jclouds.googlecomputeengine.predicates.InstancePredicates.isBootDisk;
import static org.jclouds.util.Predicates2.retry;

import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

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

import org.jclouds.collect.Memoized;
import org.jclouds.compute.ComputeServiceAdapter;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.domain.Location;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.googlecomputeengine.GoogleComputeEngineApi;
import org.jclouds.googlecomputeengine.compute.functions.FirewallTagNamingConvention;
import org.jclouds.googlecomputeengine.compute.options.GoogleComputeEngineTemplateOptions;
import org.jclouds.googlecomputeengine.config.UserProject;
import org.jclouds.googlecomputeengine.domain.Disk;
import org.jclouds.googlecomputeengine.domain.Image;
import org.jclouds.googlecomputeengine.domain.Instance;
import org.jclouds.googlecomputeengine.domain.Instance.AttachedDisk;
import org.jclouds.googlecomputeengine.domain.Instance.PersistentAttachedDisk;
import org.jclouds.googlecomputeengine.domain.InstanceInZone;
import org.jclouds.googlecomputeengine.domain.InstanceTemplate;
import org.jclouds.googlecomputeengine.domain.InstanceTemplate.PersistentDisk;
import org.jclouds.googlecomputeengine.domain.InstanceTemplate.PersistentDisk.Mode;
import org.jclouds.googlecomputeengine.domain.MachineType;
import org.jclouds.googlecomputeengine.domain.MachineTypeInZone;
import org.jclouds.googlecomputeengine.domain.Operation;
import org.jclouds.googlecomputeengine.domain.SlashEncodedIds;
import org.jclouds.googlecomputeengine.domain.Zone;
import org.jclouds.googlecomputeengine.features.InstanceApi;
import org.jclouds.http.HttpResponse;
import org.jclouds.logging.Logger;

import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.Atomics;
import com.google.common.util.concurrent.UncheckedTimeoutException;
import com.google.inject.Inject;

/**
 * @author David Alves
 */
public class GoogleComputeEngineServiceAdapter implements ComputeServiceAdapter {

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

   private final GoogleComputeEngineApi api;
   private final Supplier userProject;
   private final Supplier> zones;
   private final Function> metatadaFromTemplateOptions;
   private final Predicate> retryOperationDonePredicate;
   private final long operationCompleteCheckInterval;
   private final long operationCompleteCheckTimeout;
   private final FirewallTagNamingConvention.Factory firewallTagNamingConvention;

   @Inject
   public GoogleComputeEngineServiceAdapter(GoogleComputeEngineApi api,
                                            @UserProject Supplier userProject,
                                            Function> metatadaFromTemplateOptions,
                                            @Named("zone") Predicate> operationDonePredicate,
                                            @Named(OPERATION_COMPLETE_INTERVAL) Long operationCompleteCheckInterval,
                                            @Named(OPERATION_COMPLETE_TIMEOUT) Long operationCompleteCheckTimeout,
                                            @Memoized Supplier> zones,
                                            FirewallTagNamingConvention.Factory firewallTagNamingConvention) {
      this.api = checkNotNull(api, "google compute api");
      this.userProject = checkNotNull(userProject, "user project name");
      this.metatadaFromTemplateOptions = checkNotNull(metatadaFromTemplateOptions,
              "metadata from template options function");
      this.operationCompleteCheckInterval = checkNotNull(operationCompleteCheckInterval,
              "operation completed check interval");
      this.operationCompleteCheckTimeout = checkNotNull(operationCompleteCheckTimeout,
                                                        "operation completed check timeout");
      this.retryOperationDonePredicate = retry(operationDonePredicate, operationCompleteCheckTimeout,
                                               operationCompleteCheckInterval, TimeUnit.MILLISECONDS);
      this.zones = checkNotNull(zones, "zones");
      this.firewallTagNamingConvention = checkNotNull(firewallTagNamingConvention, "firewallTagNamingConvention");
   }

   @Override
   public NodeAndInitialCredentials createNodeWithGroupEncodedIntoName(
           final String group, final String name, final Template template) {

      checkNotNull(template, "template");

      GoogleComputeEngineTemplateOptions options = GoogleComputeEngineTemplateOptions.class.cast(template.getOptions()).clone();
      checkState(options.getNetwork().isPresent(), "network was not present in template options");
      Hardware hardware = checkNotNull(template.getHardware(), "hardware must be set");

      checkNotNull(hardware.getUri(), "hardware must have a URI");
      checkNotNull(template.getImage().getUri(), "image URI is null");

      // Note that the ordering is significant here - the first disk must be the boot disk.
      List disks = Lists.newArrayList();

      if (!tryFind(options.getDisks(), isBootDisk()).isPresent()) {
         Disk bootDisk = createBootDisk(template, name);

         disks.add(new PersistentDisk(Mode.READ_WRITE,
                                      bootDisk.getSelfLink(),
                                      null,
                                      true,
                                      true));
      }

      disks.addAll(options.getDisks());

      InstanceTemplate instanceTemplate = InstanceTemplate.builder()
              .forMachineType(hardware.getUri());

      if (options.isEnableNat()) {
         instanceTemplate.addNetworkInterface(options.getNetwork().get(), Type.ONE_TO_ONE_NAT);
      } else {
         instanceTemplate.addNetworkInterface(options.getNetwork().get());
      }

      instanceTemplate.disks(disks);

      LoginCredentials credentials = getFromImageAndOverrideIfRequired(template.getImage(), options);

      ImmutableMap.Builder metadataBuilder = metatadaFromTemplateOptions.apply(options);

      metadataBuilder.put(GCE_IMAGE_METADATA_KEY, template.getImage().getUri().toString());

      if (!options.shouldKeepBootDisk()) {
         metadataBuilder.put(GCE_DELETE_BOOT_DISK_METADATA_KEY, Boolean.TRUE.toString());
      }

      instanceTemplate.metadata(metadataBuilder.build());
      instanceTemplate.serviceAccounts(options.getServiceAccounts());

      final InstanceApi instanceApi = api.getInstanceApiForProject(userProject.get());
      final String zone = template.getLocation().getId();
      Operation operation = instanceApi.createInZone(name, zone, instanceTemplate);

      if (options.shouldBlockUntilRunning()) {
         waitOperationDone(operation);
      }

      // some times the newly created instances are not immediately returned
      AtomicReference instance = Atomics.newReference();

      retry(new Predicate>() {
         @Override
         public boolean apply(AtomicReference input) {
            input.set(instanceApi.getInZone(zone, name));
            return input.get() != null;
         }
      }, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS).apply(instance);

      if (!options.getTags().isEmpty()) {
         Operation tagsOperation = instanceApi.setTagsInZone(zone,
                 name, options.getTags(), instance.get().getTags().getFingerprint());

         waitOperationDone(tagsOperation);

         retry(new Predicate>() {
            @Override
            public boolean apply(AtomicReference input) {
               input.set(instanceApi.getInZone(zone, name));
               return input.get() != null;
            }
         }, operationCompleteCheckTimeout, operationCompleteCheckInterval, MILLISECONDS).apply(instance);
      }

      // Add tags for security groups
      final FirewallTagNamingConvention naming = firewallTagNamingConvention.get(group);
      Set tags = FluentIterable.from(Ints.asList(options.getInboundPorts()))
              .transform(new Function(){
                       @Override
                       public String apply(Integer input) {
                          return input != null
                                  ? naming.name(input)
                                  : null;
                       }
                    })
              .toSet();
      instanceApi.setTagsInZone(zone, instance.get().getName(), tags, instance.get().getTags().getFingerprint());

      InstanceInZone instanceInZone = new InstanceInZone(instance.get(), zone);

      return new NodeAndInitialCredentials(instanceInZone, instanceInZone.slashEncode(), credentials);
   }

   private Disk createBootDisk(Template template, String instanceName) {
      URI imageUri = template.getImage().getUri();

      GoogleComputeEngineTemplateOptions options = GoogleComputeEngineTemplateOptions.class.cast(template.getOptions()).clone();

      int diskSize = options.getBootDiskSize().or(10l).intValue();

      String diskName = instanceName + "-" + GCE_BOOT_DISK_SUFFIX;

      Operation diskOperation = api.getDiskApiForProject(userProject.get())
                                   .createFromImageWithSizeInZone(imageUri.toString(),
                                                                  diskName,
                                                                  diskSize,
                                                                  template.getLocation().getId());

      waitOperationDone(diskOperation);

      return api.getDiskApiForProject(userProject.get()).getInZone(template.getLocation().getId(),
                                                                   diskName);
   }

   @Override
   public Iterable listHardwareProfiles() {
      ImmutableSet.Builder builder = ImmutableSet.builder();

      for (final Location zone : zones.get().values()) {
         builder.addAll(api.getMachineTypeApiForProject(userProject.get())
                 .listInZone(zone.getId())
                 .concat()
                 .transform(new Function() {

                    @Override
                    public MachineTypeInZone apply(MachineType arg0) {
                       return new MachineTypeInZone(arg0, arg0.getZone());
                    }
                 }));
      }

      return builder.build();
   }

   @Override
   public Iterable listImages() {
      return ImmutableSet.builder()
              .addAll(api.getImageApiForProject(userProject.get()).list().concat())
              .addAll(api.getImageApiForProject(DEBIAN_PROJECT).list().concat())
              .addAll(api.getImageApiForProject(CENTOS_PROJECT).list().concat())
              .build();
   }

   @Override
   public Image getImage(String id) {
      return Objects.firstNonNull(api.getImageApiForProject(userProject.get()).get(id),
                                  Objects.firstNonNull(api.getImageApiForProject(DEBIAN_PROJECT).get(id),
                                                       api.getImageApiForProject(CENTOS_PROJECT).get(id)));

   }

   @Override
   public Iterable listLocations() {
      return api.getZoneApiForProject(userProject.get()).list().concat();
   }

   @Override
   public InstanceInZone getNode(String name) {
      SlashEncodedIds slashEncodedIds = SlashEncodedIds.fromSlashEncoded(name);

      Instance instance= api.getInstanceApiForProject(userProject.get()).getInZone(slashEncodedIds.getFirstId(),
              slashEncodedIds.getSecondId());

      return instance == null ?  null : new InstanceInZone(instance, slashEncodedIds.getFirstId());
   }

   @Override
   public Iterable listNodes() {
      return FluentIterable.from(zones.get().values()).transformAndConcat(new Function>() {
         @Override
         public ImmutableSet apply(final Location input) {
            return api.getInstanceApiForProject(userProject.get()).listInZone(input.getId()).concat()
                    .transform(new Function() {

                       @Override
                       public InstanceInZone apply(Instance arg0) {
                          return new InstanceInZone(arg0, input.getId());
                       }
                    }).toSet();
         }
      }).toSet();
   }

   @Override
   public Iterable listNodesByIds(final Iterable ids) {
      return filter(listNodes(), new Predicate() {

         @Override
         public boolean apply(InstanceInZone instanceInZone) {
            return contains(ids, instanceInZone.getInstance().getName());
         }
      });
   }

   @Override
   public void destroyNode(final String name) {
      SlashEncodedIds slashEncodedIds = SlashEncodedIds.fromSlashEncoded(name);
      String diskName = null;
      try {
         Instance instance = api.getInstanceApiForProject(userProject.get()).getInZone(slashEncodedIds.getFirstId(),
                                                                              slashEncodedIds.getSecondId());
         if (instance.getMetadata().getItems().get(GCE_DELETE_BOOT_DISK_METADATA_KEY).equals("true")) {
            Optional disk = tryFind(instance.getDisks(), new Predicate() {
               @Override
               public boolean apply(AttachedDisk input) {
                  return PersistentAttachedDisk.class.isInstance(input) &&
                         PersistentAttachedDisk.class.cast(input).isBoot();
               }
            });
            if (disk.isPresent()) {
               diskName = PersistentAttachedDisk.class.cast(disk.get()).getSourceDiskName();
            }
         }
      } catch (Exception e) {
         // TODO: what exception actually gets thrown here if the instance doesn't really exist?
      }
      waitOperationDone(api.getInstanceApiForProject(userProject.get()).deleteInZone(slashEncodedIds.getFirstId(),
              slashEncodedIds.getSecondId()));

      if (diskName != null) {
         waitOperationDone(api.getDiskApiForProject(userProject.get()).deleteInZone(slashEncodedIds.getFirstId(),
                                                                                    diskName));
      }

   }

   @Override
   public void rebootNode(final String name) {
      SlashEncodedIds slashEncodedIds = SlashEncodedIds.fromSlashEncoded(name);

      waitOperationDone(api.getInstanceApiForProject(userProject.get()).resetInZone(slashEncodedIds.getFirstId(),
              slashEncodedIds.getSecondId()));
   }

   @Override
   public void resumeNode(String name) {
      throw new UnsupportedOperationException("resume is not supported by GCE");
   }

   @Override
   public void suspendNode(String name) {
      throw new UnsupportedOperationException("suspend is not supported by GCE");
   }

   private LoginCredentials getFromImageAndOverrideIfRequired(org.jclouds.compute.domain.Image image,
                                                              GoogleComputeEngineTemplateOptions options) {
      LoginCredentials defaultCredentials = image.getDefaultCredentials();
      String[] keys = defaultCredentials.getPrivateKey().split(":");
      String publicKey = keys[0];
      String privateKey = keys[1];

      LoginCredentials.Builder credentialsBuilder = defaultCredentials.toBuilder();
      credentialsBuilder.privateKey(privateKey);

      // LoginCredentials from image stores the public key along with the private key in the privateKey field
      // @see GoogleComputePopulateDefaultLoginCredentialsForImageStrategy
      // so if options doesn't have a public key set we set it from the default
      if (options.getPublicKey() == null) {
         options.authorizePublicKey(publicKey);
      }
      if (options.hasLoginPrivateKeyOption()) {
         credentialsBuilder.privateKey(options.getPrivateKey());
      }
      if (options.getLoginUser() != null) {
         credentialsBuilder.identity(options.getLoginUser());
      }
      if (options.hasLoginPasswordOption()) {
         credentialsBuilder.password(options.getLoginPassword());
      }
      if (options.shouldAuthenticateSudo() != null) {
         credentialsBuilder.authenticateSudo(options.shouldAuthenticateSudo());
      }
      LoginCredentials credentials = credentialsBuilder.build();
      options.overrideLoginCredentials(credentials);
      return credentials;
   }

   private void waitOperationDone(Operation operation) {
      AtomicReference operationRef = Atomics.newReference(operation);

      // wait for the operation to complete
      if (!retryOperationDonePredicate.apply(operationRef)) {
         throw new UncheckedTimeoutException("operation did not reach DONE state" + operationRef.get());
      }

      // check if the operation failed
      if (operationRef.get().getHttpError().isPresent()) {
         HttpResponse response = operationRef.get().getHttpError().get();
         throw new IllegalStateException("operation failed. Http Error Code: " + response.getStatusCode() +
                 " HttpError: " + response.getMessage());
      }
   }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy