From b7d76e7b8d91175d0f1291aff904fbb6b6b18d5f Mon Sep 17 00:00:00 2001 From: Everett Toews Date: Fri, 18 Jan 2013 17:00:28 -0600 Subject: [PATCH] The Metadata API for Load Balancers and Nodes in Rackspace Cloud Load Balancers. --- .../binders/BindMetadataToJsonPayload.java | 72 ++++++++++++ .../domain/LoadBalancer.java | 34 +++--- .../domain/LoadBalancerRequest.java | 25 +++- .../cloudloadbalancers/domain/Metadata.java | 107 ++++++------------ .../cloudloadbalancers/domain/Node.java | 24 +++- .../domain/internal/BaseLoadBalancer.java | 28 +---- .../features/LoadBalancerApi.java | 42 +++++++ .../features/LoadBalancerAsyncApi.java | 65 +++++++++++ .../cloudloadbalancers/features/NodeApi.java | 41 +++++++ .../features/NodeAsyncApi.java | 67 ++++++++++- .../functions/ConvertLB.java | 10 +- .../cloudloadbalancers/functions/LB.java | 3 + .../functions/ParseMetadata.java | 88 ++++++++++++++ .../functions/ParseNode.java | 87 ++++++++++++++ .../features/LoadBalancerApiExpectTest.java | 86 +++++++++++++- .../features/LoadBalancerApiLiveTest.java | 46 ++++++++ .../features/NodeApiExpectTest.java | 83 +++++++++++++- .../features/NodeApiLiveTest.java | 48 ++++++++ .../functions/ParseLoadBalancerTest.java | 10 +- .../src/test/resources/metadata-create.json | 1 + .../src/test/resources/metadata-list.json | 19 ++++ 21 files changed, 849 insertions(+), 137 deletions(-) create mode 100644 apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/binders/BindMetadataToJsonPayload.java create mode 100644 apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseMetadata.java create mode 100644 apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseNode.java create mode 100644 apis/rackspace-cloudloadbalancers/src/test/resources/metadata-create.json create mode 100644 apis/rackspace-cloudloadbalancers/src/test/resources/metadata-list.json diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/binders/BindMetadataToJsonPayload.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/binders/BindMetadataToJsonPayload.java new file mode 100644 index 0000000000..a1257584d2 --- /dev/null +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/binders/BindMetadataToJsonPayload.java @@ -0,0 +1,72 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.rackspace.cloudloadbalancers.binders; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.http.HttpRequest; +import org.jclouds.json.Json; +import org.jclouds.rest.Binder; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + +/** + * Binds the metadata to the request as a JSON payload. + * + * @author Everett Toews + */ +@Singleton +public class BindMetadataToJsonPayload implements Binder { + + protected final Json jsonBinder; + + @Inject + public BindMetadataToJsonPayload(Json jsonBinder) { + this.jsonBinder = checkNotNull(jsonBinder, "jsonBinder"); + } + + @SuppressWarnings("unchecked") + @Override + public R bindToRequest(R request, Object input) { + checkArgument(checkNotNull(input, "input") instanceof Map, "This binder is only valid for Map"); + checkNotNull(request, "request"); + + Map metadata = (Map) input; + List> clbMetadata = Lists.newArrayList(); + + for (String key: metadata.keySet()) { + clbMetadata.add(ImmutableMap. of( + "key", key, + "value", metadata.get(key))); + } + + String json = jsonBinder.toJson(ImmutableMap.of("metadata", clbMetadata)); + request.setPayload(json); + request.getPayload().getContentMetadata().setContentType("application/json"); + return request; + } +} diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/LoadBalancer.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/LoadBalancer.java index 1c6536693b..34340a0945 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/LoadBalancer.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/LoadBalancer.java @@ -50,6 +50,7 @@ public class LoadBalancer extends BaseLoadBalancer { private final SSLTermination sslTermination; private final SourceAddresses sourceAddresses; private final Set accessRules; + private final Metadata metadata; public LoadBalancer(String region, int id, String name, String protocol, @Nullable Integer port, Set nodes, @Nullable Integer timeout, @Nullable Boolean halfClosed, @Nullable Algorithm algorithm, Status status, @@ -57,10 +58,9 @@ public class LoadBalancer extends BaseLoadBalancer { String clusterName, Date created, Date updated, @Nullable Map connectionLogging, @Nullable ConnectionThrottle connectionThrottle, boolean contentCaching, int nodeCount, @Nullable HealthMonitor healthMonitor, @Nullable SSLTermination sslTermination, - SourceAddresses sourceAddresses, Set accessRules, - @Nullable Set metadata) { + SourceAddresses sourceAddresses, Set accessRules, Metadata metadata) { super(name, protocol, port, nodes, algorithm, timeout, halfClosed, sessionPersistenceType, connectionLogging, - connectionThrottle, healthMonitor, metadata); + connectionThrottle, healthMonitor); this.region = checkNotNull(region, "region"); checkArgument(id != -1, "id must be specified"); this.id = id; @@ -74,6 +74,7 @@ public class LoadBalancer extends BaseLoadBalancer { this.sslTermination = sslTermination; this.sourceAddresses = sourceAddresses; this.accessRules = accessRules == null ? ImmutableSet. of() : ImmutableSet.copyOf(accessRules); + this.metadata = metadata == null ? new Metadata() : metadata; } public String getRegion() { @@ -153,10 +154,20 @@ public class LoadBalancer extends BaseLoadBalancer { return sourceAddresses; } + /** + * @see AccessRule + */ public Set getAccessRules() { return accessRules; } + /** + * @see Metadata + */ + public Metadata getMetadata() { + return metadata; + } + protected ToStringHelper string() { return Objects.toStringHelper(this).omitNullValues().add("id", id).add("region", region).add("status", status) .add("name", name).add("protocol", protocol).add("port", port).add("nodeCount", getNodeCount()) @@ -260,6 +271,7 @@ public class LoadBalancer extends BaseLoadBalancer { private SSLTermination sslTermination; private SourceAddresses sourceAddresses; private Set accessRules; + private Metadata metadata; public Builder region(String region) { this.region = region; @@ -324,6 +336,10 @@ public class LoadBalancer extends BaseLoadBalancer { return this; } + public Builder metadata(Metadata metadata) { + this.metadata = checkNotNull(metadata, "metadata"); + return this; + } public LoadBalancer build() { return new LoadBalancer(region, id, name, protocol, port, nodes, timeout, halfClosed, algorithm, status, @@ -344,8 +360,8 @@ public class LoadBalancer extends BaseLoadBalancer { * {@inheritDoc} */ @Override - public Builder node(Node nodes) { - this.nodes.add(checkNotNull(nodes, "nodes")); + public Builder node(Node node) { + this.nodes.add(checkNotNull(node, "nodes")); return this; } @@ -429,14 +445,6 @@ public class LoadBalancer extends BaseLoadBalancer { return Builder.class.cast(super.healthMonitor(healthMonitor)); } - /** - * {@inheritDoc} - */ - @Override - public Builder metadata(@Nullable Set metadata) { - return Builder.class.cast(super.metadata(metadata)); - } - @Override public Builder from(LoadBalancer in) { return Builder.class.cast(super.from(in)).region(in.getRegion()).id(in.getId()).status(in.getStatus()) diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/LoadBalancerRequest.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/LoadBalancerRequest.java index 916208d12d..5f767d3700 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/LoadBalancerRequest.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/LoadBalancerRequest.java @@ -41,13 +41,14 @@ public class LoadBalancerRequest extends BaseLoadBalancer> virtualIps; private final Set accessRules; + private final Map metadata; public LoadBalancerRequest(String name, String protocol, @Nullable Integer port, Set nodes, @Nullable Algorithm algorithm, @Nullable Integer timeout, @Nullable Boolean halfClosed, @Nullable Map sessionPersistenceType, @Nullable Map connectionLogging, @Nullable ConnectionThrottle connectionThrottle, @Nullable HealthMonitor healthMonitor, @Nullable Set accessRules, - @Nullable Set metadata, VirtualIP.Type virtualIPType, Integer virtualIPId) { + @Nullable Map metadata, VirtualIP.Type virtualIPType, Integer virtualIPId) { this(name, protocol, port, nodes, algorithm, timeout, halfClosed, sessionPersistenceType, connectionLogging, connectionThrottle, healthMonitor, accessRules, metadata, getVirtualIPsFromOptions(virtualIPType, virtualIPId)); @@ -58,11 +59,20 @@ public class LoadBalancerRequest extends BaseLoadBalancer sessionPersistenceType, @Nullable Map connectionLogging, @Nullable ConnectionThrottle connectionThrottle, @Nullable HealthMonitor healthMonitor, @Nullable Set accessRules, - @Nullable Set metadata, Set> virtualIPsFromOptions) { + @Nullable Map metadata, Set> virtualIPsFromOptions) { super(name, protocol, port, nodes, algorithm, timeout, halfClosed, sessionPersistenceType, connectionLogging, - connectionThrottle, healthMonitor, metadata); + connectionThrottle, healthMonitor); this.virtualIps = checkNotNull(virtualIPsFromOptions, "virtualIPsFromOptions"); this.accessRules = accessRules; + this.metadata = metadata; + } + + public Map getMetadata() { + return metadata != null ? metadata : ImmutableMap. of(); + } + + public Set getAccessRules() { + return accessRules != null ? accessRules : ImmutableSet. of(); } static Set> getVirtualIPsFromOptions(VirtualIP.Type virtualIPType, Integer virtualIPId) { @@ -95,6 +105,7 @@ public class LoadBalancerRequest extends BaseLoadBalancer> virtualIps; private Set accessRules; + private Map metadata; /** * @see VirtualIP @@ -131,6 +142,14 @@ public class LoadBalancerRequest extends BaseLoadBalancer metadata) { + this.metadata = ImmutableMap. copyOf(checkNotNull(metadata, "metadata")); + return this; + } + public LoadBalancerRequest build() { if (virtualIps == null) { return new LoadBalancerRequest(name, protocol, port, nodes, algorithm, timeout, halfClosed, diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/Metadata.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/Metadata.java index bc4540567a..81ce85b1f7 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/Metadata.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/Metadata.java @@ -18,94 +18,53 @@ */ package org.jclouds.rackspace.cloudloadbalancers.domain; -import com.google.common.base.Objects; -import com.google.common.base.Objects.ToStringHelper; +import java.util.Map; +import java.util.Set; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; /** + * Key and value must be 256 characters or less. All UTF-8 characters are valid. + *

+ * Use the *Id methods when you need to get a metadata id for updating and removal. + * * @author Everett Toews */ -public class Metadata { - private int id; - private String key; - private String value; +public class Metadata extends ForwardingMap { + private Map metadata = Maps.newHashMap(); + private Map keyToId = Maps.newHashMap(); - private Metadata(Integer id, String key, String value) { - this.id = id; - this.key = key; - this.value = value; + public Metadata(Metadata metadata) { + super(); + this.metadata.putAll(metadata); } - public int getId() { - return id; - } - - public String getKey() { - return key; - } - - public String getValue() { - return value; + public Metadata() { + super(); } @Override - public int hashCode() { - return Objects.hashCode(id); + protected Map delegate() { + return metadata; + } + + public int getId(String key) { + return keyToId.get(key); + } + + public Integer putId(String key, int id) { + return keyToId.put(key, id); } - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; - Metadata that = Metadata.class.cast(obj); + public Iterable getIds() { + Set ids = Sets.newHashSet(); - return Objects.equal(this.id, that.id); - } - - protected ToStringHelper string() { - return Objects.toStringHelper(this).omitNullValues() - .add("id", id).add("key", key).add("value", value); - } - - @Override - public String toString() { - return string().toString(); - } - - public static class Builder { - private Integer id; - private String key; - private String value; - - public Builder id(Integer id) { - this.id = id; - return this; + for (String key: keyToId.keySet()) { + ids.add(keyToId.get(key)); } - - public Builder key(String key) { - this.key = key; - return this; - } - - public Builder value(String value) { - this.value = value; - return this; - } - - public Metadata build() { - return new Metadata(id, key, value); - } - - public Builder from(Metadata in) { - return id(in.getId()).key(in.getKey()).value(in.getValue()); - } - } - - public static Builder builder() { - return new Builder(); - } - - public Builder toBuilder() { - return new Builder().from(this); + + return ids; } } diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/Node.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/Node.java index bf0be57b19..abd5704fb3 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/Node.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/Node.java @@ -56,16 +56,18 @@ public class Node extends BaseNode { private int id; private Status status; - + private Metadata metadata = new Metadata(); + // for serialization only - Node() { + protected Node() { } - public Node(int id, String address, int port, Condition condition, Type type, Status status, Integer weight) { + public Node(String address, int port, Condition condition, Type type, Integer weight, int id, Status status, Metadata metadata) { super(address, port, condition, type, weight); checkArgument(id != -1, "id must be specified"); this.id = id; this.status = checkNotNull(status, "status"); + this.metadata = metadata != null ? metadata : this.metadata; } public int getId() { @@ -76,10 +78,14 @@ public class Node extends BaseNode { return status; } + public Metadata getMetadata() { + return metadata; + } + protected ToStringHelper string() { return Objects.toStringHelper(this).omitNullValues() .add("id", id).add("address", address).add("port", port).add("condition", condition) - .add("type", type).add("weight", weight).add("status", status); + .add("type", type).add("weight", weight).add("status", status).add("metadata", metadata); } @Override @@ -136,6 +142,7 @@ public class Node extends BaseNode { public static class Builder extends BaseNode.Builder { private int id = -1; private Status status; + private Metadata metadata; public Builder id(int id) { this.id = id; @@ -150,9 +157,14 @@ public class Node extends BaseNode { return this; } + public Builder metadata(Metadata metadata) { + this.metadata = checkNotNull(metadata, "metadata"); + return this; + } + @Override public Node build() { - return new Node(id, address, port, condition, type, status, weight); + return new Node(address, port, condition, type, weight, id, status, metadata); } /** @@ -196,7 +208,7 @@ public class Node extends BaseNode { } @Override public Builder from(Node in) { - return Builder.class.cast(super.from(in)).id(in.getId()).status(in.getStatus()); + return Builder.class.cast(super.from(in)).id(in.getId()).status(in.getStatus()).metadata(in.getMetadata()); } } diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/internal/BaseLoadBalancer.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/internal/BaseLoadBalancer.java index 4808f53d31..9681645648 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/internal/BaseLoadBalancer.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/domain/internal/BaseLoadBalancer.java @@ -28,7 +28,6 @@ import org.jclouds.javax.annotation.Nullable; import org.jclouds.rackspace.cloudloadbalancers.domain.ConnectionThrottle; import org.jclouds.rackspace.cloudloadbalancers.domain.HealthMonitor; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancer; -import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; import org.jclouds.rackspace.cloudloadbalancers.features.LoadBalancerApi; import com.google.common.base.Objects; @@ -61,7 +60,6 @@ public class BaseLoadBalancer, T extends BaseLoadBalancer< protected Map connectionLogging; protected ConnectionThrottle connectionThrottle; protected HealthMonitor healthMonitor; - protected Set metadata; // for serialization only protected BaseLoadBalancer() { @@ -71,7 +69,7 @@ public class BaseLoadBalancer, T extends BaseLoadBalancer< @Nullable Algorithm algorithm, @Nullable Integer timeout, @Nullable Boolean halfClosed, @Nullable Map sessionPersistence, @Nullable Map connectionLogging, @Nullable ConnectionThrottle connectionThrottle, - @Nullable HealthMonitor healthMonitor, @Nullable Set metadata) { + @Nullable HealthMonitor healthMonitor) { this.name = checkNotNull(name, "name"); this.protocol = protocol;// null on deleted LB this.port = port;// null on deleted LB @@ -83,7 +81,6 @@ public class BaseLoadBalancer, T extends BaseLoadBalancer< this.connectionLogging = connectionLogging; this.connectionThrottle = connectionThrottle; this.healthMonitor = healthMonitor; - this.metadata = metadata; } @Override @@ -167,20 +164,12 @@ public class BaseLoadBalancer, T extends BaseLoadBalancer< return healthMonitor; } - /** - * @return metadata, which may be null if metadata has not been set. - */ - @Nullable - public Set getMetadata() { - return metadata; - } - protected ToStringHelper string() { return Objects.toStringHelper(this).omitNullValues().add("name", name).add("protocol", protocol) .add("port", port).add("nodes", nodes).add("timeout", timeout).add("algorithm", algorithm) .add("timeout", timeout).add("sessionPersistenceType", getSessionPersistenceType()) .add("connectionLogging", connectionLogging).add("connectionThrottle", connectionThrottle) - .add("healthMonitor", healthMonitor).add("metadata", metadata); + .add("healthMonitor", healthMonitor); } @Override @@ -288,7 +277,6 @@ public class BaseLoadBalancer, T extends BaseLoadBalancer< protected Map connectionLogging; protected ConnectionThrottle connectionThrottle; protected HealthMonitor healthMonitor; - protected Set metadata; /** * Required. Name of the load balancer to create. The name must be 128 characters or less in length, and all @@ -414,17 +402,9 @@ public class BaseLoadBalancer, T extends BaseLoadBalancer< return this; } - /** - * Information (metadata) that can be associated with each load balancer for the client's personal use. - */ - public Builder metadata(@Nullable Set metadata) { - this.metadata = metadata; - return this; - } - public BaseLoadBalancer build() { return new BaseLoadBalancer(name, protocol, port, nodes, algorithm, timeout, halfClosed, - sessionPersistence, connectionLogging, connectionThrottle, healthMonitor, metadata); + sessionPersistence, connectionLogging, connectionThrottle, healthMonitor); } public Builder from(T baseLB) { @@ -432,7 +412,7 @@ public class BaseLoadBalancer, T extends BaseLoadBalancer< .algorithm(baseLB.getAlgorithm()).timeout(baseLB.getTimeout()).halfClosed(baseLB.isHalfClosed()) .nodes(baseLB.getNodes()).sessionPersistenceType(baseLB.getSessionPersistenceType()) .connectionLogging(baseLB.isConnectionLogging()).connectionThrottle(baseLB.getConnectionThrottle()) - .healthMonitor(baseLB.getHealthMonitor()).metadata(baseLB.getMetadata()); + .healthMonitor(baseLB.getHealthMonitor()); } } diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApi.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApi.java index bd6e092bba..3985f18ab7 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApi.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApi.java @@ -18,6 +18,8 @@ */ package org.jclouds.rackspace.cloudloadbalancers.features; +import java.util.Map; + import org.jclouds.collect.IterableWithMarker; import org.jclouds.collect.PagedIterable; import org.jclouds.http.HttpResponseException; @@ -25,6 +27,7 @@ import org.jclouds.openstack.v2_0.options.PaginationOptions; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancer; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancerAttributes; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancerRequest; +import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; /** * Provides synchronous access to CloudLoadBalancers LoadBalancer features. @@ -104,4 +107,43 @@ public interface LoadBalancerApi { * to remove */ void remove(int id); + + /** + * When a metadata item is added, it is assigned a unique identifier that can be used for mutating operations such + * as changing the value attribute or removing it. Key and value must be 256 characters or less. + * All UTF-8 characters are valid. + */ + Metadata createMetadata(int id, Map metadata); + + /** + * List a load balancer's metadata. + */ + Metadata getMetadata(int id); + + /** + * Update metadatum. Key and value must be 256 characters or less. All UTF-8 characters are valid. + * + * @return true on a successful update, false if the metadatum was not found + */ + boolean updateMetadatum(int id, int metadatumId, String value); + + /** + * Remove metadatum. + * + * @see LoadBalancerApi#remove(int, Iterable) + * + * @return true on a successful removal, false if the metadatum was not found + */ + boolean removeMetadatum(int id, int metadatumId); + + /** + * Batch delete metadata given the specified ids. + * + * The current default limit is ten ids per request. Any and all configuration data is immediately purged and is + * not recoverable. If one or more of the items in the list cannot be removed due to its current status, an + * exception is thrown along with the ids of the ones the system identified as potential failures for this request. + * + * @return true on a successful removal, false if the metadata was not found + */ + boolean removeMetadata(int id, Iterable metadataIds); } diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerAsyncApi.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerAsyncApi.java index c16f381155..b5e9d35f27 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerAsyncApi.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerAsyncApi.java @@ -18,6 +18,8 @@ */ package org.jclouds.rackspace.cloudloadbalancers.features; +import java.util.Map; + import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -25,9 +27,13 @@ import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import org.jclouds.Fallbacks.EmptyMapOnNotFoundOr404; import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404; +import org.jclouds.Fallbacks.FalseOnNotFoundOr404; import org.jclouds.Fallbacks.NullOnNotFoundOr404; import org.jclouds.Fallbacks.VoidOnNotFoundOr404; import org.jclouds.collect.IterableWithMarker; @@ -35,12 +41,18 @@ import org.jclouds.collect.PagedIterable; import org.jclouds.openstack.keystone.v2_0.KeystoneFallbacks.EmptyPaginatedCollectionOnNotFoundOr404; import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; import org.jclouds.openstack.v2_0.options.PaginationOptions; +import org.jclouds.rackspace.cloudloadbalancers.binders.BindMetadataToJsonPayload; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancer; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancerAttributes; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancerRequest; +import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; import org.jclouds.rackspace.cloudloadbalancers.functions.ParseLoadBalancer; import org.jclouds.rackspace.cloudloadbalancers.functions.ParseLoadBalancers; +import org.jclouds.rackspace.cloudloadbalancers.functions.ParseMetadata; +import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.Payload; +import org.jclouds.rest.annotations.PayloadParam; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.ResponseParser; import org.jclouds.rest.annotations.Transform; @@ -117,4 +129,57 @@ public interface LoadBalancerAsyncApi { @Consumes("*/*") ListenableFuture remove(@PathParam("id") int id); + /** + * @see LoadBalancerApi#createMetadata(int, Iterable) + */ + @POST + @Consumes(MediaType.APPLICATION_JSON) + @ResponseParser(ParseMetadata.class) + @Fallback(EmptyMapOnNotFoundOr404.class) + @Path("/loadbalancers/{id}/metadata") + ListenableFuture createMetadata( + @PathParam("id") int id, + @BinderParam(BindMetadataToJsonPayload.class) Map metadata); + + /** + * @see LoadBalancerApi#getMetadata(int) + */ + @GET + @Consumes(MediaType.APPLICATION_JSON) + @ResponseParser(ParseMetadata.class) + @Fallback(EmptyMapOnNotFoundOr404.class) + @Path("/loadbalancers/{id}/metadata") + ListenableFuture getMetadata(@PathParam("id") int lb); + + /** + * @see LoadBalancerApi#updateMetadatum(int, int, String) + */ + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes("*/*") + @Fallback(FalseOnNotFoundOr404.class) + @Payload("%7B\"meta\":%7B\"value\":\"{value}\"%7D%7D") + @Path("/loadbalancers/{id}/metadata/{metadatumId}") + ListenableFuture updateMetadatum(@PathParam("id") int id, + @PathParam("metadatumId") int metadatumId, + @PayloadParam("value") String value); + + /** + * @see LoadBalancerApi#removeMetadatum(int, int) + */ + @DELETE + @Fallback(FalseOnNotFoundOr404.class) + @Consumes("*/*") + @Path("/loadbalancers/{id}/metadata/{metadatumId}") + ListenableFuture removeMetadatum(@PathParam("id") int id, @PathParam("metadatumId") int metadatumId); + + /** + * @see LoadBalancerApi#removeMetadata(int, Iterable) + */ + @DELETE + @Fallback(FalseOnNotFoundOr404.class) + @Consumes("*/*") + @Path("/loadbalancers/{id}/metadata") + ListenableFuture removeMetadata(@PathParam("id") int id, + @QueryParam("id") Iterable metadataIds); } diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApi.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApi.java index 22f65e87ae..a601dd9eca 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApi.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApi.java @@ -18,12 +18,14 @@ */ package org.jclouds.rackspace.cloudloadbalancers.features; +import java.util.Map; import java.util.Set; import org.jclouds.collect.IterableWithMarker; import org.jclouds.collect.PagedIterable; import org.jclouds.http.HttpResponseException; import org.jclouds.openstack.v2_0.options.PaginationOptions; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancerAttributes; +import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; import org.jclouds.rackspace.cloudloadbalancers.domain.Node; import org.jclouds.rackspace.cloudloadbalancers.domain.NodeAttributes; import org.jclouds.rackspace.cloudloadbalancers.domain.NodeRequest; @@ -111,4 +113,43 @@ public interface NodeApi { * nodes to remove */ void remove(Iterable ids); + + /** + * When a metadata item is added, it is assigned a unique identifier that can be used for mutating operations such + * as changing the value attribute or removing it. Key and value must be 256 characters or less. + * All UTF-8 characters are valid. + */ + Metadata createMetadata(int id, Map metadata); + + /** + * List a load balancer's metadata. + */ + Metadata getMetadata(int id); + + /** + * Update metadatum. Key and value must be 256 characters or less. All UTF-8 characters are valid. + * + * @return true on a successful update, false if the metadatum was not found + */ + boolean updateMetadatum(int id, int metadatumId, String value); + + /** + * Remove metadatum. + * + * @see NodeApi#remove(int, Iterable) + * + * @return true on a successful removal, false if the metadatum was not found + */ + boolean removeMetadatum(int id, int metadatumId); + + /** + * Batch delete metadata given the specified ids. + * + * The current default limit is ten ids per request. Any and all configuration data is immediately purged and is + * not recoverable. If one or more of the items in the list cannot be removed due to its current status, an + * exception is thrown along with the ids of the ones the system identified as potential failures for this request. + * + * @return true on a successful removal, false if the metadata was not found + */ + boolean removeMetadata(int id, Iterable metadataIds); } diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeAsyncApi.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeAsyncApi.java index e1fa433c30..6115c336c0 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeAsyncApi.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeAsyncApi.java @@ -18,6 +18,7 @@ */ package org.jclouds.rackspace.cloudloadbalancers.features; +import java.util.Map; import java.util.Set; import javax.ws.rs.Consumes; @@ -27,10 +28,13 @@ import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import org.jclouds.Fallbacks.EmptyMapOnNotFoundOr404; import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404; +import org.jclouds.Fallbacks.FalseOnNotFoundOr404; import org.jclouds.Fallbacks.NullOnNotFoundOr404; import org.jclouds.Fallbacks.VoidOnNotFoundOr404; import org.jclouds.collect.IterableWithMarker; @@ -38,12 +42,19 @@ import org.jclouds.collect.PagedIterable; import org.jclouds.openstack.keystone.v2_0.KeystoneFallbacks.EmptyPaginatedCollectionOnNotFoundOr404; import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest; import org.jclouds.openstack.v2_0.options.PaginationOptions; +import org.jclouds.rackspace.cloudloadbalancers.binders.BindMetadataToJsonPayload; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancer; +import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; import org.jclouds.rackspace.cloudloadbalancers.domain.Node; import org.jclouds.rackspace.cloudloadbalancers.domain.NodeAttributes; import org.jclouds.rackspace.cloudloadbalancers.domain.NodeRequest; +import org.jclouds.rackspace.cloudloadbalancers.functions.ParseMetadata; +import org.jclouds.rackspace.cloudloadbalancers.functions.ParseNode; import org.jclouds.rackspace.cloudloadbalancers.functions.ParseNodes; +import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.Payload; +import org.jclouds.rest.annotations.PayloadParam; import org.jclouds.rest.annotations.RequestFilters; import org.jclouds.rest.annotations.ResponseParser; import org.jclouds.rest.annotations.SelectJson; @@ -98,15 +109,15 @@ public interface NodeAsyncApi { @ResponseParser(ParseNodes.class) @Consumes(MediaType.APPLICATION_JSON) @Fallback(EmptyPaginatedCollectionOnNotFoundOr404.class) - @Path("/loadbalancers") + @Path("/nodes") ListenableFuture> list(PaginationOptions options); /** * @see NodeApi#get(int) */ @GET - @SelectJson("node") @Consumes(MediaType.APPLICATION_JSON) + @ResponseParser(ParseNode.class) @Path("/nodes/{id}") @Fallback(NullOnNotFoundOr404.class) ListenableFuture get(@PathParam("id") int id); @@ -129,5 +140,57 @@ public interface NodeAsyncApi { @Consumes("*/*") ListenableFuture remove(@QueryParam("id") Iterable ids); + /** + * @see NodeApi#createMetadata(int, Iterable) + */ + @POST + @Consumes(MediaType.APPLICATION_JSON) + @ResponseParser(ParseMetadata.class) + @Fallback(EmptyMapOnNotFoundOr404.class) + @Path("/nodes/{id}/metadata") + ListenableFuture createMetadata( + @PathParam("id") int id, + @BinderParam(BindMetadataToJsonPayload.class) Map metadata); + /** + * @see NodeApi#getMetadata(int) + */ + @GET + @Consumes(MediaType.APPLICATION_JSON) + @ResponseParser(ParseMetadata.class) + @Fallback(EmptyMapOnNotFoundOr404.class) + @Path("/nodes/{id}/metadata") + ListenableFuture getMetadata(@PathParam("id") int lb); + + /** + * @see NodeApi#updateMetadatum(int, int, String) + */ + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes("*/*") + @Fallback(FalseOnNotFoundOr404.class) + @Payload("%7B\"meta\":%7B\"value\":\"{value}\"%7D%7D") + @Path("/nodes/{id}/metadata/{metadatumId}") + ListenableFuture updateMetadatum(@PathParam("id") int id, + @PathParam("metadatumId") int metadatumId, + @PayloadParam("value") String value); + + /** + * @see NodeApi#removeMetadatum(int, int) + */ + @DELETE + @Fallback(FalseOnNotFoundOr404.class) + @Consumes("*/*") + @Path("/nodes/{id}/metadata/{metadatumId}") + ListenableFuture removeMetadatum(@PathParam("id") int id, @PathParam("metadatumId") int metadatumId); + + /** + * @see NodeApi#removeMetadata(int, Iterable) + */ + @DELETE + @Fallback(FalseOnNotFoundOr404.class) + @Consumes("*/*") + @Path("/nodes/{id}/metadata") + ListenableFuture removeMetadata(@PathParam("id") int id, + @QueryParam("id") Iterable metadataIds); } diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ConvertLB.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ConvertLB.java index e94bed500d..5e7bd2c810 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ConvertLB.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ConvertLB.java @@ -24,8 +24,9 @@ import javax.inject.Inject; import org.jclouds.logging.Logger; import org.jclouds.rackspace.cloudloadbalancers.domain.AccessRuleWithId; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancer; -import org.jclouds.rackspace.cloudloadbalancers.domain.VirtualIPWithId; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancer.Builder; +import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; +import org.jclouds.rackspace.cloudloadbalancers.domain.VirtualIPWithId; import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; @@ -58,8 +59,7 @@ public class ConvertLB implements Function { .protocol(lb.getProtocol()).port(lb.getPort()).nodeCount(lb.nodeCount).nodes(lb.getNodes()) .timeout(lb.getTimeout()).algorithm(lb.getAlgorithm()).halfClosed(lb.isHalfClosed()) .sessionPersistenceType(lb.getSessionPersistenceType()).connectionLogging(lb.isConnectionLogging()) - .connectionThrottle(lb.getConnectionThrottle()).healthMonitor(lb.getHealthMonitor()) - .metadata(lb.getMetadata()); + .connectionThrottle(lb.getConnectionThrottle()).healthMonitor(lb.getHealthMonitor()); if (lb.cluster.size() == 1) builder.clusterName(Iterables.get(lb.cluster.values(), 0)); @@ -81,6 +81,10 @@ public class ConvertLB implements Function { builder.virtualIPs(ImmutableSet. of()); else builder.virtualIPs(lb.virtualIps); + if (lb.metadata == null) + builder.metadata(new Metadata()); + else + builder.metadata(ParseMetadata.transformCLBMetadataToMetadata(lb.metadata)); return builder.build(); } diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/LB.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/LB.java index 20dc3f6b3c..3e3c72849c 100644 --- a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/LB.java +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/LB.java @@ -19,6 +19,7 @@ package org.jclouds.rackspace.cloudloadbalancers.functions; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.Set; @@ -29,6 +30,7 @@ import org.jclouds.rackspace.cloudloadbalancers.domain.SSLTermination; import org.jclouds.rackspace.cloudloadbalancers.domain.SourceAddresses; import org.jclouds.rackspace.cloudloadbalancers.domain.VirtualIPWithId; import org.jclouds.rackspace.cloudloadbalancers.domain.internal.BaseLoadBalancer; +import org.jclouds.rackspace.cloudloadbalancers.functions.ParseMetadata.CLBMetadata; import com.google.common.base.Objects; import com.google.common.collect.Maps; @@ -49,6 +51,7 @@ class LB extends BaseLoadBalancer { SSLTermination sslTermination; SourceAddresses sourceAddresses; Set accessList; + List metadata; @Override public int hashCode() { diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseMetadata.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseMetadata.java new file mode 100644 index 0000000000..2dab0f345a --- /dev/null +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseMetadata.java @@ -0,0 +1,88 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.rackspace.cloudloadbalancers.functions; + +import static org.jclouds.http.HttpUtils.releasePayload; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.jclouds.http.functions.ParseJson; +import org.jclouds.json.Json; +import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; + +import com.google.inject.TypeLiteral; + +/** + * @author Everett Toews + */ +public class ParseMetadata extends ParseJson { + + @Inject + public ParseMetadata(Json json, TypeLiteral type) { + super(json, type); + } + + @Override + public Metadata apply(HttpResponse response) { + Map> clbMetadata; + + try { + Type clbMetadataType = new TypeLiteral>>() {}.getType(); + clbMetadata = apply(response.getPayload().getInput(), clbMetadataType); + } + catch (IOException e) { + StringBuilder message = new StringBuilder(); + message.append("Error parsing response"); + logger.error(e, message.toString()); + throw new HttpResponseException(message.toString() + "\n" + response, null, response, e); + } + finally { + releasePayload(response); + } + + return transformCLBMetadataToMetadata(clbMetadata.get("metadata")); + } + + public static Metadata transformCLBMetadataToMetadata(List clbMetadatum) { + Metadata metadata = new Metadata(); + + for (CLBMetadata clbMetadata: clbMetadatum) { + metadata.put(clbMetadata.key, clbMetadata.value); + metadata.putId(clbMetadata.key, clbMetadata.id); + } + + return metadata; + } + + /** + * This class is here only to deal with the metadata format in CLB. + */ + public static class CLBMetadata { + private int id; + private String key; + private String value; + } +} diff --git a/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseNode.java b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseNode.java new file mode 100644 index 0000000000..663fcb844a --- /dev/null +++ b/apis/rackspace-cloudloadbalancers/src/main/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseNode.java @@ -0,0 +1,87 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds 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.rackspace.cloudloadbalancers.functions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.functions.ParseJson; +import org.jclouds.rackspace.cloudloadbalancers.domain.Node; +import org.jclouds.rackspace.cloudloadbalancers.domain.Node.Status; +import org.jclouds.rackspace.cloudloadbalancers.domain.internal.BaseNode; +import org.jclouds.rackspace.cloudloadbalancers.functions.ParseMetadata.CLBMetadata; +import org.jclouds.rest.InvocationContext; + +import com.google.common.base.Function; +import com.google.common.collect.Iterables; + +/** + * @author Everett Toews + */ +public class ParseNode implements Function, InvocationContext { + + private final ParseJson> json; + + @Inject + ParseNode(ParseJson> json) { + this.json = checkNotNull(json, "json"); + } + + @Override + public Node apply(HttpResponse response) { + Map map = json.apply(response); + + if (map == null || map.size() == 0) + return null; + + NodeWithCLBMetadata nodeWithCLBMetadata = Iterables.get(map.values(), 0); + Node node = Node.builder() + .address(nodeWithCLBMetadata.getAddress()) + .port(nodeWithCLBMetadata.getPort()) + .condition(nodeWithCLBMetadata.getCondition()) + .type(nodeWithCLBMetadata.getType()) + .weight(nodeWithCLBMetadata.getWeight()) + .id(nodeWithCLBMetadata.id) + .status(nodeWithCLBMetadata.status) + .metadata(ParseMetadata.transformCLBMetadataToMetadata(nodeWithCLBMetadata.metadata)) + .build(); + + return node; + } + + @Override + public ParseNode setContext(HttpRequest request) { + return this; + } + + /** + * This class is here only to deal with the metadata format in CLB. + */ + private static class NodeWithCLBMetadata extends BaseNode { + private int id; + private Status status; + private List metadata; + } +} diff --git a/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApiExpectTest.java b/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApiExpectTest.java index af9199072f..db10d1991a 100644 --- a/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApiExpectTest.java +++ b/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApiExpectTest.java @@ -19,6 +19,7 @@ package org.jclouds.rackspace.cloudloadbalancers.features; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; import java.net.URI; import java.util.Set; @@ -30,6 +31,7 @@ import org.jclouds.rackspace.cloudloadbalancers.CloudLoadBalancersApi; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancer; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancerAttributes; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancerRequest; +import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; import org.jclouds.rackspace.cloudloadbalancers.domain.NodeRequest; import org.jclouds.rackspace.cloudloadbalancers.domain.VirtualIP; import org.jclouds.rackspace.cloudloadbalancers.functions.ParseLoadBalancerTest; @@ -37,6 +39,7 @@ import org.jclouds.rackspace.cloudloadbalancers.functions.ParseLoadBalancersTest import org.jclouds.rackspace.cloudloadbalancers.internal.BaseCloudLoadBalancerApiExpectTest; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; /** @@ -55,7 +58,7 @@ public class LoadBalancerApiExpectTest extends BaseCloudLoadBalancerApiExpectTes ).getLoadBalancerApiForZone("DFW"); Set loadBalancers = api.list().concat().toSet(); - assertEquals(loadBalancers, testLoadBalancers()); + assertEquals(loadBalancers, getExpectedLoadBalancers()); } public void testGetLoadBalancer() throws Exception { @@ -68,7 +71,7 @@ public class LoadBalancerApiExpectTest extends BaseCloudLoadBalancerApiExpectTes ).getLoadBalancerApiForZone("DFW"); LoadBalancer loadBalancer = api.get(2000); - assertEquals(loadBalancer, testLoadBalancer()); + assertEquals(loadBalancer, getExpectedLoadBalancer()); } public void testCreateLoadBalancer() throws Exception { @@ -109,7 +112,7 @@ public class LoadBalancerApiExpectTest extends BaseCloudLoadBalancerApiExpectTes LoadBalancer loadBalancer = api.create(lbRequest); - assertEquals(loadBalancer, testLoadBalancer()); + assertEquals(loadBalancer, getExpectedLoadBalancer()); } public void testUpdateLoadBalancerAttributes() { @@ -146,11 +149,84 @@ public class LoadBalancerApiExpectTest extends BaseCloudLoadBalancerApiExpectTes api.remove(2000); } - private Object testLoadBalancer() { + public void testListMetadata() { + URI endpoint = URI.create("https://dfw.loadbalancers.api.rackspacecloud.com/v1.0/123123/loadbalancers/2000/metadata"); + LoadBalancerApi api = requestsSendResponses( + rackspaceAuthWithUsernameAndApiKey, + responseWithAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/metadata-list.json")).build() + ).getLoadBalancerApiForZone("DFW"); + + Metadata metadata = api.getMetadata(2000); + assertEquals(metadata, getExpectedMetadataWithIds()); + } + + public void testCreateMetadata() { + URI endpoint = URI.create("https://dfw.loadbalancers.api.rackspacecloud.com/v1.0/123123/loadbalancers/2000/metadata"); + LoadBalancerApi api = requestsSendResponses( + rackspaceAuthWithUsernameAndApiKey, + responseWithAccess, + authenticatedGET() + .method("POST") + .endpoint(endpoint) + .payload(payloadFromResourceWithContentType("/metadata-create.json", MediaType.APPLICATION_JSON)).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/metadata-list.json")).build() + ).getLoadBalancerApiForZone("DFW"); + + Metadata metadata = api.createMetadata(2000, getExpectedMetadata()); + assertEquals(metadata, getExpectedMetadataWithIds()); + } + + public void testRemoveSingleMetadata() { + URI endpoint = URI.create("https://dfw.loadbalancers.api.rackspacecloud.com/v1.0/123123/loadbalancers/2000/metadata/23"); + LoadBalancerApi api = requestsSendResponses( + rackspaceAuthWithUsernameAndApiKey, + responseWithAccess, + authenticatedGET().method("DELETE").endpoint(endpoint).replaceHeader("Accept", MediaType.WILDCARD).build(), + HttpResponse.builder().statusCode(200).build() + ).getLoadBalancerApiForZone("DFW"); + + assertTrue(api.removeMetadatum(2000, 23)); + } + + public void testRemoveManyMetadata() { + URI endpoint = URI.create("https://dfw.loadbalancers.api.rackspacecloud.com/v1.0/123123/loadbalancers/2000/metadata?id=23&id=24"); + LoadBalancerApi api = requestsSendResponses( + rackspaceAuthWithUsernameAndApiKey, + responseWithAccess, + authenticatedGET().method("DELETE").endpoint(endpoint).replaceHeader("Accept", MediaType.WILDCARD).build(), + HttpResponse.builder().statusCode(200).build() + ).getLoadBalancerApiForZone("DFW"); + + + + assertTrue(api.removeMetadata(2000, ImmutableList. of(23, 24))); + } + + private Object getExpectedLoadBalancer() { return new ParseLoadBalancerTest().expected(); } - private Set testLoadBalancers() { + private Set getExpectedLoadBalancers() { return new ParseLoadBalancersTest().data(); } + + private Metadata getExpectedMetadata() { + Metadata metadata = new Metadata(); + metadata.put("color", "red"); + metadata.put("label", "web-load-balancer"); + metadata.put("os", "ubuntu"); + + return metadata; + } + + private Metadata getExpectedMetadataWithIds() { + Metadata metadata = getExpectedMetadata(); + metadata.putId("color", 1); + metadata.putId("label", 2); + metadata.putId("os", 3); + + return metadata; + } } diff --git a/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApiLiveTest.java b/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApiLiveTest.java index 04d9e0f60b..4c7d582183 100644 --- a/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApiLiveTest.java +++ b/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/LoadBalancerApiLiveTest.java @@ -22,20 +22,25 @@ import static org.jclouds.rackspace.cloudloadbalancers.predicates.LoadBalancerPr import static org.jclouds.rackspace.cloudloadbalancers.predicates.LoadBalancerPredicates.awaitDeleted; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +import java.util.Map; import java.util.Set; import java.util.logging.Logger; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancer; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancerAttributes; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancerRequest; +import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; import org.jclouds.rackspace.cloudloadbalancers.domain.NodeRequest; import org.jclouds.rackspace.cloudloadbalancers.domain.VirtualIP.Type; import org.jclouds.rackspace.cloudloadbalancers.internal.BaseCloudLoadBalancersApiLiveTest; import org.testng.annotations.AfterGroups; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; @@ -146,6 +151,39 @@ public class LoadBalancerApiLiveTest extends BaseCloudLoadBalancersApiLiveTest { } } } + + @Test(dependsOnMethods = "testListLoadBalancers") + public void testLoadBalancerMetadata() throws Exception { + for (LoadBalancer lb: lbs) { + Map metadataMap = ImmutableMap. of( + "key1", "value1", + "key2", "value2", + "key3", "value3"); + + Metadata metadata = clbApi.getLoadBalancerApiForZone(lb.getRegion()).createMetadata(lb.getId(), metadataMap); + assertEquals(metadata, getExpectedMetadata()); + assertTrue(awaitAvailable(clbApi.getLoadBalancerApiForZone(lb.getRegion())).apply(lb)); + + metadata = clbApi.getLoadBalancerApiForZone(lb.getRegion()).getMetadata(lb.getId()); + assertEquals(metadata, getExpectedMetadata()); + + assertTrue(clbApi.getLoadBalancerApiForZone(lb.getRegion()).updateMetadatum(lb.getId(), metadata.getId("key1"), "key1-updated")); + assertTrue(awaitAvailable(clbApi.getLoadBalancerApiForZone(lb.getRegion())).apply(lb)); + metadata = clbApi.getLoadBalancerApiForZone(lb.getRegion()).getMetadata(lb.getId()); + assertEquals(metadata.get("key1"), "key1-updated"); + + assertTrue(clbApi.getLoadBalancerApiForZone(lb.getRegion()).removeMetadatum(lb.getId(), metadata.getId("key1"))); + assertTrue(awaitAvailable(clbApi.getLoadBalancerApiForZone(lb.getRegion())).apply(lb)); + metadata = clbApi.getLoadBalancerApiForZone(lb.getRegion()).getMetadata(lb.getId()); + assertNull(metadata.get("key1")); + + assertTrue(clbApi.getLoadBalancerApiForZone(lb.getRegion()).removeMetadata(lb.getId(), + ImmutableList. of(metadata.getId("key2"), metadata.getId("key3")))); + assertTrue(awaitAvailable(clbApi.getLoadBalancerApiForZone(lb.getRegion())).apply(lb)); + metadata = clbApi.getLoadBalancerApiForZone(lb.getRegion()).getMetadata(lb.getId()); + assertEquals(metadata.size(), 0); + } + } private void checkLBInRegion(String region, LoadBalancer lb, String name) { assertEquals(lb.getRegion(), region); @@ -155,4 +193,12 @@ public class LoadBalancerApiLiveTest extends BaseCloudLoadBalancersApiLiveTest { assertEquals(Iterables.get(lb.getVirtualIPs(), 0).getType(), Type.PUBLIC); } + private Metadata getExpectedMetadata() { + Metadata metadata = new Metadata(); + metadata.put("key1", "value1"); + metadata.put("key2", "value2"); + metadata.put("key3", "value3"); + + return metadata; + } } diff --git a/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApiExpectTest.java b/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApiExpectTest.java index 5f5e2c4efe..20cdac0a01 100644 --- a/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApiExpectTest.java +++ b/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApiExpectTest.java @@ -19,6 +19,7 @@ package org.jclouds.rackspace.cloudloadbalancers.features; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; import java.net.URI; import java.util.Set; @@ -27,13 +28,14 @@ import javax.ws.rs.core.MediaType; import org.jclouds.http.HttpResponse; import org.jclouds.rackspace.cloudloadbalancers.CloudLoadBalancersApi; +import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; import org.jclouds.rackspace.cloudloadbalancers.domain.Node; import org.jclouds.rackspace.cloudloadbalancers.domain.NodeAttributes; import org.jclouds.rackspace.cloudloadbalancers.domain.NodeRequest; -import org.jclouds.rackspace.cloudloadbalancers.features.NodeApi; import org.jclouds.rackspace.cloudloadbalancers.internal.BaseCloudLoadBalancerApiExpectTest; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; @@ -52,7 +54,7 @@ public class NodeApiExpectTest extends BaseCloudLoadBalancerApiExpectTest nodes = api.list().concat().toSet(); - assertEquals(nodes, testNodes()); + assertEquals(nodes, getExpectedNodes()); } public void testGetNodeInLoadBalancer() { @@ -106,7 +108,7 @@ public class NodeApiExpectTest extends BaseCloudLoadBalancerApiExpectTest nodeRequests = ImmutableSortedSet. of(nodeRequest1, nodeRequest2, nodeRequest3); Set nodes = api.add(nodeRequests); - assertEquals(nodes, testNodes()); + assertEquals(nodes, getExpectedNodes()); } public void testUpdateAttributesForNodeInLoadBalancer() { @@ -152,7 +154,80 @@ public class NodeApiExpectTest extends BaseCloudLoadBalancerApiExpectTest testNodes() { + public void testListMetadata() { + URI endpoint = URI.create("https://dfw.loadbalancers.api.rackspacecloud.com/v1.0/123123/loadbalancers/2000/nodes/410/metadata"); + NodeApi api = requestsSendResponses( + rackspaceAuthWithUsernameAndApiKey, + responseWithAccess, + authenticatedGET().endpoint(endpoint).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/metadata-list.json")).build() + ).getNodeApiForZoneAndLoadBalancer("DFW", 2000); + + Metadata metadata = api.getMetadata(410); + assertEquals(metadata, getExpectedMetadataWithIds()); + } + + public void testCreateMetadata() { + URI endpoint = URI.create("https://dfw.loadbalancers.api.rackspacecloud.com/v1.0/123123/loadbalancers/2000/nodes/410/metadata"); + NodeApi api = requestsSendResponses( + rackspaceAuthWithUsernameAndApiKey, + responseWithAccess, + authenticatedGET() + .method("POST") + .endpoint(endpoint) + .payload(payloadFromResourceWithContentType("/metadata-create.json", MediaType.APPLICATION_JSON)).build(), + HttpResponse.builder().statusCode(200).payload(payloadFromResource("/metadata-list.json")).build() + ).getNodeApiForZoneAndLoadBalancer("DFW", 2000); + + Metadata metadata = api.createMetadata(410, getExpectedMetadata()); + assertEquals(metadata, getExpectedMetadataWithIds()); + } + + public void testRemoveSingleMetadata() { + URI endpoint = URI.create("https://dfw.loadbalancers.api.rackspacecloud.com/v1.0/123123/loadbalancers/2000/nodes/410/metadata/23"); + NodeApi api = requestsSendResponses( + rackspaceAuthWithUsernameAndApiKey, + responseWithAccess, + authenticatedGET().method("DELETE").endpoint(endpoint).replaceHeader("Accept", MediaType.WILDCARD).build(), + HttpResponse.builder().statusCode(200).build() + ).getNodeApiForZoneAndLoadBalancer("DFW", 2000); + + assertTrue(api.removeMetadatum(410, 23)); + } + + public void testRemoveManyMetadata() { + URI endpoint = URI.create("https://dfw.loadbalancers.api.rackspacecloud.com/v1.0/123123/loadbalancers/2000/nodes/410/metadata?id=23&id=24"); + NodeApi api = requestsSendResponses( + rackspaceAuthWithUsernameAndApiKey, + responseWithAccess, + authenticatedGET().method("DELETE").endpoint(endpoint).replaceHeader("Accept", MediaType.WILDCARD).build(), + HttpResponse.builder().statusCode(200).build() + ).getNodeApiForZoneAndLoadBalancer("DFW", 2000); + + + + assertTrue(api.removeMetadata(410, ImmutableList. of(23, 24))); + } + + private Metadata getExpectedMetadata() { + Metadata metadata = new Metadata(); + metadata.put("color", "red"); + metadata.put("label", "web-load-balancer"); + metadata.put("os", "ubuntu"); + + return metadata; + } + + private Metadata getExpectedMetadataWithIds() { + Metadata metadata = getExpectedMetadata(); + metadata.putId("color", 1); + metadata.putId("label", 2); + metadata.putId("os", 3); + + return metadata; + } + + private Set getExpectedNodes() { Node node1 = Node.builder() .id(410) .address("10.1.1.1") diff --git a/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApiLiveTest.java b/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApiLiveTest.java index 01aa3e3064..c03992bf08 100644 --- a/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApiLiveTest.java +++ b/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/features/NodeApiLiveTest.java @@ -21,6 +21,7 @@ package org.jclouds.rackspace.cloudloadbalancers.features; import static org.jclouds.rackspace.cloudloadbalancers.predicates.LoadBalancerPredicates.awaitAvailable; import static org.jclouds.rackspace.cloudloadbalancers.predicates.LoadBalancerPredicates.awaitDeleted; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import java.util.Arrays; @@ -30,11 +31,14 @@ import java.util.Set; import java.util.Map.Entry; import java.util.logging.Logger; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancer; import org.jclouds.rackspace.cloudloadbalancers.domain.LoadBalancerRequest; +import org.jclouds.rackspace.cloudloadbalancers.domain.Metadata; import org.jclouds.rackspace.cloudloadbalancers.domain.Node; import org.jclouds.rackspace.cloudloadbalancers.domain.NodeAttributes; import org.jclouds.rackspace.cloudloadbalancers.domain.NodeRequest; @@ -134,6 +138,41 @@ public class NodeApiLiveTest extends BaseCloudLoadBalancersApiLiveTest { } } } + + @Test(dependsOnMethods = "testListNodes") + public void testNodeMetadata() throws Exception { + for (Entry> entry : nodes.entrySet()) { + LoadBalancer lb = entry.getKey(); + Node node = entry.getValue().iterator().next(); + Map metadataMap = ImmutableMap. of( + "key1", "value1", + "key2", "value2", + "key3", "value3"); + + Metadata metadata = clbApi.getNodeApiForZoneAndLoadBalancer(lb.getRegion(), lb.getId()).createMetadata(node.getId(), metadataMap); + assertEquals(metadata, getExpectedMetadata()); + assertTrue(awaitAvailable(clbApi.getLoadBalancerApiForZone(lb.getRegion())).apply(lb)); + + metadata = clbApi.getNodeApiForZoneAndLoadBalancer(lb.getRegion(), lb.getId()).getMetadata(node.getId()); + assertEquals(metadata, getExpectedMetadata()); + + assertTrue(clbApi.getNodeApiForZoneAndLoadBalancer(lb.getRegion(), lb.getId()).updateMetadatum(node.getId(), metadata.getId("key1"), "key1-updated")); + assertTrue(awaitAvailable(clbApi.getLoadBalancerApiForZone(lb.getRegion())).apply(lb)); + metadata = clbApi.getNodeApiForZoneAndLoadBalancer(lb.getRegion(), lb.getId()).getMetadata(node.getId()); + assertEquals(metadata.get("key1"), "key1-updated"); + + assertTrue(clbApi.getNodeApiForZoneAndLoadBalancer(lb.getRegion(), lb.getId()).removeMetadatum(node.getId(), metadata.getId("key1"))); + assertTrue(awaitAvailable(clbApi.getLoadBalancerApiForZone(lb.getRegion())).apply(lb)); + metadata = clbApi.getNodeApiForZoneAndLoadBalancer(lb.getRegion(), lb.getId()).getMetadata(node.getId()); + assertNull(metadata.get("key1")); + + assertTrue(clbApi.getNodeApiForZoneAndLoadBalancer(lb.getRegion(), lb.getId()).removeMetadata(node.getId(), + ImmutableList. of(metadata.getId("key2"), metadata.getId("key3")))); + assertTrue(awaitAvailable(clbApi.getLoadBalancerApiForZone(lb.getRegion())).apply(lb)); + metadata = clbApi.getNodeApiForZoneAndLoadBalancer(lb.getRegion(), lb.getId()).getMetadata(node.getId()); + assertEquals(metadata.size(), 0); + } + } @Override @AfterGroups(groups = "live") @@ -150,4 +189,13 @@ public class NodeApiLiveTest extends BaseCloudLoadBalancersApiLiveTest { } super.tearDownContext(); } + + private Metadata getExpectedMetadata() { + Metadata metadata = new Metadata(); + metadata.put("key1", "value1"); + metadata.put("key2", "value2"); + metadata.put("key3", "value3"); + + return metadata; + } } diff --git a/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseLoadBalancerTest.java b/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseLoadBalancerTest.java index 198db8d46a..b76f601c01 100644 --- a/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseLoadBalancerTest.java +++ b/apis/rackspace-cloudloadbalancers/src/test/java/org/jclouds/rackspace/cloudloadbalancers/functions/ParseLoadBalancerTest.java @@ -58,6 +58,12 @@ public class ParseLoadBalancerTest extends BaseItemParserTest { @Override public LoadBalancer expected() { + Metadata metadata = new Metadata(); + metadata.put("color", "red"); + metadata.putId("color", 1); + metadata.put("label", "web-load-balancer"); + metadata.putId("label", 2); + return LoadBalancer .builder() .region("DFW") @@ -88,9 +94,7 @@ public class ParseLoadBalancerTest extends BaseItemParserTest { .clusterName("c1.dfw1") .created(new SimpleDateFormatDateService().iso8601SecondsDateParse("2010-11-30T03:23:42Z")) .updated(new SimpleDateFormatDateService().iso8601SecondsDateParse("2010-11-30T03:23:44Z")) - .metadata(ImmutableSet. of( - Metadata.builder().id(1).key("color").value("red").build(), - Metadata.builder().id(2).key("label").value("web-load-balancer").build())).build(); + .metadata(metadata).build(); } // add factory binding as this is not default diff --git a/apis/rackspace-cloudloadbalancers/src/test/resources/metadata-create.json b/apis/rackspace-cloudloadbalancers/src/test/resources/metadata-create.json new file mode 100644 index 0000000000..3bd30bdb0b --- /dev/null +++ b/apis/rackspace-cloudloadbalancers/src/test/resources/metadata-create.json @@ -0,0 +1 @@ +{"metadata":[{"key":"os","value":"ubuntu"},{"key":"color","value":"red"},{"key":"label","value":"web-load-balancer"}]} \ No newline at end of file diff --git a/apis/rackspace-cloudloadbalancers/src/test/resources/metadata-list.json b/apis/rackspace-cloudloadbalancers/src/test/resources/metadata-list.json new file mode 100644 index 0000000000..e1ff79c49a --- /dev/null +++ b/apis/rackspace-cloudloadbalancers/src/test/resources/metadata-list.json @@ -0,0 +1,19 @@ +{ + "metadata": [ + { + "id": "1", + "key": "color", + "value": "red" + }, + { + "id": "2", + "key": "label", + "value": "web-load-balancer" + }, + { + "id": "3", + "key": "os", + "value": "ubuntu" + } + ] +} \ No newline at end of file