Package org.platformlayer.service.cloud.google.ops.compute

Source Code of org.platformlayer.service.cloud.google.ops.compute.GoogleComputeClient

package org.platformlayer.service.cloud.google.ops.compute;

import java.io.IOException;
import java.security.PublicKey;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.platformlayer.ExceptionUtils;
import org.platformlayer.core.model.Tag;
import org.platformlayer.images.model.DiskImageRecipe;
import org.platformlayer.images.model.OperatingSystemRecipe;
import org.platformlayer.ops.MachineCreationRequest;
import org.platformlayer.ops.OpsException;
import org.platformlayer.ops.machines.PlatformLayerHelpers;
import org.platformlayer.service.cloud.google.model.GoogleCloud;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fathomdb.crypto.OpenSshUtils;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.model.AccessConfig;
import com.google.api.services.compute.model.Firewall;
import com.google.api.services.compute.model.FirewallList;
import com.google.api.services.compute.model.Image;
import com.google.api.services.compute.model.ImageList;
import com.google.api.services.compute.model.Instance;
import com.google.api.services.compute.model.MachineType;
import com.google.api.services.compute.model.MachineTypeList;
import com.google.api.services.compute.model.Metadata;
import com.google.api.services.compute.model.Metadata.Items;
import com.google.api.services.compute.model.NetworkInterface;
import com.google.api.services.compute.model.Operation;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

public class GoogleComputeClient {

  private static final Logger log = LoggerFactory.getLogger(GoogleComputeClient.class);

  public static final String ZONE_US_EAST1_A = "us-east1-a";
  public static final String ZONE_US_CENTRAL1_A = "us-central1-a";

  public static final String USER_NAME = "platfomlayer";

  public static final String PROJECTID_GOOGLE = "google";

  final Compute compute;
  final String projectId;

  final PlatformLayerHelpers platformLayerClient;

  public GoogleComputeClient(PlatformLayerHelpers platformLayerClient, Compute compute, String projectId) {
    this.platformLayerClient = platformLayerClient;
    this.compute = compute;
    this.projectId = projectId;
  }

  public List<Firewall> getInstanceFirewallRules(String instanceUrl) throws OpsException {
    List<Firewall> ret = Lists.newArrayList();

    FirewallList firewalls;
    try {
      log.debug("Listing firewall rules");
      firewalls = compute.firewalls().list(projectId).execute();
    } catch (IOException e) {
      throw new OpsException("Error listing firewalls", e);
    }

    // TODO: Use filter

    if (firewalls.getItems() != null) {
      for (Firewall firewall : firewalls.getItems()) {
        if (firewall.getTargetTags() != null && firewall.getTargetTags().contains(instanceUrl)) {
          ret.add(firewall);
        }
      }
    }

    return ret;
  }

  public void createFirewallRule(Firewall rule) throws OpsException {
    try {
      log.debug("Inserting firewall rule: " + rule);
      Operation operation = compute.firewalls().insert(projectId, rule).execute();
      waitComplete(operation, 5, TimeUnit.MINUTES);
      // TODO: Check success of operation?
    } catch (IOException e) {
      throw new OpsException("Error creating firewall", e);
    } catch (TimeoutException e) {
      throw new OpsException("Timeout while waiting for firewall creation", e);
    }
  }

  public void deleteFirewallRule(Firewall rule) throws OpsException {
    try {
      log.debug("Deleting firewall rule: " + rule);
      Operation operation = compute.firewalls().delete(projectId, rule.getName()).execute();
      waitComplete(operation, 5, TimeUnit.MINUTES);
      // TODO: Check success of operation?
    } catch (IOException e) {
      throw new OpsException("Error deleting firewall", e);
    } catch (TimeoutException e) {
      throw new OpsException("Timeout while waiting for firewall deletion", e);
    }
  }

