/**
* Copyright (C) 2012-2015 Dell, Inc
* See annotations for authorship information
*
* ====================================================================
* Licensed 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.dasein.cloud.google.compute.server;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.log4j.Logger;
import org.dasein.cloud.CloudErrorType;
import org.dasein.cloud.CloudException;
import org.dasein.cloud.InternalException;
import org.dasein.cloud.OperationNotSupportedException;
import org.dasein.cloud.ResourceStatus;
import org.dasein.cloud.Tag;
import org.dasein.cloud.VisibleScope;
import org.dasein.cloud.compute.AbstractVMSupport;
import org.dasein.cloud.compute.Architecture;
import org.dasein.cloud.compute.MachineImage;
import org.dasein.cloud.compute.Platform;
import org.dasein.cloud.compute.VMFilterOptions;
import org.dasein.cloud.compute.VMLaunchOptions;
import org.dasein.cloud.compute.VMScalingOptions;
import org.dasein.cloud.compute.VirtualMachine;
import org.dasein.cloud.compute.VirtualMachineProduct;
import org.dasein.cloud.compute.VirtualMachineProductFilterOptions;
import org.dasein.cloud.compute.VmState;
import org.dasein.cloud.compute.VolumeAttachment;
import org.dasein.cloud.compute.VolumeCreateOptions;
import org.dasein.cloud.google.GoogleOperationType;
import org.dasein.cloud.google.Google;
import org.dasein.cloud.google.GoogleException;
import org.dasein.cloud.google.GoogleMethod;
import org.dasein.cloud.google.capabilities.GCEInstanceCapabilities;
import org.dasein.cloud.network.RawAddress;
import org.dasein.cloud.network.VLAN;
import org.dasein.cloud.util.APITrace;
import org.dasein.cloud.util.Cache;
import org.dasein.cloud.util.CacheLevel;
import org.dasein.cloud.util.NamingConstraints;
import org.dasein.util.Jiterator;
import org.dasein.util.JiteratorPopulator;
import org.dasein.util.PopulatorThread;
import org.dasein.util.uom.storage.Gigabyte;
import org.dasein.util.uom.storage.Megabyte;
import org.dasein.util.uom.storage.Storage;
import org.dasein.util.uom.time.Day;
import org.dasein.util.uom.time.TimePeriod;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
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.AttachedDisk;
import com.google.api.services.compute.model.AttachedDiskInitializeParams;
import com.google.api.services.compute.model.Disk;
import com.google.api.services.compute.model.Image;
import com.google.api.services.compute.model.Instance;
import com.google.api.services.compute.model.InstanceAggregatedList;
import com.google.api.services.compute.model.MachineType;
import com.google.api.services.compute.model.MachineTypeAggregatedList;
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.api.services.compute.model.Scheduling;
import com.google.api.services.compute.model.SerialPortOutput;
import com.google.api.services.compute.model.Tags;
public class ServerSupport extends AbstractVMSupport<Google> {
private Google provider;
static private final Logger logger = Google.getLogger(ServerSupport.class);
private Cache<MachineTypeAggregatedList> machineTypesCache;
public ServerSupport(Google provider){
super(provider);
this.provider = provider;
machineTypesCache = Cache.getInstance(provider, "MachineTypes", MachineTypeAggregatedList.class, CacheLevel.CLOUD, new TimePeriod<Day>(1, TimePeriod.DAY));
}
@Override
public VirtualMachine alterVirtualMachine(@Nonnull String vmId, @Nonnull VMScalingOptions options) throws InternalException, CloudException {
throw new OperationNotSupportedException("GCE does not support altering of existing instances.");
}
@Override
public VirtualMachine modifyInstance(@Nonnull String vmId, @Nonnull String[] firewalls) throws InternalException, CloudException{
throw new OperationNotSupportedException("GCE does not support altering of existing instances.");
}
@Override
public @Nonnull VirtualMachine clone(@Nonnull String vmId, @Nonnull String intoDcId, @Nonnull String name, @Nonnull String description, boolean powerOn, String... firewallIds) throws InternalException, CloudException {
throw new OperationNotSupportedException("GCE does not support cloning of instances via the API.");
}
@Override
public void disableAnalytics(@Nonnull String vmId) throws InternalException, CloudException {
throw new OperationNotSupportedException("GCE does not currently support analytics.");
}
@Override
public void enableAnalytics(@Nonnull String vmId) throws InternalException, CloudException {
throw new OperationNotSupportedException("GCE does not currently support analytics.");
}
private transient volatile GCEInstanceCapabilities capabilities;
@Override
public @Nonnull GCEInstanceCapabilities getCapabilities(){
if( capabilities == null ) {
capabilities = new GCEInstanceCapabilities(provider);
}
return capabilities;
}
@Override
public String getPassword(@Nonnull String vmId) throws InternalException, CloudException {
throw new OperationNotSupportedException("GCE instances do not have passwords");
}
public @Nonnull String getVmNameFromId(String vmId) throws InternalException, CloudException {
if (null == vmId) {
throw new InternalException("vmId cannot be null");
}
if (vmId.contains("_")) {
String[] parts = vmId.split("_");
if (null == parts[0]) {
throw new InternalException("vmId cannot begin with '_'");
}
return parts[0];
} else {
return vmId;
}
}
public @Nonnull String getVmIdFromName(String vmName) throws InternalException, CloudException {
if (null == vmName) {
throw new InternalException("vmName cannot be null ");
}
VirtualMachine vm = getVirtualMachine(vmName);
if ((null != vm) && (null != vm.getProviderVirtualMachineId())) {
return vm.getProviderVirtualMachineId();
} else {
throw new CloudException("Unable to lookup vmId for vm named: " + vmName);
}
}
@Override
public @Nonnull String getConsoleOutput(@Nonnull String vmId) throws InternalException, CloudException {
try{
for(VirtualMachine vm : listVirtualMachines()){
if(vm.getProviderVirtualMachineId().equalsIgnoreCase(vmId)){
Compute gce = provider.getGoogleCompute();
SerialPortOutput output = gce.instances().getSerialPortOutput(provider.getContext().getAccountNumber(), vm.getProviderDataCenterId(), getVmNameFromId(vmId)).execute();
return output.getContents();
}
}
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred when getting console output for VM: " + vmId + ": " + ex.getMessage());
}
throw new InternalException("The Virtual Machine: " + vmId + " could not be found.");
}
@Override
public VirtualMachineProduct getProduct(@Nonnull String productId) throws InternalException, CloudException {
try{
Compute gce = provider.getGoogleCompute();
String[] parts = productId.split("\\+");
if ((parts != null) && (parts.length > 1)) {
MachineTypeList types = gce.machineTypes().list(provider.getContext().getAccountNumber(), parts[1]).setFilter("name eq " + parts[0]).execute();
if ((null != types) && (null != types.getItems())) {
for(MachineType type : types.getItems()){
if(parts[0].equals(type.getName()))return toProduct(type);
}
}
}
return null; // Tests indicate null should come back, rather than exception
//throw new CloudException("The product: " + productId + " could not be found.");
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred retrieving the product: " + productId + ": " + ex.getMessage());
}
}
@Override
public VirtualMachine getVirtualMachine(@Nonnull String vmId)throws InternalException, CloudException {
APITrace.begin(getProvider(), "getVirtualMachine");
try{
try{
Compute gce = provider.getGoogleCompute();
InstanceAggregatedList instances = gce.instances().aggregatedList(provider.getContext().getAccountNumber()).setFilter("name eq " + getVmNameFromId(vmId)).execute();
Iterator<String> it = instances.getItems().keySet().iterator();
while (it.hasNext()){
String zone = it.next();
if(instances.getItems() != null && instances.getItems().get(zone) != null && instances.getItems().get(zone).getInstances() != null){
for(Instance instance : instances.getItems().get(zone).getInstances()){
if(instance.getName().equals(getVmNameFromId(vmId)))return toVirtualMachine(instance);
}
}
}
return null; // not found
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred retrieving VM: " + vmId + ": " + ex.getMessage());
}
}
finally {
APITrace.end();
}
}
@Override
public boolean isSubscribed() throws CloudException, InternalException {
listVirtualMachines();
return true;
}
public void validateLaunchOptions(@Nonnull VMLaunchOptions withLaunchOptions) throws CloudException, InternalException {
if (withLaunchOptions.getDataCenterId() == null || withLaunchOptions.getDataCenterId().equals("")) {
throw new InternalException("A datacenter must be specified when launching an instance");
}
if (withLaunchOptions.getMachineImageId() == null || withLaunchOptions.getMachineImageId().equals("")) {
throw new InternalException("A MachineImage must be specified when launching an instance");
}
if (withLaunchOptions.getHostName() == null || withLaunchOptions.getHostName().equals("")) {
throw new InternalException("A hostname must be specified when launching an instance");
}
if (withLaunchOptions.getVlanId().equals("")) {
throw new InternalException("A vlan must be specified when launching an instance");
} else {
VLAN vlan = provider.getNetworkServices().getVlanSupport().getVlan(withLaunchOptions.getVlanId());
if ((null == vlan) || (null == vlan.getTag("contentLink"))) {
throw new InternalException("Problem getting Vlan for " + withLaunchOptions.getVlanId());
}
}
String hostName = getCapabilities().getVirtualMachineNamingConstraints().convertToValidName(withLaunchOptions.getHostName(), Locale.US);
if (null != provider.getComputeServices().getVolumeSupport().getVolume(hostName)) {
throw new InternalException("Root disk " + hostName + " already exists.");
}
}
@Override
public @Nonnull VirtualMachine launch(@Nonnull VMLaunchOptions withLaunchOptions)throws CloudException, InternalException {
APITrace.begin(getProvider(), "launchVM");
validateLaunchOptions(withLaunchOptions); // this will exception out on problem.
try{
Compute gce = provider.getGoogleCompute();
GoogleMethod method = new GoogleMethod(provider);
String hostName = getCapabilities().getVirtualMachineNamingConstraints().convertToValidName(withLaunchOptions.getHostName(), Locale.US);
Instance instance = new Instance();
instance.setName(hostName);
instance.setDescription(withLaunchOptions.getDescription());
if (withLaunchOptions.getStandardProductId().contains("+")) {
instance.setMachineType(getProduct(withLaunchOptions.getStandardProductId()).getDescription());
} else {
instance.setMachineType(getProduct(withLaunchOptions.getStandardProductId() + "+" + withLaunchOptions.getDataCenterId()).getDescription());
}
MachineImage image = provider.getComputeServices().getImageSupport().getImage(withLaunchOptions.getMachineImageId());
AttachedDisk rootVolume = new AttachedDisk();
rootVolume.setBoot(Boolean.TRUE);
rootVolume.setType("PERSISTENT");
rootVolume.setMode("READ_WRITE");
AttachedDiskInitializeParams params = new AttachedDiskInitializeParams();
// do not use withLaunchOptions.getFriendlyName() it is non compliant!!!
params.setDiskName(hostName);
// Not Optimum solution, update in core should come next release to have this be part of MachineImage
try {
String[] parts = withLaunchOptions.getMachineImageId().split("_");
Image img = gce.images().get(parts[0], parts[1]).execute();
Long size = img.getDiskSizeGb();
String diskSizeGb = size.toString();
if (null == diskSizeGb) {
diskSizeGb = img.getUnknownKeys().get("diskSizeGb").toString();
}
Long MinimumDiskSizeGb = Long.valueOf(diskSizeGb).longValue();
params.setDiskSizeGb(MinimumDiskSizeGb);
} catch ( Exception e ) {
params.setDiskSizeGb(10L);
}
if ((image != null) && (image.getTag("contentLink") != null))
params.setSourceImage((String)image.getTag("contentLink"));
else
throw new CloudException("Problem getting the contentLink tag value from the image for " + withLaunchOptions.getMachineImageId());
rootVolume.setInitializeParams(params);
List<AttachedDisk> attachedDisks = new ArrayList<AttachedDisk>();
attachedDisks.add(rootVolume);
if (withLaunchOptions.getVolumes().length > 0) {
for (VolumeAttachment volume : withLaunchOptions.getVolumes()) {
AttachedDisk vol = new AttachedDisk();
vol.setBoot(Boolean.FALSE);
vol.setType("PERSISTENT");
vol.setMode("READ_WRITE");
vol.setAutoDelete(Boolean.FALSE);
vol.setKind("compute#attachedDisk");
if (null != volume.getExistingVolumeId()) {
vol.setDeviceName(volume.getExistingVolumeId());
vol.setSource(provider.getComputeServices().getVolumeSupport().getVolume(volume.getExistingVolumeId()).getMediaLink());
} else {
VolumeCreateOptions volumeOptions = volume.getVolumeToCreate();
volumeOptions.setDataCenterId(withLaunchOptions.getDataCenterId());
String newDisk = provider.getComputeServices().getVolumeSupport().createVolume(volume.getVolumeToCreate());
vol.setDeviceName(newDisk);
vol.setSource(provider.getComputeServices().getVolumeSupport().getVolume(newDisk).getMediaLink());
}
attachedDisks.add(vol);
}
}
instance.setDisks(attachedDisks);
AccessConfig nicConfig = new AccessConfig();
nicConfig.setName("External NAT");
nicConfig.setType("ONE_TO_ONE_NAT");//Currently the only type supported
if (withLaunchOptions.getStaticIpIds().length > 0) {
nicConfig.setNatIP(withLaunchOptions.getStaticIpIds()[0]);
}
List<AccessConfig> accessConfigs = new ArrayList<AccessConfig>();
accessConfigs.add(nicConfig);
NetworkInterface nic = new NetworkInterface();
nic.setName("nic0");
if (null != withLaunchOptions.getVlanId()) {
VLAN vlan = provider.getNetworkServices().getVlanSupport().getVlan(withLaunchOptions.getVlanId());
nic.setNetwork(vlan.getTag("contentLink"));
} else {
nic.setNetwork(provider.getNetworkServices().getVlanSupport().getVlan("default").getTag("contentLink"));
}
nic.setAccessConfigs(accessConfigs);
List<NetworkInterface> nics = new ArrayList<NetworkInterface>();
nics.add(nic);
instance.setNetworkInterfaces(nics);
instance.setCanIpForward(Boolean.FALSE);
Scheduling scheduling = new Scheduling();
scheduling.setAutomaticRestart(Boolean.TRUE);
scheduling.setOnHostMaintenance("TERMINATE");
instance.setScheduling(scheduling);
Map<String,String> keyValues = new HashMap<String, String>();
if(withLaunchOptions.getBootstrapUser() != null && withLaunchOptions.getBootstrapKey() != null && !withLaunchOptions.getBootstrapUser().equals("") && !withLaunchOptions.getBootstrapKey().equals("")){
keyValues.put("sshKeys", withLaunchOptions.getBootstrapUser() + ":" + withLaunchOptions.getBootstrapKey());
}
if(!withLaunchOptions.getMetaData().isEmpty()) {
for( Map.Entry<String,Object> entry : withLaunchOptions.getMetaData().entrySet() ) {
keyValues.put(entry.getKey(), (String)entry.getValue());
}
}
if (!keyValues.isEmpty()) {
Metadata metadata = new Metadata();
ArrayList<Metadata.Items> items = new ArrayList<Metadata.Items>();
for (Map.Entry<String, String> entry : keyValues.entrySet()) {
Metadata.Items item = new Metadata.Items();
item.set("key", entry.getKey());
if ((entry.getValue() == null) || (entry.getValue().isEmpty() == true) || (entry.getValue().equals("")))
item.set("value", ""); // GCE HATES nulls...
else
item.set("value", entry.getValue());
items.add(item);
}
// https://github.com/GoogleCloudPlatform/compute-image-packages/tree/master/google-startup-scripts
if (null != withLaunchOptions.getUserData()) {
Metadata.Items item = new Metadata.Items();
item.set("key", "startup-script");
item.set("value", withLaunchOptions.getUserData());
items.add(item);
}
metadata.setItems(items);
instance.setMetadata(metadata);
}
Tags tags = new Tags();
ArrayList<String> tagItems = new ArrayList<String>();
tagItems.add(hostName); // Each tag must be 1-63 characters long, and comply with RFC1035
tags.setItems(tagItems);
instance.setTags(tags);
String vmId = "";
try{
Operation job = gce.instances().insert(provider.getContext().getAccountNumber(), withLaunchOptions.getDataCenterId(), instance).execute();
vmId = method.getOperationTarget(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION, "", withLaunchOptions.getDataCenterId(), false);
} catch (IOException ex) {
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred launching the instance: " + ex.getMessage());
} catch (Exception e) {
if ((e.getMessage().contains("The resource")) &&
(e.getMessage().contains("disks")) &&
(e.getMessage().contains("already exists"))) {
throw new CloudException("A disk named '" + withLaunchOptions.getFriendlyName() + "' already exists.");
} else {
throw new CloudException(e);
}
}
if(!vmId.equals("")){
return getVirtualMachine(vmId);
} else {
throw new CloudException("Could not find the instance: " + withLaunchOptions.getFriendlyName() + " after launch.");
}
}
finally {
APITrace.end();
}
}
@Override
public @Nonnull Iterable<String> listFirewalls(@Nonnull String vmId) throws InternalException, CloudException {
ArrayList<String> firewalls = new ArrayList<String>();
for(org.dasein.cloud.network.Firewall firewall : provider.getNetworkServices().getFirewallSupport().list()){
for(String key : firewall.getTags().keySet()){
if (firewall.getTags().get(key).equals(getVmNameFromId(vmId))) {
firewalls.add(firewall.getName());
}
}
}
return firewalls;
}
public @Nonnull Iterable<VirtualMachineProduct> listProducts(@Nonnull Architecture architecture, String preferredDataCenterId) throws InternalException, CloudException {
MachineTypeAggregatedList machineTypes = null;
Compute gce = provider.getGoogleCompute();
Iterable<MachineTypeAggregatedList> machineTypesCachedList = machineTypesCache.get(provider.getContext());
if (machineTypesCachedList != null) {
Iterator<MachineTypeAggregatedList> machineTypesCachedListIterator = machineTypesCachedList.iterator();
if (machineTypesCachedListIterator.hasNext())
machineTypes = machineTypesCachedListIterator.next();
} else {
try {
machineTypes = gce.machineTypes().aggregatedList(provider.getContext().getAccountNumber()).execute();
machineTypesCache.put(provider.getContext(), Arrays.asList(machineTypes));
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred listing VM products.");
}
}
Collection<VirtualMachineProduct> products = new ArrayList<VirtualMachineProduct>();
Iterator<String> it = machineTypes.getItems().keySet().iterator();
while(it.hasNext()){
Object dataCenterId = it.next();
if ((preferredDataCenterId == null) || (dataCenterId.toString().endsWith(preferredDataCenterId)))
for(MachineType type : machineTypes.getItems().get(dataCenterId).getMachineTypes()){
//TODO: Filter out deprecated states somehow
if ((preferredDataCenterId == null) || (type.getZone().equals(preferredDataCenterId))) {
VirtualMachineProduct product = toProduct(type);
products.add(product);
}
}
}
return products;
}
@Override
public @Nonnull Iterable<VirtualMachineProduct> listProducts(@Nonnull String machineImageId, @Nonnull VirtualMachineProductFilterOptions options) throws InternalException, CloudException{
Collection<VirtualMachineProduct> products = new ArrayList<VirtualMachineProduct>();
Iterable<VirtualMachineProduct> candidateProduct = listProducts(options, null);
for (VirtualMachineProduct product : candidateProduct) {
if (options == null || options.matches(product)) {
products.add(product);
}
}
return products;
}
@Override
public Iterable<VirtualMachineProduct> listProducts(VirtualMachineProductFilterOptions options, Architecture architecture) throws InternalException, CloudException{
if ((architecture == null) || (Architecture.I64 == architecture)) { // GCE only has I64 architecture
String dataCenterId = null;
if (options != null)
dataCenterId = options.getDataCenterId();
Iterable<VirtualMachineProduct> result = listProducts(Architecture.I64, dataCenterId);
return result;
} else
return new ArrayList<VirtualMachineProduct>(); // empty!
}
@Override
public @Nonnull Iterable<VirtualMachine> listVirtualMachines(VMFilterOptions options)throws InternalException, CloudException {
APITrace.begin(getProvider(), "listVirtualMachines");
try{
try{
ArrayList<VirtualMachine> vms = new ArrayList<VirtualMachine>();
Compute gce = provider.getGoogleCompute();
InstanceAggregatedList instances = gce.instances().aggregatedList(provider.getContext().getAccountNumber()).execute();
Iterator<String> it = instances.getItems().keySet().iterator();
while(it.hasNext()){
String zone = it.next();
if(getContext().getRegionId().equals(provider.getDataCenterServices().getRegionFromZone(zone))){
if(instances.getItems() != null && instances.getItems().get(zone) != null && instances.getItems().get(zone).getInstances() != null){
for(Instance instance : instances.getItems().get(zone).getInstances()){
VirtualMachine vm = toVirtualMachine(instance);
if (options == null || options.matches(vm)) {
vms.add(vm);
}
}
}
}
}
return vms;
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred while listing Virtual Machines.");
}
}
finally{
APITrace.end();
}
}
@Override
public @Nonnull Iterable<VirtualMachine> listVirtualMachines()throws InternalException, CloudException {
VMFilterOptions options = VMFilterOptions.getInstance();
return listVirtualMachines(options);
}
@Override
public @Nonnull Iterable<ResourceStatus> listVirtualMachineStatus() throws InternalException, CloudException {
ArrayList<ResourceStatus> vmStatuses = new ArrayList<ResourceStatus>();
for(VirtualMachine vm : listVirtualMachines()){
ResourceStatus status = new ResourceStatus(vm.getProviderVirtualMachineId(), vm.getCurrentState());
vmStatuses.add(status);
}
return vmStatuses;
}
@Override
public void pause(@Nonnull String vmId) throws InternalException, CloudException {
throw new OperationNotSupportedException("GCE does not support pausing vms.");
}
@Override
public void reboot(@Nonnull String vmId) throws CloudException, InternalException {
APITrace.begin(getProvider(), "rebootVM");
try{
try{
Operation job = null;
String zone = null;
for (VirtualMachine vm : listVirtualMachines()) {
if (vm.getProviderVirtualMachineId().equalsIgnoreCase(vmId)) {
zone = vm.getProviderDataCenterId();
Compute gce = provider.getGoogleCompute();
job = gce.instances().reset(provider.getContext().getAccountNumber(), vm.getProviderDataCenterId(), getVmNameFromId(vmId)).execute();
break;
}
}
if(job != null){
GoogleMethod method = new GoogleMethod(provider);
method.getOperationComplete(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION, null, zone);
}
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred while rebooting VM: " + vmId + ": " + ex.getMessage());
}
}
finally{
APITrace.end();
}
}
@Override
public void resume(@Nonnull String vmId) throws CloudException, InternalException {
throw new OperationNotSupportedException("GCE does not support suspend/resume of instances.");
}
@Override
public void suspend(@Nonnull String vmId) throws CloudException, InternalException {
throw new OperationNotSupportedException("GCE does not support suspend/resume of instances.");
}
@Override
public void start(@Nonnull String vmId) throws InternalException, CloudException {
Compute gce = provider.getGoogleCompute();
try {
VirtualMachine vm = getVirtualMachine(vmId);
gce.instances().start(provider.getContext().getAccountNumber(), vm.getProviderDataCenterId(), getVmNameFromId(vmId)).execute();
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred while rebooting VM: " + vmId + ": " + ex.getMessage());
}
}
@Override
public void stop(@Nonnull String vmId, boolean force) throws InternalException, CloudException {
Compute gce = provider.getGoogleCompute();
try {
VirtualMachine vm = getVirtualMachine(vmId);
gce.instances().stop(provider.getContext().getAccountNumber(), vm.getProviderDataCenterId(), getVmNameFromId(vmId)).execute();
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred while rebooting VM: " + vmId + ": " + ex.getMessage());
}
}
@Override
public void terminate(@Nonnull String vmId) throws InternalException, CloudException {
VirtualMachine vm = getVirtualMachine(vmId);
terminateVm(vmId);
terminateVmDisk(getVmNameFromId(vmId), vm.getProviderDataCenterId());
}
@Override
public void terminate(@Nonnull String vmId, String reason) throws InternalException, CloudException{
VirtualMachine vm = getVirtualMachine(vmId);
terminateVm(vmId, null);
terminateVmDisk(vmId, vm.getProviderDataCenterId());
}
public void terminateVm(@Nonnull String vmId) throws InternalException, CloudException {
terminateVm(vmId, null);
}
public void terminateVm(@Nonnull String vmId, String reason) throws InternalException, CloudException {
try {
APITrace.begin(getProvider(), "terminateVM");
Operation job = null;
GoogleMethod method = null;
String zone = null;
Compute gce = provider.getGoogleCompute();
VirtualMachine vm = getVirtualMachine(vmId);
if (null == vm) {
throw new CloudException("Virtual Machine " + vmId + " was not found.");
}
try {
zone = vm.getProviderDataCenterId();
job = gce.instances().delete(provider.getContext().getAccountNumber(), zone, getVmNameFromId(vmId)).execute();
if(job != null) {
method = new GoogleMethod(provider);
if (false == method.getOperationComplete(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION, null, zone)) {
throw new CloudException("An error occurred while terminating the VM. Note: The root disk might also still exist");
}
}
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred while terminating VM: " + vmId + ": " + ex.getMessage());
} catch (Exception ex) {
throw new CloudException(ex); // catch exception from getOperationComplete
}
} finally {
APITrace.end();
}
}
public void terminateVmDisk(@Nonnull String diskName, String zone) throws InternalException, CloudException {
try {
APITrace.begin(getProvider(), "terminateVM");
try {
Compute gce = provider.getGoogleCompute();
Operation job = gce.disks().delete(provider.getContext().getAccountNumber(), zone, diskName).execute();
GoogleMethod method = new GoogleMethod(provider);
method.getOperationComplete(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION, null, zone);
} catch (IOException ex) {
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
if ((404 == gjre.getStatusCode()) &&
(gjre.getStatusMessage().equals("Not Found"))) {
// remain silent. this happens when instance is created with delete root volume on terminate is selected.
//throw new CloudException("Virtual Machine disk image '" + vmId + "' was not found.");
} else {
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
}
} else
throw new CloudException("An error occurred while deleting VM disk: " + diskName + ": " + ex.getMessage());
} catch (Exception ex) {
throw new CloudException(ex); // catch exception from getOperationComplete
}
}
finally{
APITrace.end();
}
}
@Override
public void unpause(@Nonnull String vmId) throws CloudException, InternalException {
throw new OperationNotSupportedException("GCE does not support unpausing vms.");
}
@Override
public void updateTags(String vmId, Tag... tags) throws CloudException, InternalException {
updateTags(new String[]{vmId}, tags);
}
@Override
public void updateTags(String[] vmIds, Tag... tags) throws CloudException, InternalException {
//TODO: Implement me
}
@Override
public void removeTags(String vmId, Tag... tags) throws CloudException, InternalException {
throw new OperationNotSupportedException("Google does not support removing meta data from vms");
}
@Override
public void removeTags(String[] vmIds, Tag... tags) throws CloudException, InternalException {
throw new OperationNotSupportedException("Google does not support removing meta data from vms");
}
private VirtualMachine toVirtualMachine(Instance instance) throws InternalException, CloudException{
VirtualMachine vm = new VirtualMachine();
vm.setProviderVirtualMachineId(instance.getName() + "_" + instance.getId().toString());
vm.setName(instance.getName());
if (instance.getDescription() != null) {
vm.setDescription(instance.getDescription());
} else {
vm.setDescription(instance.getName());
}
vm.setProviderOwnerId(provider.getContext().getAccountNumber());
VmState vmState = null;
if (instance.getStatus().equalsIgnoreCase("provisioning") ||
instance.getStatus().equalsIgnoreCase("staging")) {
if ((null != instance.getStatusMessage()) && (instance.getStatusMessage().contains("failed"))) {
vmState = VmState.ERROR;
} else {
vmState = VmState.PENDING;
}
} else if (instance.getStatus().equalsIgnoreCase("stopping")) {
vmState = VmState.STOPPING;
} else if (instance.getStatus().equalsIgnoreCase("terminated")) {
vmState = VmState.STOPPED;
} else {
vmState = VmState.RUNNING;
}
vm.setCurrentState(vmState);
String regionId = "";
try {
regionId = provider.getDataCenterServices().getRegionFromZone(instance.getZone().substring(instance.getZone().lastIndexOf("/") + 1));
}
catch (Exception ex) {
logger.error("An error occurred getting the region for the instance");
return null;
}
vm.setProviderRegionId(regionId);
String zone = instance.getZone();
zone = zone.substring(zone.lastIndexOf("/") + 1);
vm.setProviderDataCenterId(zone);
DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
DateTime dt = DateTime.parse(instance.getCreationTimestamp(), fmt);
vm.setCreationTimestamp(dt.toDate().getTime());
if (instance.getDisks() != null) {
for (AttachedDisk disk : instance.getDisks()) {
if (disk != null && disk.getBoot() != null && disk.getBoot()) {
String diskName = disk.getSource().substring(disk.getSource().lastIndexOf("/") + 1);
Compute gce = provider.getGoogleCompute();
try {
Disk sourceDisk = gce.disks().get(provider.getContext().getAccountNumber(), zone, diskName).execute();
if (sourceDisk != null && sourceDisk.getSourceImage() != null) {
String project = "";
Pattern p = Pattern.compile("/projects/(.*?)/");
Matcher m = p.matcher(sourceDisk.getSourceImage());
while(m.find()){
project = m.group(1);
break;
}
vm.setProviderMachineImageId(project + "_" + sourceDisk.getSourceImage().substring(sourceDisk.getSourceImage().lastIndexOf("/") + 1));
}
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new InternalException("IOException: " + ex.getMessage());
}
}
}
}
String machineTypeName = instance.getMachineType().substring(instance.getMachineType().lastIndexOf("/") + 1);
vm.setProductId(machineTypeName + "+" + zone);
ArrayList<RawAddress> publicAddresses = new ArrayList<RawAddress>();
ArrayList<RawAddress> privateAddresses = new ArrayList<RawAddress>();
boolean firstPass = true;
boolean isSet = false;
String providerAssignedIpAddressId = null;
for (NetworkInterface nic : instance.getNetworkInterfaces()) {
if (firstPass) {
vm.setProviderVlanId(nic.getNetwork().substring(nic.getNetwork().lastIndexOf("/") + 1));
firstPass = false;
}
if (nic.getNetworkIP() != null) {
privateAddresses.add(new RawAddress(nic.getNetworkIP()));
}
if (nic.getAccessConfigs() != null && !nic.getAccessConfigs().isEmpty()) {
for (AccessConfig accessConfig : nic.getAccessConfigs()) {
if (accessConfig.getNatIP() != null) {
publicAddresses.add(new RawAddress(accessConfig.getNatIP()));
if (!isSet) {
try {
isSet = true;
providerAssignedIpAddressId = provider.getNetworkServices().getIpAddressSupport().getIpAddressIdFromIP(accessConfig.getNatIP(), regionId);
} catch(InternalException ex) {
/*Likely to be an ephemeral IP*/
}
}
}
}
}
}
vm.setPublicAddresses(publicAddresses.toArray(new RawAddress[publicAddresses.size()]));
vm.setPrivateAddresses(privateAddresses.toArray(new RawAddress[privateAddresses.size()]));
vm.setProviderAssignedIpAddressId(providerAssignedIpAddressId);
vm.setRebootable(true);
vm.setPersistent(true);
vm.setIpForwardingAllowed(true);
vm.setImagable(false);
vm.setClonable(false);
vm.setPlatform(Platform.guess(instance.getName()));
vm.setArchitecture(Architecture.I64);
vm.setTag("contentLink", instance.getSelfLink());
return vm;
}
private VirtualMachineProduct toProduct(MachineType machineType){
VirtualMachineProduct product = new VirtualMachineProduct();
product.setProviderProductId(machineType.getName() + "+" + machineType.getZone());
product.setName(machineType.getName());
product.setDescription(machineType.getSelfLink());
product.setCpuCount(machineType.getGuestCpus());
product.setRamSize(new Storage<Megabyte>(machineType.getMemoryMb(), Storage.MEGABYTE));
if (machineType.getImageSpaceGb() != null)
product.setRootVolumeSize(new Storage<Gigabyte>(machineType.getImageSpaceGb(), Storage.GIGABYTE));
else
product.setRootVolumeSize(new Storage<Gigabyte>(0, Storage.GIGABYTE)); // defined at creation time by specified root volume size.
product.setVisibleScope(VisibleScope.ACCOUNT_DATACENTER);
return product;
}
// the default implementation does parallel launches and throws an exception only if it is unable to launch any virtual machines
@Override
public @Nonnull Iterable<String> launchMany(final @Nonnull VMLaunchOptions withLaunchOptions, final @Nonnegative int count) throws CloudException, InternalException {
if( count < 1 ) {
throw new InternalException("Invalid attempt to launch less than 1 virtual machine (requested " + count + ").");
}
if( count == 1 ) {
return Collections.singleton(launch(withLaunchOptions).getProviderVirtualMachineId());
}
final List<Future<String>> results = new ArrayList<Future<String>>();
// windows on GCE follows same naming constraints as regular instances, 1-62 lower and numbers, must begin with a letter.
NamingConstraints c = NamingConstraints.getAlphaNumeric(1, 63).withNoSpaces().withRegularExpression("(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)").lowerCaseOnly().constrainedBy('-');
String baseHost = c.convertToValidName(withLaunchOptions.getHostName(), Locale.US);
if( baseHost == null ) {
baseHost = withLaunchOptions.getHostName();
}
for (int i = 1; i <= count; i++) {
String hostName = c.incrementName(baseHost, i);
String friendlyName = withLaunchOptions.getFriendlyName() + "-" + i;
VMLaunchOptions options = withLaunchOptions.copy(hostName == null ? withLaunchOptions.getHostName() + "-" + i : hostName, friendlyName);
results.add(launchAsync(options));
}
PopulatorThread<String> populator = new PopulatorThread<String>(new JiteratorPopulator<String>() {
@Override
public void populate( @Nonnull Jiterator<String> iterator ) throws Exception {
List<Future<String>> original = results;
List<Future<String>> copy = new ArrayList<Future<String>>();
Exception exception = null;
boolean loaded = false;
while( !original.isEmpty() ) {
for( Future<String> result : original ) {
if( result.isDone() ) {
try {
iterator.push(result.get());
loaded = true;
} catch( Exception e ) {
exception = e;
}
}
else {
copy.add(result);
}
}
original = copy;
// copy has to be a new list else we'll get into concurrently modified list state
copy = new ArrayList<Future<String>>();
}
if( exception != null && !loaded ) {
throw exception;
}
}
});
populator.populate();
return populator.getResult();
}
@Override
public @Nullable String getUserData( @Nonnull String vmId ) throws InternalException, CloudException {
APITrace.begin(getProvider(), "getVirtualMachine");
try{
try{
Compute gce = provider.getGoogleCompute();
InstanceAggregatedList instances = gce.instances().aggregatedList(provider.getContext().getAccountNumber()).setFilter("name eq " + getVmNameFromId(vmId)).execute();
Iterator<String> it = instances.getItems().keySet().iterator();
while (it.hasNext()){
String zone = it.next();
if(instances.getItems() != null && instances.getItems().get(zone) != null && instances.getItems().get(zone).getInstances() != null){
for(Instance instance : instances.getItems().get(zone).getInstances()){
if(instance.getName().equals(getVmNameFromId(vmId))) {
Metadata metadata = instance.getMetadata();
if (null != metadata) {
List<Items> items = metadata.getItems();
if (null != items) {
for (Items item : items) {
if ("startup-script".equals(item.getKey())) {
return item.getValue();
}
}
}
}
}
}
}
}
return null; // not found
} catch (IOException ex) {
logger.error(ex.getMessage());
if (ex.getClass() == GoogleJsonResponseException.class) {
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred retrieving VM: " + vmId + ": " + ex.getMessage());
}
}
finally {
APITrace.end();
}
}
}