Implement setting and retrieving the notes property on Softlayer machines

This commit is contained in:
Svetoslav Neykov 2015-05-28 22:24:50 +03:00 committed by Ignasi Barrera
parent 8cdba226af
commit 06f1b13200
7 changed files with 201 additions and 28 deletions

View File

@ -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 extends HttpRequest> 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))));
}
}

View File

@ -25,14 +25,15 @@ import static com.google.common.collect.Iterables.get;
import static com.google.common.collect.Iterables.tryFind; import static com.google.common.collect.Iterables.tryFind;
import static java.lang.Math.round; import static java.lang.Math.round;
import static java.lang.String.format; 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.getCores;
import static org.jclouds.compute.util.ComputeServiceUtils.getSpace; 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_ACTIVE_TRANSACTIONS_DELAY;
import static org.jclouds.softlayer.reference.SoftLayerConstants.PROPERTY_SOFTLAYER_VIRTUALGUEST_LOGIN_DETAILS_DELAY; import static org.jclouds.softlayer.reference.SoftLayerConstants.PROPERTY_SOFTLAYER_VIRTUALGUEST_LOGIN_DETAILS_DELAY;
import static org.jclouds.util.Predicates2.retry; import static org.jclouds.util.Predicates2.retry;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; 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.Processor;
import org.jclouds.compute.domain.Template; import org.jclouds.compute.domain.Template;
import org.jclouds.compute.domain.Volume; import org.jclouds.compute.domain.Volume;
import org.jclouds.compute.domain.Volume.Type;
import org.jclouds.compute.domain.internal.VolumeImpl; import org.jclouds.compute.domain.internal.VolumeImpl;
import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.domain.LoginCredentials; 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.Optional;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.collect.ComparisonChain; import com.google.common.collect.ComparisonChain;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
@ -92,6 +95,8 @@ import com.google.common.collect.Sets;
public class SoftLayerComputeServiceAdapter implements public class SoftLayerComputeServiceAdapter implements
ComputeServiceAdapter<VirtualGuest, Hardware, OperatingSystem, Datacenter> { ComputeServiceAdapter<VirtualGuest, Hardware, OperatingSystem, Datacenter> {
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"; private static final String BOOTABLE_DEVICE = "0";
public static final String DEFAULT_DISK_TYPE = "LOCAL"; public static final String DEFAULT_DISK_TYPE = "LOCAL";
public static final int DEFAULT_MAX_PORT_SPEED = 100; 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 Datacenter datacenter = Datacenter.builder().name(template.getLocation().getId()).build();
final String imageId = template.getImage().getId(); final String imageId = template.getImage().getId();
int cores = (int) template.getHardware().getProcessors().get(0).getCores(); 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) .domain(domainName)
.hostname(name) .hostname(name)
.hourlyBillingFlag(hourlyBillingFlag) .hourlyBillingFlag(hourlyBillingFlag)
@ -213,6 +219,11 @@ public class SoftLayerComputeServiceAdapter implements
api.getVirtualGuestApi().setTags(result.getId(), templateOptions.getTags()); 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()); logger.debug(">> awaiting login details for virtualGuest(%s)", result.getId());
boolean orderInSystem = loginDetailsTester.apply(result); boolean orderInSystem = loginDetailsTester.apply(result);
logger.trace("<< VirtualGuest(%s) complete(%s)", result.getId(), orderInSystem); logger.trace("<< VirtualGuest(%s) complete(%s)", result.getId(), orderInSystem);
@ -227,10 +238,22 @@ public class SoftLayerComputeServiceAdapter implements
} }
result = api.getVirtualGuestApi().getVirtualGuest(result.getId()); result = api.getVirtualGuestApi().getVirtualGuest(result.getId());
Password pwd = get(result.getOperatingSystem().getPasswords(), 0); Password pwd = get(result.getOperatingSystem().getPasswords(), 0);
return new NodeAndInitialCredentials(result, result.getId() + "", return new NodeAndInitialCredentials<VirtualGuest>(result, result.getId() + "",
LoginCredentials.builder().user(pwd.getUsername()).password(pwd.getPassword()).build()); LoginCredentials.builder().user(pwd.getUsername()).password(pwd.getPassword()).build());
} }
private String getNotes(SoftLayerTemplateOptions templateOptions) {
String notes = null;
Map<String, String> 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 @Override
public Iterable<Hardware> listHardwareProfiles() { public Iterable<Hardware> listHardwareProfiles() {
ContainerVirtualGuestConfiguration virtualGuestConfiguration = createObjectOptionsSupplier.get(); ContainerVirtualGuestConfiguration virtualGuestConfiguration = createObjectOptionsSupplier.get();

View File

@ -33,6 +33,7 @@ import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.Fallback; import org.jclouds.rest.annotations.Fallback;
import org.jclouds.rest.annotations.QueryParams; import org.jclouds.rest.annotations.QueryParams;
import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.softlayer.binders.NotesToJson;
import org.jclouds.softlayer.binders.TagToJson; import org.jclouds.softlayer.binders.TagToJson;
import org.jclouds.softlayer.binders.VirtualGuestToJson; import org.jclouds.softlayer.binders.VirtualGuestToJson;
import org.jclouds.softlayer.domain.ContainerVirtualGuestConfiguration; import org.jclouds.softlayer.domain.ContainerVirtualGuestConfiguration;
@ -53,6 +54,8 @@ public interface VirtualGuestApi {
"statusId;operatingSystem.passwords;primaryBackendIpAddress;primaryIpAddress;activeTransactionCount;" + "statusId;operatingSystem.passwords;primaryBackendIpAddress;primaryIpAddress;activeTransactionCount;" +
"blockDevices.diskImage;datacenter;tagReferences;privateNetworkOnlyFlag;sshKeys"; "blockDevices.diskImage;datacenter;tagReferences;privateNetworkOnlyFlag;sshKeys";
String NOTES_MASK = "id;notes";
/** /**
* Enables the creation of computing instances on an account. * 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. * @param virtualGuest this data type presents the structure in which all virtual guests will be presented.
@ -63,7 +66,6 @@ public interface VirtualGuestApi {
@POST @POST
@Path("SoftLayer_Virtual_Guest") @Path("SoftLayer_Virtual_Guest")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Fallback(Fallbacks.NullOnNotFoundOr404.class)
VirtualGuest createVirtualGuest(@BinderParam(VirtualGuestToJson.class) VirtualGuest virtualGuest); VirtualGuest createVirtualGuest(@BinderParam(VirtualGuestToJson.class) VirtualGuest virtualGuest);
/** /**
@ -138,7 +140,7 @@ public interface VirtualGuestApi {
void resumeVirtualGuest(@PathParam("id") long id); void resumeVirtualGuest(@PathParam("id") long id);
/** /**
* Resume the guest. * Set the tags on the instance
* *
* @param id * @param id
* id of the virtual guest * id of the virtual guest
@ -147,6 +149,34 @@ public interface VirtualGuestApi {
@POST @POST
@Path("/SoftLayer_Virtual_Guest/{id}/setTags") @Path("/SoftLayer_Virtual_Guest/{id}/setTags")
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@Fallback(Fallbacks.FalseOnNotFoundOr404.class)
boolean setTags(@PathParam("id") long id, @BinderParam(TagToJson.class) Set<String> tags); boolean setTags(@PathParam("id") long id, @BinderParam(TagToJson.class) Set<String> 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);
} }

View File

@ -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);
}
}

View File

@ -85,21 +85,6 @@ public class VirtualGuestApiExpectTest extends BaseSoftLayerApiExpectTest {
assertEquals(result, new CreateVirtualGuestResponseTest().expected()); 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() { public void testDeleteVirtualGuestWhenResponseIs2xx() {
HttpRequest deleteVirtualGuest = HttpRequest.builder().method("GET") 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"))); assertTrue(api.getVirtualGuestApi().setTags(virtualGuest.getId(), ImmutableSet.of("test1", "test2", "test3")));
} }
public void testSetTagsOnVirtualGuestWhenResponseIs4xx() { public void testSetNotesOnVirtualGuestWhenResponseIs2xx() {
HttpRequest setTagsOnVirtualGuest = HttpRequest.builder().method("POST") HttpRequest setNodesOnVirtualGuest = HttpRequest.builder().method("POST")
.endpoint("https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest/1301396/setTags") .endpoint("https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest/1301396/editObject")
.addHeader("Accept", "application/json") .addHeader("Accept", "application/json")
.addHeader("Authorization", "Basic aWRlbnRpdHk6Y3JlZGVudGlhbA==") .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(); .build();
HttpResponse setTagsOnVirtualGuestResponse = HttpResponse.builder().statusCode(404).build(); HttpResponse setNotesOnVirtualGuestResponse = HttpResponse.builder().statusCode(200)
SoftLayerApi api = requestSendsResponse(setTagsOnVirtualGuest, setTagsOnVirtualGuestResponse); .payload("true").build();
SoftLayerApi api = requestSendsResponse(setNodesOnVirtualGuest, setNotesOnVirtualGuestResponse);
VirtualGuest virtualGuest = createVirtualGuest(); VirtualGuest virtualGuest = createVirtualGuest();
assertFalse(api.getVirtualGuestApi().setTags(virtualGuest.getId(), ImmutableSet.of("test1", "test2", "test3"))); assertTrue(api.getVirtualGuestApi().setNotes(virtualGuest.getId(), "some notes"));
} }
} }

View File

@ -36,6 +36,7 @@ import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.inject.Injector; import com.google.inject.Injector;
@ -125,6 +126,15 @@ public class VirtualGuestApiLiveTest extends BaseSoftLayerApiLiveTest {
} }
@Test(dependsOnMethods = "testSetTagsOnVirtualGuest") @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 { public void testPauseVirtualGuest() throws Exception {
virtualGuestApi.pauseVirtualGuest(virtualGuest.getId()); virtualGuestApi.pauseVirtualGuest(virtualGuest.getId());
checkState(retry(new Predicate<VirtualGuest>() { checkState(retry(new Predicate<VirtualGuest>() {

View File

@ -0,0 +1 @@
{"parameters":[{"notes":"some notes"}]}