diff --git a/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java b/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java index bf45ed0d56..2ab5c0f8ce 100644 --- a/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java +++ b/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/GoogleComputeEngineServiceAdapter.java @@ -138,15 +138,14 @@ public final class GoogleComputeEngineServiceAdapter tags.add(naming.name(ports)); } - NewInstance newInstance = NewInstance.create( - name, // name + NewInstance newInstance = new NewInstance.Builder( name, template.getHardware().getUri(), // machineType - network, // network - disks, // disks - group, // description - Tags.create(null, ImmutableList.copyOf(tags)) // tags - ); - + network, + disks) + .description(group) + .tags(Tags.create(null, ImmutableList.copyOf(tags))) + .serviceAccounts(options.serviceAccounts()) + .build(); // Add metadata from template and for ssh key and image id newInstance.metadata().putAll(options.getUserMetadata()); @@ -177,7 +176,7 @@ public final class GoogleComputeEngineServiceAdapter null, // networkInterfaces null, // disks newInstance.metadata(), // metadata - null, // serviceAccounts + newInstance.serviceAccounts(), // serviceAccounts Scheduling.create(OnHostMaintenance.MIGRATE, true) // scheduling )); checkState(instanceVisible.apply(instance), "instance %s is not api visible!", instance.get()); diff --git a/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/options/GoogleComputeEngineTemplateOptions.java b/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/options/GoogleComputeEngineTemplateOptions.java index 2fc8e8a662..1923c3a20d 100644 --- a/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/options/GoogleComputeEngineTemplateOptions.java +++ b/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/options/GoogleComputeEngineTemplateOptions.java @@ -16,16 +16,19 @@ */ package org.jclouds.googlecomputeengine.compute.options; +import java.util.List; import java.util.Map; import org.jclouds.compute.options.TemplateOptions; import org.jclouds.domain.LoginCredentials; import org.jclouds.scriptbuilder.domain.Statement; +import org.jclouds.googlecomputeengine.domain.Instance.ServiceAccount; /** Instance options specific to Google Compute Engine. */ public final class GoogleComputeEngineTemplateOptions extends TemplateOptions { private boolean autoCreateKeyPair = true; + private List serviceAccounts; @Override public GoogleComputeEngineTemplateOptions clone() { @@ -40,6 +43,7 @@ public final class GoogleComputeEngineTemplateOptions extends TemplateOptions { if (to instanceof GoogleComputeEngineTemplateOptions) { GoogleComputeEngineTemplateOptions eTo = GoogleComputeEngineTemplateOptions.class.cast(to); eTo.autoCreateKeyPair(autoCreateKeyPair()); + eTo.serviceAccounts(serviceAccounts()); } } @@ -52,12 +56,30 @@ public final class GoogleComputeEngineTemplateOptions extends TemplateOptions { } /** - * Sets whether an SSH key pair should be created automatically. + * Gets whether an SSH key pair should be created automatically. */ public boolean autoCreateKeyPair() { return autoCreateKeyPair; } + /** + * Sets a list of service accounts, with their specified scopes, to authorize on created instance. + * For example, to give a node the 'compute' scope you would add a service account with the email 'default' + * and the scope 'https://www.googleapis.com/auth/compute' + * These scopes will be given to all nodes created with these template options. + */ + public GoogleComputeEngineTemplateOptions serviceAccounts(List serviceAccounts){ + this.serviceAccounts = serviceAccounts; + return this; + } + + /** + * Gets the list of service accounts, with their specified scopes, that will be authorize on created instances. + */ + public List serviceAccounts(){ + return serviceAccounts; + } + /** * {@inheritDoc} */ diff --git a/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/NewInstance.java b/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/NewInstance.java index f6b6f6d74c..2738646dde 100644 --- a/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/NewInstance.java +++ b/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/NewInstance.java @@ -17,6 +17,7 @@ package org.jclouds.googlecomputeengine.domain; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import java.net.URI; import java.util.Arrays; @@ -80,7 +81,8 @@ public abstract class NewInstance { return create(name, machineType, network, Arrays.asList(AttachDisk.newBootDisk(sourceImage)), null, null); } - public static NewInstance create(String name, URI machineType, URI network, List disks, @Nullable String description, @Nullable Tags tags) { + public static NewInstance create(String name, URI machineType, URI network, List disks, + @Nullable String description, @Nullable Tags tags) { checkArgument(disks.get(0).boot(), "disk 0 must be a boot disk! %s", disks); boolean foundBoot = false; for (AttachDisk disk : disks) { @@ -93,13 +95,86 @@ public abstract class NewInstance { description, tags != null ? tags : Tags.create(), Metadata.create(), null, null); } - @SerializedNames({ "name", "machineType", "canIpForward", "networkInterfaces", "disks", "description", "tags", "metadata", - "serviceAccounts", "scheduling" }) - static NewInstance create(String name, URI machineType, Boolean canIpForward, List networkInterfaces, - List disks, String description, Tags tags, Metadata metadata, List serviceAccounts, Scheduling scheduling) { - return new AutoValue_NewInstance(name, machineType, canIpForward, networkInterfaces, disks, description, tags, metadata, serviceAccounts, scheduling); + @SerializedNames({ "name", "machineType", "canIpForward", "networkInterfaces", "disks", "description", + "tags", "metadata", "serviceAccounts", "scheduling" }) + static NewInstance create(String name, URI machineType, Boolean canIpForward, + List networkInterfaces, List disks, String description, + Tags tags, Metadata metadata, List serviceAccounts, Scheduling scheduling) { + return new AutoValue_NewInstance(name, machineType, canIpForward, networkInterfaces, disks, description, + tags, metadata, serviceAccounts, scheduling); } NewInstance() { } + + public static class Builder { + private String name; + private URI machineType; + private Boolean canIpForward; + private List networkInterfaces; + private List disks; + private String description; + private Tags tags; + private Metadata metadata; + private List serviceAccounts; + private Scheduling scheduling; + + public Builder(String name, URI machineType, URI network, List disks) { + checkNotNull(name, "NewInstance name cannot be null"); + this.name = name; + this.machineType = machineType; + this.networkInterfaces = ImmutableList.of(NetworkInterface.create(network)); + this.disks = disks; + } + + public Builder(String name, URI machineType, URI network, URI sourceImage) { + checkNotNull(name, "NewInstance name cannot be null"); + this.name = name; + this.machineType = machineType; + this.networkInterfaces = ImmutableList.of(NetworkInterface.create(network)); + this.disks = Arrays.asList(AttachDisk.newBootDisk(sourceImage)); + } + + public Builder canIpForward(Boolean canIpForward){ + this.canIpForward = canIpForward; + return this; + } + + public Builder description(String description){ + this.description = description; + return this; + } + + public Builder tags(Tags tags){ + this.tags = tags; + return this; + } + + public Builder metadata(Metadata metadata){ + this.metadata = metadata; + return this; + } + + /** + * A list of service accounts, with their specified scopes, authorized for this instance. + * Service accounts generate access tokens that can be accessed through the metadata server + * and used to authenticate applications on the instance. + * Note: to add scopes to the default service account on the VM you can use 'default' as + * a keyword for email. + */ + public Builder serviceAccounts(List serviceAccounts){ + this.serviceAccounts = serviceAccounts; + return this; + } + + public Builder scheduling(Scheduling scheduling){ + this.scheduling = scheduling; + return this; + } + + public NewInstance build() { + return create(name, machineType, canIpForward, networkInterfaces, disks, description, tags != null ? tags : Tags.create(), + metadata != null ? metadata : Metadata.create(), serviceAccounts, scheduling); + } + } } diff --git a/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Tags.java b/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Tags.java index 2bd09fdc6e..59f30712c0 100644 --- a/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Tags.java +++ b/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/domain/Tags.java @@ -40,10 +40,6 @@ public abstract class Tags implements Cloneable { return Tags.create(null, null); } - public static Tags create(String fingerprint) { - return Tags.create(fingerprint, null); - } - @SerializedNames({ "fingerprint", "items" }) public static Tags create(String fingerprint, ImmutableList items) { // Dictates the type when created from json! ImmutableList empty = ImmutableList.of(); diff --git a/providers/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/features/InstanceApiLiveTest.java b/providers/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/features/InstanceApiLiveTest.java index 651c7d1e76..5200c3a821 100644 --- a/providers/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/features/InstanceApiLiveTest.java +++ b/providers/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/features/InstanceApiLiveTest.java @@ -34,7 +34,9 @@ import org.jclouds.googlecomputeengine.domain.Instance; import org.jclouds.googlecomputeengine.domain.Instance.AttachedDisk; import org.jclouds.googlecomputeengine.domain.Instance.NetworkInterface.AccessConfig.Type; import org.jclouds.googlecomputeengine.domain.Instance.Scheduling; +import org.jclouds.googlecomputeengine.domain.Instance.Scheduling.OnHostMaintenance; import org.jclouds.googlecomputeengine.domain.Instance.SerialPortOutput; +import org.jclouds.googlecomputeengine.domain.Instance.ServiceAccount; import org.jclouds.googlecomputeengine.domain.Instance.NetworkInterface.AccessConfig; import org.jclouds.googlecomputeengine.domain.Metadata; import org.jclouds.googlecomputeengine.domain.NewInstance; @@ -99,11 +101,16 @@ public class InstanceApiLiveTest extends BaseGoogleComputeEngineApiLiveTest { ); instance.metadata().put("mykey", "myvalue"); - instance2 = NewInstance.create( - INSTANCE_NAME2, // name + instance2 = new NewInstance.Builder(INSTANCE_NAME2, // name getDefaultMachineTypeUrl(), // machineType getNetworkUrl(INSTANCE_NETWORK_NAME), // network - imageUri); // sourceImage + imageUri) // sourceImage + .canIpForward(true) + .description("description") + .tags(Tags.create(null, ImmutableList.of("tag1"))) + .serviceAccounts(ImmutableList.of(ServiceAccount.create("default", ImmutableList.of("https://www.googleapis.com/auth/compute")))) + .scheduling(Scheduling.create(OnHostMaintenance.MIGRATE, true)) + .build(); return api; } @@ -116,6 +123,18 @@ public class InstanceApiLiveTest extends BaseGoogleComputeEngineApiLiveTest { return api.disksInZone(DEFAULT_ZONE_NAME); } + @Test(groups = "live", dependsOnMethods = "testInsertInstance") + public void testGetInstance2() { + Instance instance = api().get(INSTANCE_NAME2); + assertNotNull(instance); + assertInstanceEquals(instance, this.instance2); + assertTrue(instance.canIpForward()); + assertEquals(instance.description(), "description"); + assertEquals(instance.serviceAccounts().get(0).scopes(), ImmutableList.of("https://www.googleapis.com/auth/compute")); + assertTrue(instance.scheduling().automaticRestart()); + assertEquals(instance.scheduling().onHostMaintenance(), OnHostMaintenance.MIGRATE); + } + @Test(groups = "live") public void testInsertInstance() { // need to insert the network first @@ -294,7 +313,7 @@ public class InstanceApiLiveTest extends BaseGoogleComputeEngineApiLiveTest { assertOperationDoneSuccessfully(api().reset(INSTANCE_NAME)); } - @Test(groups = "live", dependsOnMethods = "testInsertInstance") + @Test(groups = "live", dependsOnMethods = "testGetInstance2") public void testStopInstance() { Instance originalInstance = api().get(INSTANCE_NAME2); assertEquals(originalInstance.status(), Instance.Status.RUNNING); diff --git a/providers/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/features/InstanceApiMockTest.java b/providers/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/features/InstanceApiMockTest.java index 7d1a72a81f..2c19324f8c 100644 --- a/providers/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/features/InstanceApiMockTest.java +++ b/providers/google-compute-engine/src/test/java/org/jclouds/googlecomputeengine/features/InstanceApiMockTest.java @@ -27,6 +27,8 @@ import org.jclouds.googlecomputeengine.domain.AttachDisk; import org.jclouds.googlecomputeengine.domain.AttachDisk.DiskInterface; import org.jclouds.googlecomputeengine.domain.Instance.NetworkInterface.AccessConfig; import org.jclouds.googlecomputeengine.domain.Instance.NetworkInterface.AccessConfig.Type; +import org.jclouds.googlecomputeengine.domain.Instance.Scheduling; +import org.jclouds.googlecomputeengine.domain.Instance.ServiceAccount; import org.jclouds.googlecomputeengine.domain.Metadata; import org.jclouds.googlecomputeengine.domain.NewInstance; import org.jclouds.googlecomputeengine.domain.Instance.Scheduling.OnHostMaintenance; @@ -250,6 +252,41 @@ public class InstanceApiMockTest extends BaseGoogleComputeEngineApiMockTest { assertSent(server, "POST", "/projects/party/zones/us-central1-a/instances/test-1/stop"); } + public void builderTest() throws Exception { + server.enqueue(jsonResponse("/zone_operation.json")); + + NewInstance newInstance = new NewInstance.Builder("test-1", // name + URI.create(url("/projects/party/zones/us-central1-a/machineTypes/n1-standard-1")), // machineType + URI.create(url("/projects/party/global/networks/default")), // network + URI.create(url("/projects/party/global/images/centos-6-2-v20120326"))).build(); // sourceImage) + + assertEquals(instanceApi().create(newInstance), new ParseZoneOperationTest().expected(url("/projects"))); + assertSent(server, "POST", "/projects/party/zones/us-central1-a/instances", + stringFromResource("/instance_insert_simple.json")); + } + + public void insert_builder_allOptions() throws Exception { + server.enqueue(jsonResponse("/zone_operation.json")); + + NewInstance newInstance = new NewInstance.Builder( + "test-1", // name + URI.create(url("/projects/party/zones/us-central1-a/machineTypes/n1-standard-1")), // machineType + URI.create(url("/projects/party/global/networks/default")), // network + Arrays.asList(AttachDisk.existingBootDisk(URI.create(url("/projects/party/zones/us-central1-a/disks/test"))))) + .canIpForward(true) + .description("desc") + .tags(null) + .metadata(Metadata.create().put("aKey", "aValue")) + .serviceAccounts(ImmutableList.of(ServiceAccount.create("default", + ImmutableList.of("https://www.googleapis.com/auth/compute")))) + .scheduling(Scheduling.create(OnHostMaintenance.MIGRATE, true)) + .build(); + + assertEquals(instanceApi().create(newInstance), new ParseZoneOperationTest().expected(url("/projects"))); + assertSent(server, "POST", "/projects/party/zones/us-central1-a/instances", + stringFromResource("/instance_insert_full.json")); + } + InstanceApi instanceApi(){ return api().instancesInZone("us-central1-a"); } diff --git a/providers/google-compute-engine/src/test/resources/instance_insert_full.json b/providers/google-compute-engine/src/test/resources/instance_insert_full.json new file mode 100644 index 0000000000..61302dfa24 --- /dev/null +++ b/providers/google-compute-engine/src/test/resources/instance_insert_full.json @@ -0,0 +1,47 @@ +{ + "name": "test-1", + "machineType": "https://www.googleapis.com/compute/v1/projects/party/zones/us-central1-a/machineTypes/n1-standard-1", + "canIpForward": true, + "networkInterfaces": [ + { + "network": "https://www.googleapis.com/compute/v1/projects/party/global/networks/default", + "accessConfigs": [ + { + "type": "ONE_TO_ONE_NAT" + } + ] + } + ], + "disks": [ + { + "type": "PERSISTENT", + "source": "https://www.googleapis.com/compute/v1/projects/party/zones/us-central1-a/disks/test", + "boot": true, + "autoDelete": false + } + ], + "description": "desc", + "tags": { + "items": [] + }, + "metadata": { + "items": [ + { + "key": "aKey", + "value": "aValue" + } + ] + }, + "serviceAccounts": [ + { + "email": "default", + "scopes": [ + "https://www.googleapis.com/auth/compute" + ] + } + ], + "scheduling": { + "onHostMaintenance": "MIGRATE", + "automaticRestart": true + } +} \ No newline at end of file