Skip to content

Commit

Permalink
More GoogleCloud operations (related to #215)
Browse files Browse the repository at this point in the history
  • Loading branch information
vania-pooh committed Feb 14, 2017
1 parent fd6b7cf commit b8c3df7
Show file tree
Hide file tree
Showing 11 changed files with 424 additions and 13 deletions.
2 changes: 1 addition & 1 deletion perspective-google-cloud/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<name>Perspective Google Cloud</name>

<properties>
<google-cloud-client.version>0.8.3-alpha</google-cloud-client.version>
<google-cloud-client.version>0.9.0-alpha</google-cloud-client.version>
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,45 @@
package org.meridor.perspective.googlecloud;

import com.google.cloud.compute.MachineType;
import com.google.cloud.compute.Network;
import com.google.cloud.compute.Region;
import org.meridor.perspective.beans.Keypair;

import java.util.List;

public interface Api {

// Project operations

List<MachineType> listFlavors();

List<Network> listNetworks();

List<Region> listRegions();

List<Keypair> listKeypairs();

// Instance operations

boolean deleteInstance(String instanceId);

boolean startInstance(String instanceId);

boolean shutdownInstance(String instanceId);

boolean rebootInstance(String instanceId);

boolean hardRebootInstance(String instanceId);

boolean resizeInstance(String instanceId, String flavorId);

List<com.google.cloud.compute.Instance> listInstances();

// Image operations

boolean deleteImage(String imageId);

List<com.google.cloud.compute.Image> listImages();

List<com.google.cloud.compute.Snapshot> listSnapshots();
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.meridor.perspective.googlecloud;

import com.google.api.services.compute.model.Region;
import com.google.cloud.compute.Region;
import org.meridor.perspective.config.Cloud;

import java.util.function.BiConsumer;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
package org.meridor.perspective.googlecloud;

import com.google.api.services.compute.model.Region;
import com.google.api.client.util.Lists;
import com.google.auth.Credentials;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.compute.Compute;
import com.google.cloud.compute.ComputeOptions;
import com.google.cloud.compute.*;
import org.meridor.perspective.beans.Keypair;
import org.meridor.perspective.config.Cloud;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;

import static java.nio.file.StandardOpenOption.READ;
import static org.meridor.perspective.googlecloud.IdUtils.stringToImageId;
import static org.meridor.perspective.googlecloud.IdUtils.stringToInstanceId;

@Component
public class ApiProviderImpl implements ApiProvider {

@Override
Expand All @@ -25,31 +33,147 @@ public Api getApi(Cloud cloud) {

@Override
public void forEachRegion(Cloud cloud, BiConsumer<Region, Api> action) throws Exception {

Api api = getApi(cloud);
api.listRegions().forEach(r -> action.accept(r, api));
}

private class ApiImpl implements Api {
private static class ApiImpl implements Api {

private static final Logger LOG = LoggerFactory.getLogger(Api.class);


private final Compute computeApi;

ApiImpl(Cloud cloud) {
this.computeApi = createComputeApi(cloud);
Credentials credentials = getCredentials(cloud);
this.computeApi = createComputeApi(credentials);
}

private Compute createComputeApi(Cloud cloud) {
private Credentials getCredentials(Cloud cloud) {

Path jsonPath = Paths.get(cloud.getCredential());
try (InputStream inputStream = Files.newInputStream(jsonPath, READ)) {
Credentials credentials = GoogleCredentials.fromStream(inputStream);
return ComputeOptions.newBuilder()
.setCredentials(credentials)
.build().getService();
return GoogleCredentials.fromStream(inputStream);
} catch (IOException e) {
throw new RuntimeException(String.format(
"Failed to read JSON credentials file [%s]",
jsonPath.toAbsolutePath().toString()
), e);
}
}

private Compute createComputeApi(Credentials credentials) {
return ComputeOptions.newBuilder()
.setCredentials(credentials)
.build().getService();
}

private static boolean executeInstanceOperation(String instanceId, Function<InstanceId, Operation> action) {
return isOperationSuccessful(action.apply(stringToInstanceId(instanceId)));
}

private static boolean executeImageOperation(String imageId, Function<ImageId, Operation> action) {
return isOperationSuccessful(action.apply(stringToImageId(imageId)));
}

private static boolean isOperationSuccessful(Operation operation) {
try {
Operation completedOperation = operation.waitFor();
if (completedOperation != null) {
if (completedOperation.getErrors() != null) {
completedOperation.getErrors()
.forEach(
e -> LOG.error(
"Error {} in {}: {}",
e.getCode(),
e.getLocation(),
e.getMessage()
)
);
} else {
return true;
}
}
return false;
} catch (Exception e) {
return false;
}
}

@Override
public List<MachineType> listFlavors() {
return Lists.newArrayList(computeApi.listMachineTypes().getValues());
}

@Override
public List<Network> listNetworks() {
return Lists.newArrayList(computeApi.listNetworks().getValues());
}

@Override
public List<Region> listRegions() {
return Lists.newArrayList(computeApi.listRegions().getValues());
}

@Override
public List<Keypair> listKeypairs() {
//TODO: see https://cloud.google.com/compute/docs/instances/adding-removing-ssh-keys#project-wide
throw new UnsupportedOperationException();
}

@Override
public boolean deleteInstance(String instanceId) {
return executeInstanceOperation(instanceId, id -> computeApi.deleteInstance(id));
}

@Override
public boolean startInstance(String instanceId) {
return executeInstanceOperation(instanceId, id -> computeApi.start(id));
}

@Override
public boolean shutdownInstance(String instanceId) {
return executeInstanceOperation(instanceId, id -> computeApi.stop(id));
}

@Override
public boolean rebootInstance(String instanceId) {
//There's no soft reboot operation in Google Cloud
return shutdownInstance(instanceId) && startInstance(instanceId);
}

@Override
public boolean hardRebootInstance(String instanceId) {
return executeInstanceOperation(instanceId, id -> computeApi.reset(id));
}

@Override
public boolean resizeInstance(String instanceId, String flavorId) {
return executeInstanceOperation(instanceId, id -> {
MachineTypeId machineTypeId = IdUtils.stringToMachineTypeId(flavorId);
return computeApi.setMachineType(id, machineTypeId);
});
}

@Override
public List<com.google.cloud.compute.Instance> listInstances() {
return Lists.newArrayList(computeApi.listInstances().getValues());
}

@Override
public boolean deleteImage(String imageId) {
return executeImageOperation(imageId, id -> computeApi.deleteImage(id));
}

@Override
public List<com.google.cloud.compute.Image> listImages() {
return Lists.newArrayList(computeApi.listImages().getValues());
}

@Override
public List<Snapshot> listSnapshots() {
return Lists.newArrayList(computeApi.listSnapshots().getValues());
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.meridor.perspective.googlecloud;

import org.meridor.perspective.beans.Instance;
import org.meridor.perspective.config.Cloud;
import org.meridor.perspective.worker.operation.AbstractInstanceOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public abstract class BaseInstanceOperation extends AbstractInstanceOperation<Api> {

@Autowired
private ApiProvider apiProvider;

@Override
protected Api getApi(Cloud cloud, Instance instance) {
return apiProvider.getApi(cloud);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.meridor.perspective.googlecloud;

import org.meridor.perspective.beans.Instance;
import org.meridor.perspective.config.OperationType;
import org.springframework.stereotype.Component;

import java.util.function.BiFunction;

@Component
public class DeleteInstanceOperation extends BaseInstanceOperation {

@Override
protected BiFunction<Api, Instance, Boolean> getAction() {
return (api, instance) -> api.deleteInstance(instance.getRealId());
}

@Override
protected String getSuccessMessage(Instance instance) {
return String.format("Deleted instance %s (%s)", instance.getName(), instance.getId());
}

@Override
protected String getErrorMessage(Instance instance) {
return String.format("Failed to delete instance %s (%s)", instance.getName(), instance.getId());
}

@Override
public OperationType[] getTypes() {
return new OperationType[]{OperationType.DELETE_INSTANCE};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.meridor.perspective.googlecloud;

import org.meridor.perspective.beans.Instance;
import org.meridor.perspective.config.OperationType;
import org.springframework.stereotype.Component;

import java.util.function.BiFunction;

import static org.meridor.perspective.config.OperationType.HARD_REBOOT_INSTANCE;

@Component
public class HardRebootInstanceOperation extends BaseInstanceOperation {

@Override
protected BiFunction<Api, Instance, Boolean> getAction() {
return (api, instance) -> api.hardRebootInstance(instance.getRealId());
}

@Override
protected String getSuccessMessage(Instance instance) {
return String.format("Hard rebooted instance %s (%s)", instance.getName(), instance.getId());
}

@Override
protected String getErrorMessage(Instance instance) {
return String.format("Failed to hard reboot instance %s (%s)", instance.getName(), instance.getId());
}

@Override
public OperationType[] getTypes() {
return new OperationType[]{HARD_REBOOT_INSTANCE};
}

}
Loading

0 comments on commit b8c3df7

Please sign in to comment.