diff --git a/providers/softlayer/src/main/java/org/jclouds/softlayer/binders/NotesToJson.java b/providers/softlayer/src/main/java/org/jclouds/softlayer/binders/NotesToJson.java new file mode 100644 index 0000000000..8a906bcd80 --- /dev/null +++ b/providers/softlayer/src/main/java/org/jclouds/softlayer/binders/NotesToJson.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.jclouds.softlayer.binders; + +import static com.google.common.base.Preconditions.checkArgument; + +import org.jclouds.http.HttpRequest; +import org.jclouds.json.Json; +import org.jclouds.rest.Binder; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Converts a String into a json string for changing the notes property of an instance + * The string is set into the payload of the HttpRequest + * + */ +@Singleton +public class NotesToJson implements Binder { + + private final Json json; + + @Inject + NotesToJson(Json json) { + this.json = json; + } + + @Override + public R bindToRequest(R request, Object input) { + checkArgument(input instanceof String); + String notes = (String)input; + request.setPayload(buildJson(notes)); + return request; + } + + private String buildJson(String notes) { + return json.toJson(ImmutableMap.of("parameters", ImmutableList.of(ImmutableMap.of("notes", notes)))); + } + +} diff --git a/providers/softlayer/src/main/java/org/jclouds/softlayer/compute/strategy/SoftLayerComputeServiceAdapter.java b/providers/softlayer/src/main/java/org/jclouds/softlayer/compute/strategy/SoftLayerComputeServiceAdapter.java index 835329b7f8..f3283a3cc4 100644 --- a/providers/softlayer/src/main/java/org/jclouds/softlayer/compute/strategy/SoftLayerComputeServiceAdapter.java +++ b/providers/softlayer/src/main/java/org/jclouds/softlayer/compute/strategy/SoftLayerComputeServiceAdapter.java @@ -25,14 +25,15 @@ import static com.google.common.collect.Iterables.get; import static com.google.common.collect.Iterables.tryFind; import static java.lang.Math.round; import static java.lang.String.format; -import static org.jclouds.compute.domain.Volume.Type; import static org.jclouds.compute.util.ComputeServiceUtils.getCores; import static org.jclouds.compute.util.ComputeServiceUtils.getSpace; import static org.jclouds.softlayer.reference.SoftLayerConstants.PROPERTY_SOFTLAYER_VIRTUALGUEST_ACTIVE_TRANSACTIONS_DELAY; import static org.jclouds.softlayer.reference.SoftLayerConstants.PROPERTY_SOFTLAYER_VIRTUALGUEST_LOGIN_DETAILS_DELAY; import static org.jclouds.util.Predicates2.retry; + import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -48,6 +49,7 @@ import org.jclouds.compute.domain.HardwareBuilder; import org.jclouds.compute.domain.Processor; import org.jclouds.compute.domain.Template; import org.jclouds.compute.domain.Volume; +import org.jclouds.compute.domain.Volume.Type; import org.jclouds.compute.domain.internal.VolumeImpl; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.domain.LoginCredentials; @@ -75,6 +77,7 @@ import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Stopwatch; +import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.collect.ComparisonChain; import com.google.common.collect.FluentIterable; @@ -92,6 +95,8 @@ import com.google.common.collect.Sets; public class SoftLayerComputeServiceAdapter implements ComputeServiceAdapter { + private static final String USER_META_NOTES = "notes"; + private static final int USER_META_NOTES_MAX_LENGTH = 1000; private static final String BOOTABLE_DEVICE = "0"; public static final String DEFAULT_DISK_TYPE = "LOCAL"; public static final int DEFAULT_MAX_PORT_SPEED = 100; @@ -137,8 +142,9 @@ public class SoftLayerComputeServiceAdapter implements final Datacenter datacenter = Datacenter.builder().name(template.getLocation().getId()).build(); final String imageId = template.getImage().getId(); int cores = (int) template.getHardware().getProcessors().get(0).getCores(); + String notes = getNotes(templateOptions); - VirtualGuest.Builder virtualGuestBuilder = VirtualGuest.builder() + VirtualGuest.Builder virtualGuestBuilder = VirtualGuest.builder() .domain(domainName) .hostname(name) .hourlyBillingFlag(hourlyBillingFlag) @@ -213,6 +219,11 @@ public class SoftLayerComputeServiceAdapter implements api.getVirtualGuestApi().setTags(result.getId(), templateOptions.getTags()); } + // notes + if (!Strings.isNullOrEmpty(notes)) { + api.getVirtualGuestApi().setNotes(result.getId(), notes); + } + logger.debug(">> awaiting login details for virtualGuest(%s)", result.getId()); boolean orderInSystem = loginDetailsTester.apply(result); logger.trace("<< VirtualGuest(%s) complete(%s)", result.getId(), orderInSystem); @@ -227,10 +238,22 @@ public class SoftLayerComputeServiceAdapter implements } result = api.getVirtualGuestApi().getVirtualGuest(result.getId()); Password pwd = get(result.getOperatingSystem().getPasswords(), 0); - return new NodeAndInitialCredentials(result, result.getId() + "", + return new NodeAndInitialCredentials(result, result.getId() + "", LoginCredentials.builder().user(pwd.getUsername()).password(pwd.getPassword()).build()); } + private String getNotes(SoftLayerTemplateOptions templateOptions) { + String notes = null; + Map meta = templateOptions.getUserMetadata(); + if (meta != null) { + notes = meta.get(USER_META_NOTES); + if (!Strings.isNullOrEmpty(notes)) { + checkArgument(notes.length() <= USER_META_NOTES_MAX_LENGTH, "'notes' property in user metadata should be long at most " + USER_META_NOTES_MAX_LENGTH + " characters."); + } + } + return notes; + } + @Override public Iterable listHardwareProfiles() { ContainerVirtualGuestConfiguration virtualGuestConfiguration = createObjectOptionsSupplier.get(); diff --git a/providers/softlayer/src/main/java/org/jclouds/softlayer/features/VirtualGuestApi.java b/providers/softlayer/src/main/java/org/jclouds/softlayer/features/VirtualGuestApi.java index abc581e942..046a711034 100644 --- a/providers/softlayer/src/main/java/org/jclouds/softlayer/features/VirtualGuestApi.java +++ b/providers/softlayer/src/main/java/org/jclouds/softlayer/features/VirtualGuestApi.java @@ -33,6 +33,7 @@ import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.Fallback; import org.jclouds.rest.annotations.QueryParams; import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.softlayer.binders.NotesToJson; import org.jclouds.softlayer.binders.TagToJson; import org.jclouds.softlayer.binders.VirtualGuestToJson; import org.jclouds.softlayer.domain.ContainerVirtualGuestConfiguration; @@ -53,6 +54,8 @@ public interface VirtualGuestApi { "statusId;operatingSystem.passwords;primaryBackendIpAddress;primaryIpAddress;activeTransactionCount;" + "blockDevices.diskImage;datacenter;tagReferences;privateNetworkOnlyFlag;sshKeys"; + String NOTES_MASK = "id;notes"; + /** * Enables the creation of computing instances on an account. * @param virtualGuest this data type presents the structure in which all virtual guests will be presented. @@ -63,7 +66,6 @@ public interface VirtualGuestApi { @POST @Path("SoftLayer_Virtual_Guest") @Produces(MediaType.APPLICATION_JSON) - @Fallback(Fallbacks.NullOnNotFoundOr404.class) VirtualGuest createVirtualGuest(@BinderParam(VirtualGuestToJson.class) VirtualGuest virtualGuest); /** @@ -138,7 +140,7 @@ public interface VirtualGuestApi { void resumeVirtualGuest(@PathParam("id") long id); /** - * Resume the guest. + * Set the tags on the instance * * @param id * id of the virtual guest @@ -147,6 +149,34 @@ public interface VirtualGuestApi { @POST @Path("/SoftLayer_Virtual_Guest/{id}/setTags") @Produces(MediaType.APPLICATION_JSON) - @Fallback(Fallbacks.FalseOnNotFoundOr404.class) boolean setTags(@PathParam("id") long id, @BinderParam(TagToJson.class) Set tags); + + /** + * Set notes (visible in UI) + * + * @param id id of the virtual guest + * @param notes The notes property to set on the machine - visible in UI + */ + @Named("VirtualGuest:setNotes") + @POST + @Path("/SoftLayer_Virtual_Guest/{id}/editObject") + @Produces(MediaType.APPLICATION_JSON) + boolean setNotes(@PathParam("id") long id, @BinderParam(NotesToJson.class) String notes); + + /** + * Get notes (visible in UI) + * + * Don't include it in default getObject mask as it can get quite big (up to 1000 chars). + * Also no place to put it in NodeMetadata. + * + * @param id + * id of the virtual guest + */ + @Named("VirtualGuest:getNotes") + @GET + @Path("/SoftLayer_Virtual_Guest/{id}/getObject") + @Produces(MediaType.APPLICATION_JSON) + @QueryParams(keys = "objectMask", values = NOTES_MASK) + @Fallback(Fallbacks.NullOnNotFoundOr404.class) + VirtualGuest getNotes(@PathParam("id") long id); } diff --git a/providers/softlayer/src/test/java/org/jclouds/softlayer/binders/NotesToJsonTest.java b/providers/softlayer/src/test/java/org/jclouds/softlayer/binders/NotesToJsonTest.java new file mode 100644 index 0000000000..4d52c7ea9d --- /dev/null +++ b/providers/softlayer/src/test/java/org/jclouds/softlayer/binders/NotesToJsonTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.jclouds.softlayer.binders; + +import static org.testng.Assert.assertEquals; + +import org.jclouds.http.HttpRequest; +import org.jclouds.json.Json; +import org.jclouds.json.internal.GsonWrapper; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import com.google.gson.Gson; + +@Test(groups = "unit", testName = "NotesToJsonTest") +public class NotesToJsonTest { + + private Json json; + + @BeforeClass + public void init() { + json = new GsonWrapper(new Gson()); + } + + @Test + public void testVirtualGuestWithNotes() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint("https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest").build(); + NotesToJson binder = new NotesToJson(json); + String notes = "some notes"; + + request = binder.bindToRequest(request, notes); + + assertEquals(request.getPayload().getRawContent(), "{\"parameters\":[{\"notes\":\"some notes\"}]}"); + } + + @Test + public void testVirtualGuestWithoutNotes() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint("https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest").build(); + NotesToJson binder = new NotesToJson(json); + request = binder.bindToRequest(request, ""); + + assertEquals(request.getPayload().getRawContent(), "{\"parameters\":[{\"notes\":\"\"}]}"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testVirtualGuestNullNotes() { + HttpRequest request = HttpRequest.builder().method("POST").endpoint("https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest").build(); + NotesToJson binder = new NotesToJson(json); + binder.bindToRequest(request, null); + } +} diff --git a/providers/softlayer/src/test/java/org/jclouds/softlayer/features/VirtualGuestApiExpectTest.java b/providers/softlayer/src/test/java/org/jclouds/softlayer/features/VirtualGuestApiExpectTest.java index 6627acfe37..a629fc7dfd 100644 --- a/providers/softlayer/src/test/java/org/jclouds/softlayer/features/VirtualGuestApiExpectTest.java +++ b/providers/softlayer/src/test/java/org/jclouds/softlayer/features/VirtualGuestApiExpectTest.java @@ -85,21 +85,6 @@ public class VirtualGuestApiExpectTest extends BaseSoftLayerApiExpectTest { assertEquals(result, new CreateVirtualGuestResponseTest().expected()); } - public void testCreateVirtualGuestWhenResponseIs4xx() { - - HttpRequest createVirtualGuest = HttpRequest.builder().method("POST") - .endpoint("https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest") - .addHeader("Accept", "application/json") - .addHeader("Authorization", "Basic aWRlbnRpdHk6Y3JlZGVudGlhbA==") - .payload(payloadFromResourceWithContentType("/virtual_guest_create.json", MediaType.APPLICATION_JSON)) - .build(); - - HttpResponse createVirtualGuestResponse = HttpResponse.builder().statusCode(404).build(); - SoftLayerApi api = requestSendsResponse(createVirtualGuest, createVirtualGuestResponse); - VirtualGuest virtualGuest = createVirtualGuest(); - assertNull(api.getVirtualGuestApi().createVirtualGuest(virtualGuest)); - } - public void testDeleteVirtualGuestWhenResponseIs2xx() { HttpRequest deleteVirtualGuest = HttpRequest.builder().method("GET") @@ -259,18 +244,20 @@ public class VirtualGuestApiExpectTest extends BaseSoftLayerApiExpectTest { assertTrue(api.getVirtualGuestApi().setTags(virtualGuest.getId(), ImmutableSet.of("test1", "test2", "test3"))); } - public void testSetTagsOnVirtualGuestWhenResponseIs4xx() { + public void testSetNotesOnVirtualGuestWhenResponseIs2xx() { - HttpRequest setTagsOnVirtualGuest = HttpRequest.builder().method("POST") - .endpoint("https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest/1301396/setTags") + HttpRequest setNodesOnVirtualGuest = HttpRequest.builder().method("POST") + .endpoint("https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest/1301396/editObject") .addHeader("Accept", "application/json") .addHeader("Authorization", "Basic aWRlbnRpdHk6Y3JlZGVudGlhbA==") - .payload(payloadFromResourceWithContentType("/virtual_guest_set_tags.json", MediaType.APPLICATION_JSON)) + .payload(payloadFromResourceWithContentType("/virtual_guest_set_notes.json", MediaType.APPLICATION_JSON)) .build(); - HttpResponse setTagsOnVirtualGuestResponse = HttpResponse.builder().statusCode(404).build(); - SoftLayerApi api = requestSendsResponse(setTagsOnVirtualGuest, setTagsOnVirtualGuestResponse); + HttpResponse setNotesOnVirtualGuestResponse = HttpResponse.builder().statusCode(200) + .payload("true").build(); + + SoftLayerApi api = requestSendsResponse(setNodesOnVirtualGuest, setNotesOnVirtualGuestResponse); VirtualGuest virtualGuest = createVirtualGuest(); - assertFalse(api.getVirtualGuestApi().setTags(virtualGuest.getId(), ImmutableSet.of("test1", "test2", "test3"))); + assertTrue(api.getVirtualGuestApi().setNotes(virtualGuest.getId(), "some notes")); } } diff --git a/providers/softlayer/src/test/java/org/jclouds/softlayer/features/VirtualGuestApiLiveTest.java b/providers/softlayer/src/test/java/org/jclouds/softlayer/features/VirtualGuestApiLiveTest.java index c3fffd251f..bd468c10e1 100644 --- a/providers/softlayer/src/test/java/org/jclouds/softlayer/features/VirtualGuestApiLiveTest.java +++ b/providers/softlayer/src/test/java/org/jclouds/softlayer/features/VirtualGuestApiLiveTest.java @@ -36,6 +36,7 @@ import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import com.google.common.base.Predicate; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.inject.Injector; @@ -125,6 +126,15 @@ public class VirtualGuestApiLiveTest extends BaseSoftLayerApiLiveTest { } @Test(dependsOnMethods = "testSetTagsOnVirtualGuest") + public void testSetNotesOnVirtualGuest() throws Exception { + // Test with maximum allowed notes length - 1000 characters. + String notes = Strings.padStart("", 1000, 'x'); + assertTrue(virtualGuestApi.setNotes(virtualGuest.getId(), notes)); + VirtualGuest found = virtualGuestApi.getNotes(virtualGuest.getId()); + assertEquals(found.getNotes(), notes); + } + + @Test(dependsOnMethods = "testSetNotesOnVirtualGuest") public void testPauseVirtualGuest() throws Exception { virtualGuestApi.pauseVirtualGuest(virtualGuest.getId()); checkState(retry(new Predicate() { diff --git a/providers/softlayer/src/test/resources/virtual_guest_set_notes.json b/providers/softlayer/src/test/resources/virtual_guest_set_notes.json new file mode 100644 index 0000000000..a877dfb979 --- /dev/null +++ b/providers/softlayer/src/test/resources/virtual_guest_set_notes.json @@ -0,0 +1 @@ +{"parameters":[{"notes":"some notes"}]} \ No newline at end of file