  public Operation waitComplete(Operation operation, int duration, TimeUnit unit) throws TimeoutException,
      OpsException {
    long timeoutAt = System.currentTimeMillis() + unit.toMillis(duration);

    // TODO: Timeout?
    while (operation.getStatus().equals("RUNNING")) {
      if (timeoutAt < System.currentTimeMillis()) {
        throw new TimeoutException("Timeout while waiting for operation to complete");
      }
      log.debug("Polling for operation completion: " + operation);
      try {
        operation = compute.operations().get(projectId, operation.getName()).execute();
      } catch (IOException e) {
        throw new OpsException("Error waiting for operation to complete", e);
      }

      try {
        Thread.sleep(2000);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new OpsException("Interrupted while waiting for operation to complete", e);
      }
    }

    return operation;
  }

  public static String buildNetworkUrl(String projectId, String networkId) {
    return Compute.DEFAULT_BASE_URL + projectId + "/networks/" + networkId;
  }

  public String buildNetworkUrl(String networkId) {
    return buildNetworkUrl(projectId, networkId);
  }

  public static String buildZoneUrl(String projectId, String zoneId) {
    return Compute.DEFAULT_BASE_URL + projectId + "/zones/" + zoneId;
  }

  public Instance createInstance(GoogleCloud cloud, MachineCreationRequest request, PublicKey sshPublicKey)
      throws OpsException {
    // GoogleComputeClient computeClient = getComputeClient(cloud);

    try {
      Image foundImage = null;

      {
        DiskImageRecipe recipe = null;
        if (request.recipeId != null) {
          recipe = platformLayerClient.getItem(request.recipeId, DiskImageRecipe.class);
        }

        OperatingSystemRecipe operatingSystem = null;
        if (recipe != null) {
          operatingSystem = recipe.getOperatingSystem();
        }

        log.info("Listing images to pick best image");
        Iterable<Image> images = listImages(PROJECTID_GOOGLE);

        // TODO: We need a better solution here!!
        log.warn("Hard coding image names");
        Set<String> imageNames = Sets.newHashSet("ubuntu-12-04-v20120621");
        // Set<String> imageNames = Sets.newHashSet("centos-6-2-v20120621");

        for (Image image : images) {
          if (imageNames.contains(image.getName())) {
            foundImage = image;
            break;
          }
        }

        if (foundImage == null) {
          throw new IllegalArgumentException("Could not find image");
        }
      }

      // GCE requires that the name comply with RFC1035, which I think means a valid DNS
      // For now, just use a UUID, with a pl- prefix so it doesn't start with a number
      // TODO: Fix this!
      String instanceName = "pl-" + UUID.randomUUID().toString();

      Operation createServerOperation;
      {
        Instance create = new Instance();

        create.setName(instanceName);

        create.setZone(buildZoneUrl(projectId, ZONE_US_CENTRAL1_A));

        {
          NetworkInterface networkInterface = new NetworkInterface();
          networkInterface.setNetwork(buildNetworkUrl(projectId, "default"));

          AccessConfig networkAccessConfig = new AccessConfig();
          networkAccessConfig.setType("ONE_TO_ONE_NAT");

          networkInterface.setAccessConfigs(Lists.newArrayList(networkAccessConfig));

          create.setNetworkInterfaces(Lists.newArrayList(networkInterface));
        }

        Metadata metadata = new Metadata();
        metadata.setItems(Lists.<Items> newArrayList());
        create.setMetadata(metadata);

        if (request.tags != null) {
          for (Tag tag : request.tags) {
            Metadata.Items meta = new Metadata.Items();
            meta.setKey(tag.getKey());
            meta.setValue(tag.getValue());
            metadata.getItems().add(meta);
          }
        }

        if (request.sshPublicKey != null) {
          Metadata.Items meta = new Metadata.Items();
          meta.setKey("sshKeys");
          meta.setValue(USER_NAME + ":" + OpenSshUtils.serialize(sshPublicKey));

          metadata.getItems().add(meta);
        }

        create.setImage(foundImage.getSelfLink());

        MachineType flavor = getClosestInstanceType(request);
        if (flavor == null) {
          throw new OpsException("Cannot determine machine type for request");
        }
        create.setMachineType(flavor.getSelfLink());

        if (request.securityGroups != null) {
          // TODO: Reimplement if needed
          throw new UnsupportedOperationException();
        }

        // if (createdSecurityGroup != null) {
        // ServerForCreate.SecurityGroup serverSecurityGroup = new ServerForCreate.SecurityGroup();
        // serverSecurityGroup.setName(createdSecurityGroup.getName());
        // create.getSecurityGroups().add(serverSecurityGroup);
        // }

        // create.setConfigDrive(cloudBehaviours.useConfigDrive());

        log.info("Launching new server: " + instanceName);
        try {
          createServerOperation = compute.instances().insert(projectId, create).execute();
        } catch (IOException e) {
          throw new OpsException("Error launching new instance", e);
        }
      }

      log.info("Waiting for server to be ready");
      createServerOperation = waitComplete(createServerOperation, 10, TimeUnit.MINUTES);

      Instance created;

      InstanceState state = null;
      while (true) {
        created = findInstanceByName(instanceName);

        state = InstanceState.get(created);
        log.info("Instance state: " + state);

        if (state.isRunning()) {
          break;
        }

        Thread.sleep(1000);
      }

      return created;
    } catch (InterruptedException e) {
      ExceptionUtils.handleInterrupted(e);
      throw new OpsException("Error building server", e);
    } catch (TimeoutException e) {
      throw new OpsException("Timeout waiting for server build", e);
    }
  }

