/**
* 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 com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.model.*;
import org.apache.log4j.Logger;
import org.dasein.cloud.*;
import org.dasein.cloud.compute.*;
import org.dasein.cloud.compute.Snapshot;
import org.dasein.cloud.google.GoogleException;
import org.dasein.cloud.google.GoogleMethod;
import org.dasein.cloud.google.GoogleOperationType;
import org.dasein.cloud.google.capabilities.GCESnapshotCapabilities;
import org.dasein.cloud.google.Google;
import org.dasein.cloud.util.APITrace;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.*;
/**
* Implements the snapshot services supported in the Google API.
* @author Drew Lyall
* @version 2014.03 initial version
* @since 2014.03
*/
public class SnapshotSupport extends AbstractSnapshotSupport{
static private final Logger logger = Google.getLogger(SnapshotSupport.class);
private Google provider;
public SnapshotSupport(Google provider){
super(provider);
this.provider = provider;
}
@Override
public void addSnapshotShare(@Nonnull String providerSnapshotId, @Nonnull String accountNumber) throws CloudException, InternalException{
throw new OperationNotSupportedException("Google does not support sharing a snapshot across accounts.");
}
@Override
public void addPublicShare(@Nonnull String providerSnapshotId) throws CloudException, InternalException{
throw new OperationNotSupportedException("Google does not support sharing a snapshot across accounts.");
}
@Override
public String createSnapshot(@Nonnull SnapshotCreateOptions options) throws CloudException, InternalException{
APITrace.begin(provider, "Snapshot.createSnapshot");
try{
Compute gce = provider.getGoogleCompute();
try{
Volume volume = provider.getComputeServices().getVolumeSupport().getVolume(options.getVolumeId());
com.google.api.services.compute.model.Snapshot snapshot = new com.google.api.services.compute.model.Snapshot();
snapshot.setName(getCapabilities().getSnapshotNamingConstraints().convertToValidName(options.getName(), Locale.US));
snapshot.setDescription(options.getDescription());
snapshot.setSourceDiskId(options.getVolumeId());
Operation job = gce.disks().createSnapshot(provider.getContext().getAccountNumber(), volume.getProviderDataCenterId(), options.getVolumeId(), snapshot).execute();
GoogleMethod method = new GoogleMethod(provider);
if(method.getOperationComplete(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION, "", volume.getProviderDataCenterId())){
SnapshotList snapshots = gce.snapshots().list(provider.getContext().getAccountNumber()).setFilter("name eq " + options.getName()).execute();
for(com.google.api.services.compute.model.Snapshot s : snapshots.getItems()){
if(s.getName().equals(options.getName()))return s.getName();
}
}
throw new CloudException("An error occurred creating the snapshot: Operation Timedout");
} 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 creating the snapshot: " + ex.getMessage());
} catch (Exception ex) {
throw new OperationNotSupportedException("Copying snapshots is not supported in GCE");
}
}
finally {
APITrace.end();
}
}
private transient volatile GCESnapshotCapabilities capabilities;
@Override
public @Nonnull GCESnapshotCapabilities getCapabilities(){
if(capabilities == null){
capabilities = new GCESnapshotCapabilities(provider);
}
return capabilities;
}
@Override
public @Nonnull String getProviderTermForSnapshot(@Nonnull Locale locale){
return "snapshot";
}
@Override
public Snapshot getSnapshot(@Nonnull String snapshotId) throws InternalException, CloudException{
APITrace.begin(provider, "Snapshot.getSnapshot");
try{
Compute gce = provider.getGoogleCompute();
try{
com.google.api.services.compute.model.Snapshot snapshot = gce.snapshots().get(provider.getContext().getAccountNumber(), snapshotId).execute();
return toSnapshot(snapshot);
} catch (IOException ex) {
if ((ex.getMessage() != null) && (ex.getMessage().contains("404 Not Found"))) // not found.
return null;
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 getting the snapshot: " + ex.getMessage());
}
}
finally {
APITrace.end();
}
}
@Override
public boolean isPublic(@Nonnull String snapshotId) throws InternalException, CloudException{
return false;
}
@Override
public boolean isSubscribed() throws InternalException, CloudException{
return true;
}
@Override
public @Nonnull Iterable<String> listShares(@Nonnull String snapshotId) throws InternalException, CloudException{
return Collections.emptyList();
}
@Override
public @Nonnull Iterable<ResourceStatus> listSnapshotStatus() throws InternalException, CloudException{
APITrace.begin(provider, "Snapshot.listSnapshotStatus");
try{
ArrayList<ResourceStatus> statuses = new ArrayList<ResourceStatus>();
Compute gce = provider.getGoogleCompute();
try{
SnapshotList list = gce.snapshots().list(provider.getContext().getAccountNumber()).execute();
if(list != null && list.size() > 0){
for(com.google.api.services.compute.model.Snapshot googleSnapshot : list.getItems()){
ResourceStatus status = toStatus(googleSnapshot);
if(status != null)statuses.add(status);
}
}
return statuses;
} 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 snapshot status");
}
}
finally {
APITrace.end();
}
}
@Override
public @Nonnull Iterable<Snapshot> listSnapshots() throws InternalException, CloudException{
APITrace.begin(provider, "Snapshot.listSnapshots");
try{
ArrayList<Snapshot> snapshots = new ArrayList<Snapshot>();
Compute gce = provider.getGoogleCompute();
try{
SnapshotList list = gce.snapshots().list(provider.getContext().getAccountNumber()).execute();
if(list != null && list.getItems() != null && list.getItems().size() > 0){
for(com.google.api.services.compute.model.Snapshot googleSnapshot : list.getItems()){
Snapshot snapshot = toSnapshot(googleSnapshot);
if(snapshot != null)snapshots.add(snapshot);
}
}
return snapshots;
} 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 snapshots: " + ex.getMessage());
}
}
finally {
APITrace.end();
}
}
@Override
public @Nonnull Iterable<Snapshot> listSnapshots(SnapshotFilterOptions options) throws InternalException, CloudException{
return searchSnapshots(options);
}
@Override
public void remove(@Nonnull String snapshotId) throws InternalException, CloudException{
APITrace.begin(provider, "Snapshot.remove");
try{
Compute gce = provider.getGoogleCompute();
try{
Operation job = gce.snapshots().delete(provider.getContext().getAccountNumber(), snapshotId).execute();
GoogleMethod method = new GoogleMethod(provider);
if(!method.getOperationComplete(provider.getContext(), job, GoogleOperationType.GLOBAL_OPERATION, "", "")){
throw new CloudException("An error occurred deleting the snapshot: Operation timed out");
}
} catch (IOException ex) {
if (ex.getClass() == GoogleJsonResponseException.class) {
logger.error(ex.getMessage());
GoogleJsonResponseException gjre = (GoogleJsonResponseException)ex;
throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(), gjre.getDetails().getMessage());
} else
throw new CloudException("An error occurred deleting the snapshot: " + ex.getMessage());
}
}
finally {
APITrace.end();
}
}
@Override
public void removeAllSnapshotShares(@Nonnull String providerSnapshotId) throws CloudException, InternalException{
// NOP in clouds without sharing
}
@Override
public void removeSnapshotShare(@Nonnull String providerSnapshotId, @Nonnull String accountNumber) throws CloudException, InternalException{
throw new OperationNotSupportedException("Google does not support sharing/unsharing a snapshot across accounts.");
}
@Override
public void removePublicShare(@Nonnull String providerSnapshotId) throws CloudException, InternalException{
throw new OperationNotSupportedException("Google does not support sharing/unsharing a snapshot across accounts.");
}
@Override
public void removeTags(@Nonnull String snapshotId, @Nonnull Tag... tags) throws CloudException, InternalException{
throw new OperationNotSupportedException("Google snapshot does not contain meta data");
}
@Override
public void removeTags(@Nonnull String[] snapshotIds, @Nonnull Tag... tags) throws CloudException, InternalException{
throw new OperationNotSupportedException("Google snapshot does not contain meta data");
}
@Override
public @Nonnull Iterable<Snapshot> searchSnapshots(@Nonnull SnapshotFilterOptions options) throws InternalException, CloudException{
APITrace.begin(provider, "Snapshot.searchSnapshots");
try{
ArrayList<Snapshot> snapshots = new ArrayList<Snapshot>();
for(Snapshot snapshot : listSnapshots()){
if(options == null || options.matches(snapshot, null)){
snapshots.add(snapshot);
}
}
return snapshots;
}
finally {
APITrace.end();
}
}
@Override
public void updateTags(@Nonnull String snapshotId, @Nonnull Tag... tags) throws CloudException, InternalException{
throw new OperationNotSupportedException("Google snapshot does not contain meta data");
}
@Override
public void updateTags(@Nonnull String[] snapshotIds, @Nonnull Tag... tags) throws CloudException, InternalException{
throw new OperationNotSupportedException("Google snapshot does not contain meta data");
}
private @Nullable Snapshot toSnapshot(com.google.api.services.compute.model.Snapshot googleSnapshot){
Snapshot snapshot = new Snapshot();
snapshot.setProviderSnapshotId(googleSnapshot.getName());
snapshot.setName(googleSnapshot.getName());
snapshot.setDescription(googleSnapshot.getDescription());
snapshot.setOwner(provider.getContext().getAccountNumber());
SnapshotState state = SnapshotState.PENDING;
if(googleSnapshot.getStatus().equals("READY"))state = SnapshotState.AVAILABLE;
else if(googleSnapshot.getStatus().equals("DELETING"))state = SnapshotState.DELETED;
snapshot.setCurrentState(state);
//TODO: Set visible scope for snapshots
snapshot.setSizeInGb(googleSnapshot.getDiskSizeGb().intValue());
DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
DateTime dt = DateTime.parse(googleSnapshot.getCreationTimestamp(), fmt);
snapshot.setSnapshotTimestamp(dt.toDate().getTime());
String sourceDisk = googleSnapshot.getSourceDisk();
if (sourceDisk != null) {
snapshot.setVolumeId(sourceDisk.substring(sourceDisk.lastIndexOf("/") + 1));
}
return snapshot;
}
private @Nullable ResourceStatus toStatus(@Nullable com.google.api.services.compute.model.Snapshot snapshot) throws CloudException {
SnapshotState state;
if(snapshot.getStatus().equals("READY")){
state = SnapshotState.AVAILABLE;
}
else if(snapshot.getStatus().equals("DELETING")){
state = SnapshotState.DELETED;
}
else state = SnapshotState.PENDING;
return new ResourceStatus(snapshot.getName(), state);
}
}