  private Iterable<Image> listImages(String projectId) throws OpsException {
    List<Image> ret = Lists.newArrayList();

    ImageList imageList;
    try {
      log.debug("Listing images in project " + projectId);
      imageList = compute.images().list(projectId).execute();
    } catch (IOException e) {
      throw new OpsException("Error listing images", e);
    }
    if (imageList.getItems() != null) {
      ret.addAll(imageList.getItems());
    }

    return ret;
  }

  private Iterable<MachineType> listMachineTypes(String projectId) throws OpsException {
    List<MachineType> ret = Lists.newArrayList();

    MachineTypeList machineTypeList;
    try {
      log.debug("Listing machine types in project " + projectId);
      machineTypeList = compute.machineTypes().list(projectId).execute();
    } catch (IOException e) {
      throw new OpsException("Error listing machine types", e);
    }
    if (machineTypeList.getItems() != null) {
      ret.addAll(machineTypeList.getItems());
    }

    return ret;
  }

  private MachineType getClosestInstanceType(MachineCreationRequest request) throws OpsException {
    log.info("Listing machine types to find best size");

    Iterable<MachineType> flavors = listMachineTypes(projectId);
    List<MachineType> candidates = Lists.newArrayList();
    for (MachineType flavor : flavors) {
      // Ignore the machine types with no ephemeral disks - they're the same price
      // TODO: Is this right?
      if (flavor.getEphemeralDisks() == null || flavor.getEphemeralDisks().isEmpty()) {
        continue;
      }
      int instanceMemoryMB = flavor.getMemoryMb();
      if (request.minimumMemoryMB > instanceMemoryMB) {
        continue;
      }

      candidates.add(flavor);
    }

    MachineType bestFlavor = findCheapest(candidates);
    if (bestFlavor == null) {
      return null;
    }

    return bestFlavor;
  }

  private MachineType findCheapest(List<MachineType> candidates) {
    MachineType bestFlavor = null;
    float bestPrice = Float.MAX_VALUE;

    for (MachineType candidate : candidates) {
      float price = computePrice(candidate);
      if (price < bestPrice) {
        bestFlavor = candidate;
        bestPrice = price;
      }
    }
    return bestFlavor;
  }

  private float computePrice(MachineType flavor) {
    // We compute the per-hour price so we can choose the smallest/cheapest instance size

    float price = 0;

    int cpus = flavor.getGuestCpus() != null ? flavor.getGuestCpus() : 0;
    int memoryMb = flavor.getMemoryMb() != null ? flavor.getMemoryMb() : 0;

    // // RAM is $0.10 / hour / GB
    // price += (0.10 / 1024.0) * ram;

    // // Disk is $0.10 / hour / TB
    // int disk = flavor.getDisk();
    // price += (0.10 / 1024.0) * disk;

    // CPUs are $0.145 / hour / vCPU
    price += 0.145 * cpus;

    return price;
  }

  public static List<String> findPublicIps(Instance instance) {
    List<String> ips = Lists.newArrayList();

    List<NetworkInterface> networkInterfaces = instance.getNetworkInterfaces();
    if (networkInterfaces == null) {
      networkInterfaces = Collections.emptyList();
    }

    for (NetworkInterface networkInterface : networkInterfaces) {
      List<AccessConfig> accessConfigList = networkInterface.getAccessConfigs();
      if (accessConfigList == null) {
        continue;
      }

      for (AccessConfig accessConfig : accessConfigList) {
        if (!Objects.equal(accessConfig.getType(), "ONE_TO_ONE_NAT")) {
          throw new IllegalStateException();
        }

        String natIp = accessConfig.getNatIP();
        if (!Strings.isNullOrEmpty(natIp)) {
          ips.add(natIp);
        }
      }
    }

    return ips;
  }

  public Instance findInstanceByName(String name) throws OpsException {
    try {
      log.debug("Retrieving instance by name: " + name);
      Instance instance = compute.instances().get(projectId, name).execute();
      return instance;
    } catch (GoogleJsonResponseException e) {
      if (e.getStatusCode() == 404) {
        return null;
      }
      throw new OpsException("Error getting instance", e);
    } catch (IOException e) {
      throw new OpsException("Error getting instance", e);
    }
  }

  public Instance ensureHasPublicIp(Instance instance) throws OpsException {
    List<String> publicIps = findPublicIps(instance);

    if (!publicIps.isEmpty()) {
      return instance;
    }

    throw new UnsupportedOperationException();
    //
    //
    // final OpenstackComputeClient compute = getComputeClient(cloud);
    //
    // log.info("Creating floating IP");
    // FloatingIp floatingIp = compute.root().floatingIps().create();
    //
    // // TODO: Don't abandon the IP e.g. if the attach fails
    // log.info("Attching floating IP " + floatingIp.getIp() + " to " + server.getId());
    // compute.root().servers().server(server.getId()).addFloatingIp(floatingIp.getIp());
    //
    // final String serverId = server.getId();
    //
    // try {
    // server = TimeoutPoll.poll(TimeSpan.FIVE_MINUTES, TimeSpan.TEN_SECONDS, new PollFunction<Server>() {
    // @Override
    // public Server call() throws Exception {
    // log.info("Waiting for floating IP attach; polling server: " + serverId);
    // Server server = compute.root().servers().server(serverId).show();
    //
    // List<Ip> publicIps = helpers.findPublicIps(cloud, server);
    // if (publicIps.isEmpty()) {
    // return null;
    // }
    // return server;
    // }
    // });
    // } catch (TimeoutException e) {
    // throw new OpsException("Timeout while waiting for attached public IP to show up", e);
    // } catch (ExecutionException e) {
    // throw new OpsException("Error while waiting for attached public IP to show up", e);
    // }
    //
    // return server;
  }

  public Operation terminateInstance(String instanceId) throws OpsException {
    try {
      log.debug("Terminating instance: " + instanceId);

      Operation operation = compute.instances().delete(projectId, instanceId).execute();
      return operation;
    } catch (IOException e) {
      throw new OpsException("Error deleting instance", e);
    }
  }

}
TOP

Related Classes of org.platformlayer.service.cloud.google.ops.compute.GoogleComputeClient

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.