Update the Intent Delete API and semantics:
* Added new Java API for adding/removing Application Intents:
IPathCalcRuntimeService.addApplicationIntents(...)
IPathCalcRuntimeService.removeApplicationIntents(...)
IPathCalcRuntimeService.removeAllApplicationIntents(...)
* Updated the corresponding REST API to use the new Java API, and
move all API implementation complexity within the Intent framework.
* Added a new REST API to support deleting multiple Intents together:
REST DELETE: /wm/onos/intent/high?intent_id=1,2,3
* Updated the semantics for deleting an Intent:
Using the new Intent API (REST or Java) to delete an intent will
prune the intent as well.
For now, the pruning is asynchronous.
* Implemented the corresponding backend support to automatically
prune intents that have been deleted.
See class DeleteIntentsTracker inside file PathCalcRuntimeModule.java
* Updated test PathCalcRuntimeModuleTest.testIntentReroute() to
reflect the old path deletion.
* Updated the EntryForIntentMatcher.matchesSafely() to remove
assertThat() statement.
With the new implementation, this matcher can be used with not()
Hamcrest wrapper.
Change-Id: I88e7de119bab9d955d71cede14eb7847c7b6afbf
diff --git a/src/main/java/net/onrc/onos/core/intent/IntentMap.java b/src/main/java/net/onrc/onos/core/intent/IntentMap.java
index e72cb13..7d87fa2 100644
--- a/src/main/java/net/onrc/onos/core/intent/IntentMap.java
+++ b/src/main/java/net/onrc/onos/core/intent/IntentMap.java
@@ -99,8 +99,13 @@
notifyEvents();
}
+ /**
+ * Purge all Intents that are in a state allowing them to be removed.
+ */
public void purge() {
LinkedList<String> removeIds = new LinkedList<>();
+
+ // Collect the IDs of all intents that can be removed
for (Entry<String, Intent> entry : intents.entrySet()) {
Intent intent = entry.getValue();
if (intent.getState() == IntentState.DEL_ACK
@@ -108,6 +113,19 @@
removeIds.add(intent.getId());
}
}
+
+ purge(removeIds);
+ }
+
+ /**
+ * Purge a collection of Intents specified by Intent IDs.
+ *
+ * NOTE: The caller needs to make sure those intents are in a state
+ * that allows them to be removed.
+ *
+ * @param removeIds the collection of Intent IDs to purge.
+ */
+ public void purge(Collection<String> removeIds) {
for (String intentId : removeIds) {
removeIntent(intentId);
}
@@ -216,6 +234,10 @@
}
protected void notifyEvents() {
+ if (events.isEmpty()) {
+ return;
+ }
+
for (ChangedListener listener : listeners) {
listener.intentsChange(events);
}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java b/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java
index dd7bfd0..b81dbab 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/IPathCalcRuntimeService.java
@@ -1,6 +1,9 @@
package net.onrc.onos.core.intent.runtime;
+import java.util.Collection;
+
import net.floodlightcontroller.core.module.IFloodlightService;
+import net.onrc.onos.api.intent.ApplicationIntent;
import net.onrc.onos.core.intent.IntentMap;
import net.onrc.onos.core.intent.IntentOperationList;
@@ -8,6 +11,35 @@
* @author Toshio Koide (t-koide@onlab.us)
*/
public interface IPathCalcRuntimeService extends IFloodlightService {
+ /**
+ * Add Application Intents.
+ *
+ * @param appId the Application ID to use.
+ * @param appIntents the Application Intents to add.
+ * @return true on success, otherwise false.
+ */
+ public boolean addApplicationIntents(
+ final String appId,
+ Collection<ApplicationIntent> appIntents);
+
+ /**
+ * Remove Application Intents.
+ *
+ * @param appId the Application ID to use.
+ * @param intentIds the Application Intent IDs to remove.
+ * @return true on success, otherwise false.
+ */
+ public boolean removeApplicationIntents(final String appId,
+ Collection<String> intentIds);
+
+ /**
+ * Remove all Application Intents.
+ *
+ * @param appId the Application ID to use.
+ * @return true on success, otherwise false.
+ */
+ public boolean removeAllApplicationIntents(final String appId);
+
public IntentOperationList executeIntentOperations(IntentOperationList list);
public IntentMap getHighLevelIntents();
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java b/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
index fe8d012..8dcfd5f 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/PathCalcRuntimeModule.java
@@ -6,6 +6,7 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -18,12 +19,17 @@
import net.floodlightcontroller.core.module.IFloodlightModule;
import net.floodlightcontroller.core.module.IFloodlightService;
import net.floodlightcontroller.restserver.IRestApiService;
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.api.intent.ApplicationIntent;
import net.onrc.onos.core.datagrid.IDatagridService;
import net.onrc.onos.core.datagrid.IEventChannel;
import net.onrc.onos.core.datagrid.IEventChannelListener;
+import net.onrc.onos.core.intent.ConstrainedShortestPathIntent;
import net.onrc.onos.core.intent.Intent;
import net.onrc.onos.core.intent.Intent.IntentState;
import net.onrc.onos.core.intent.IntentMap;
+import net.onrc.onos.core.intent.IntentMap.ChangedEvent;
+import net.onrc.onos.core.intent.IntentMap.ChangedListener;
import net.onrc.onos.core.intent.IntentOperation;
import net.onrc.onos.core.intent.IntentOperation.Operator;
import net.onrc.onos.core.intent.IntentOperationList;
@@ -38,6 +44,7 @@
import net.onrc.onos.core.topology.LinkEvent;
import net.onrc.onos.core.topology.PortEvent;
import net.onrc.onos.core.topology.SwitchEvent;
+import net.onrc.onos.core.util.Dpid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -80,6 +87,89 @@
}
}
+ /**
+ * A class to track the deletion of intents and purge them as appropriate.
+ */
+ private class DeleteIntentsTracker implements ChangedListener {
+ @Override
+ public void intentsChange(LinkedList<ChangedEvent> events) {
+ List<String> removeIntentIds = new LinkedList<String>();
+ List<String> removePathIds = new LinkedList<String>();
+
+ //
+ // Process the events one-by-one and collect the Intent IDs of
+ // those intents that should be purged.
+ //
+ for (ChangedEvent event : events) {
+ log.debug("DeleteIntentsTracker: Intent ID {}, eventType {}",
+ event.intent.getId() , event.eventType);
+ PathIntent pathIntent = (PathIntent) pathIntents.getIntent(event.intent.getId());
+ if (pathIntent == null) {
+ continue;
+ }
+
+ //
+ // Test whether the new Intent state allows the Intent
+ // to be purged.
+ //
+ boolean shouldPurge = false;
+ switch (event.eventType) {
+ case ADDED:
+ break;
+ case REMOVED:
+ break;
+ case STATE_CHANGED:
+ IntentState state = pathIntent.getState();
+ switch (state) {
+ case INST_REQ:
+ break;
+ case INST_ACK:
+ break;
+ case INST_NACK:
+ shouldPurge = true;
+ break;
+ case DEL_REQ:
+ break;
+ case DEL_ACK:
+ shouldPurge = true;
+ break;
+ case DEL_PENDING:
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (shouldPurge) {
+ removePathIds.add(pathIntent.getId());
+ Intent parentIntent = pathIntent.getParentIntent();
+ if (parentIntent != null) {
+ //
+ // Remove the High-level Intent only if it was
+ // explicitly deleted by the user via the API.
+ //
+ String intentId = parentIntent.getId();
+ if (removedApplicationIntentIds.contains(intentId)) {
+ removeIntentIds.add(intentId);
+ removedApplicationIntentIds.remove(intentId);
+ }
+ }
+ }
+ }
+
+ // Purge the intents
+ if (!removeIntentIds.isEmpty()) {
+ highLevelIntents.purge(removeIntentIds);
+ }
+ if (!removePathIds.isEmpty()) {
+ pathIntents.purge(removePathIds);
+ }
+ }
+ }
+
private PathCalcRuntime runtime;
private IDatagridService datagridService;
private ITopologyService topologyService;
@@ -98,6 +188,8 @@
private HashSet<LinkEvent> unmatchedLinkEvents = new HashSet<>();
private ConcurrentMap<String, Set<Long>> intentInstalledMap = new ConcurrentHashMap<String, Set<Long>>();
private ConcurrentMap<String, Intent> staleIntents = new ConcurrentHashMap<String, Intent>();
+ private DeleteIntentsTracker deleteIntentsTracker = new DeleteIntentsTracker();
+ private Set<String> removedApplicationIntentIds = new HashSet<String>();
// ================================================================================
// private methods
@@ -173,6 +265,7 @@
highLevelIntents = new IntentMap();
runtime = new PathCalcRuntime(topologyService.getTopology());
pathIntents = new PathIntentMap();
+ pathIntents.addChangeListener(deleteIntentsTracker);
opEventChannel = datagridService.createChannel(INTENT_OP_EVENT_CHANNEL_NAME, Long.class, IntentOperationList.class);
datagridService.addListener(INTENT_STATE_EVENT_CHANNEL_NAME, this, Long.class, IntentStateList.class);
topologyService.registerTopologyListener(this);
@@ -180,9 +273,128 @@
restApi.addRestletRoutable(new IntentWebRoutable());
}
- // ================================================================================
+ // ======================================================================
// IPathCalcRuntimeService implementations
- // ================================================================================
+ // ======================================================================
+
+ /**
+ * Add Application Intents.
+ *
+ * @param appId the Application ID to use.
+ * @param appIntents the Application Intents to add.
+ * @return true on success, otherwise false.
+ */
+ @Override
+ public boolean addApplicationIntents(
+ final String appId,
+ Collection<ApplicationIntent> appIntents) {
+ //
+ // Process all intents one-by-one
+ //
+ // TODO: The Intent Type should be enum instead of a string,
+ // and we should use a switch statement below to process the
+ // different type of intents.
+ //
+ IntentOperationList intentOperations = new IntentOperationList();
+ for (ApplicationIntent appIntent : appIntents) {
+ String appIntentId = appId + ":" + appIntent.intentId();
+
+ IntentOperation.Operator operator = IntentOperation.Operator.ADD;
+ Dpid srcSwitchDpid = new Dpid(appIntent.srcSwitchDpid());
+ Dpid dstSwitchDpid = new Dpid(appIntent.dstSwitchDpid());
+
+ if (appIntent.intentType().equals("SHORTEST_PATH")) {
+ //
+ // Process Shortest-Path Intent
+ //
+ ShortestPathIntent spi =
+ new ShortestPathIntent(appIntentId,
+ srcSwitchDpid.value(),
+ appIntent.srcSwitchPort(),
+ MACAddress.valueOf(appIntent.matchSrcMac()).toLong(),
+ dstSwitchDpid.value(),
+ appIntent.dstSwitchPort(),
+ MACAddress.valueOf(appIntent.matchDstMac()).toLong());
+ spi.setPathFrozen(appIntent.isStaticPath());
+ intentOperations.add(operator, spi);
+ } else if (appIntent.intentType().equals("CONSTRAINED_SHORTEST_PATH")) {
+ //
+ // Process Constrained Shortest-Path Intent
+ //
+ ConstrainedShortestPathIntent cspi =
+ new ConstrainedShortestPathIntent(appIntentId,
+ srcSwitchDpid.value(),
+ appIntent.srcSwitchPort(),
+ MACAddress.valueOf(appIntent.matchSrcMac()).toLong(),
+ dstSwitchDpid.value(),
+ appIntent.dstSwitchPort(),
+ MACAddress.valueOf(appIntent.matchDstMac()).toLong(),
+ appIntent.bandwidth());
+ cspi.setPathFrozen(appIntent.isStaticPath());
+ intentOperations.add(operator, cspi);
+ } else {
+ log.error("Unknown Application Intent Type: {}",
+ appIntent.intentType());
+ return false;
+ }
+ removedApplicationIntentIds.remove(appIntentId);
+ }
+ // Apply the Intent Operations
+ executeIntentOperations(intentOperations);
+ return true;
+ }
+
+ /**
+ * Remove Application Intents.
+ *
+ * @param appId the Application ID to use.
+ * @param intentIds the Application Intent IDs to remove.
+ * @return true on success, otherwise false.
+ */
+ @Override
+ public boolean removeApplicationIntents(final String appId,
+ Collection<String> intentIds) {
+ IntentMap intentMap = getHighLevelIntents();
+
+ //
+ // Process all intents one-by-one
+ //
+ IntentOperationList operations = new IntentOperationList();
+ for (String intentId : intentIds) {
+ String appIntentId = appId + ":" + intentId;
+ Intent intent = intentMap.getIntent(appIntentId);
+ if (intent != null) {
+ operations.add(IntentOperation.Operator.REMOVE, intent);
+ removedApplicationIntentIds.add(appIntentId);
+ }
+ }
+ executeIntentOperations(operations);
+
+ return true;
+ }
+
+ /**
+ * Remove all Application Intents.
+ *
+ * @param appId the Application ID to use.
+ * @return true on success, otherwise false.
+ */
+ @Override
+ public boolean removeAllApplicationIntents(final String appId) {
+ IntentMap intentMap = getHighLevelIntents();
+
+ //
+ // Remove all intents
+ //
+ IntentOperationList operations = new IntentOperationList();
+ for (Intent intent : intentMap.getAllIntents()) {
+ operations.add(IntentOperation.Operator.REMOVE, intent);
+ removedApplicationIntentIds.add(intent.getId());
+ }
+ executeIntentOperations(operations);
+
+ return true;
+ }
@Override
public IntentOperationList executeIntentOperations(IntentOperationList list) {
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java
index fa66ea1..6f1c098 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighObjectResource.java
@@ -1,11 +1,12 @@
package net.onrc.onos.core.intent.runtime.web;
+import java.util.Arrays;
+import java.util.List;
+
import net.onrc.onos.api.rest.RestError;
import net.onrc.onos.api.rest.RestErrorCodes;
import net.onrc.onos.core.intent.Intent;
import net.onrc.onos.core.intent.IntentMap;
-import net.onrc.onos.core.intent.IntentOperation;
-import net.onrc.onos.core.intent.IntentOperationList;
import net.onrc.onos.core.intent.runtime.IPathCalcRuntimeService;
import org.restlet.data.Status;
@@ -19,7 +20,7 @@
*/
public class IntentHighObjectResource extends ServerResource {
// TODO need to assign proper application id.
- private static final String APPLN_ID = "1";
+ private static final String APPLICATION_ID = "1";
/**
* Gets a single high-level intent.
@@ -34,15 +35,14 @@
(IPathCalcRuntimeService) getContext().getAttributes()
.get(IPathCalcRuntimeService.class.getCanonicalName());
- Representation result;
-
String intentId = (String) getRequestAttributes().get("intent-id");
+ Representation result;
//
// Get a single high-level Intent: use the Intent ID to find it
//
IntentMap intentMap = pathRuntime.getHighLevelIntents();
- String applnIntentId = APPLN_ID + ":" + intentId;
+ String applnIntentId = APPLICATION_ID + ":" + intentId;
Intent intent = intentMap.getIntent(applnIntentId);
if (intent != null) {
result = toRepresentation(intent, null);
@@ -69,22 +69,14 @@
.get(IPathCalcRuntimeService.class.getCanonicalName());
String intentId = (String) getRequestAttributes().get("intent-id");
+ List<String> intentIds = Arrays.asList(intentId);
- //
- // Remove a single high-level Intent: use the Intent ID to find it
- //
- //
- // TODO: The implementation below is broken - waiting for the Java API
- // TODO: The deletion should use synchronous Java API?
- IntentMap intentMap = pathRuntime.getHighLevelIntents();
- String applnIntentId = APPLN_ID + ":" + intentId;
- Intent intent = intentMap.getIntent(applnIntentId);
- if (intent != null) {
- IntentOperationList operations = new IntentOperationList();
- operations.add(IntentOperation.Operator.REMOVE, intent);
- pathRuntime.executeIntentOperations(operations);
+ if (pathRuntime.removeApplicationIntents(APPLICATION_ID, intentIds)) {
+ setStatus(Status.SUCCESS_NO_CONTENT);
+ } else {
+ setStatus(Status.SERVER_ERROR_INTERNAL);
}
- setStatus(Status.SUCCESS_NO_CONTENT);
+
return null;
}
}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java
index 764c93f..2406674 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentHighResource.java
@@ -1,20 +1,16 @@
package net.onrc.onos.core.intent.runtime.web;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
-import net.floodlightcontroller.util.MACAddress;
import net.onrc.onos.api.intent.ApplicationIntent;
import net.onrc.onos.api.rest.RestError;
import net.onrc.onos.api.rest.RestErrorCodes;
-import net.onrc.onos.core.intent.ConstrainedShortestPathIntent;
import net.onrc.onos.core.intent.Intent;
import net.onrc.onos.core.intent.IntentMap;
-import net.onrc.onos.core.intent.IntentOperation;
-import net.onrc.onos.core.intent.IntentOperationList;
-import net.onrc.onos.core.intent.ShortestPathIntent;
import net.onrc.onos.core.intent.runtime.IPathCalcRuntimeService;
-import net.onrc.onos.core.util.Dpid;
import org.codehaus.jackson.map.ObjectMapper;
import org.restlet.data.Status;
@@ -32,12 +28,12 @@
public class IntentHighResource extends ServerResource {
private static final Logger log = LoggerFactory.getLogger(IntentHighResource.class);
// TODO need to assign proper application id.
- private static final String APPLN_ID = "1";
+ private static final String APPLICATION_ID = "1";
/**
* Gets all high-level intents.
*
- * @return a Representation for a collection with all of the high-level intents.
+ * @return a Representation for a collection with all high-level intents.
*/
@Get("json")
public Representation retrieve() throws IOException {
@@ -55,19 +51,18 @@
* Adds a collection of high-level intents.
*
* @param jsonIntent JSON representation of the intents to add.
- * @return a Representation of a collection containing the intents that were
- * created.
+ * @return a Representation of a collection containing the intents that
+ * were added.
*/
@Post("json")
public Representation store(String jsonIntent) {
IPathCalcRuntimeService pathRuntime =
(IPathCalcRuntimeService) getContext().getAttributes()
.get(IPathCalcRuntimeService.class.getCanonicalName());
- if (pathRuntime == null) {
- log.warn("Failed to get path calc runtime");
- return null;
- }
+ //
+ // Extract the Application Intents
+ //
ObjectMapper mapper = new ObjectMapper();
ApplicationIntent[] addOperations = null;
try {
@@ -77,7 +72,6 @@
} catch (IOException ex) {
log.error("Exception occurred parsing inbound JSON", ex);
}
-
if (addOperations == null) {
setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
final RestError error =
@@ -86,57 +80,16 @@
}
//
- // Process all intents one-by-one
+ // Add the intents
//
- // TODO: The Intent Type should be enum instead of a string,
- // and we should use a switch statement below to process the
- // different type of intents.
- //
- IntentOperationList intentOperations = new IntentOperationList();
- for (ApplicationIntent oper : addOperations) {
- String applnIntentId = APPLN_ID + ":" + oper.intentId();
-
- IntentOperation.Operator operator = IntentOperation.Operator.ADD;
- Dpid srcSwitchDpid = new Dpid(oper.srcSwitchDpid());
- Dpid dstSwitchDpid = new Dpid(oper.dstSwitchDpid());
-
- if (oper.intentType().equals("SHORTEST_PATH")) {
- //
- // Process Shortest-Path Intent
- //
- ShortestPathIntent spi =
- new ShortestPathIntent(applnIntentId,
- srcSwitchDpid.value(),
- oper.srcSwitchPort(),
- MACAddress.valueOf(oper.matchSrcMac()).toLong(),
- dstSwitchDpid.value(),
- oper.dstSwitchPort(),
- MACAddress.valueOf(oper.matchDstMac()).toLong());
- spi.setPathFrozen(oper.isStaticPath());
- intentOperations.add(operator, spi);
- } else {
- //
- // Process Constrained Shortest-Path Intent
- //
- ConstrainedShortestPathIntent cspi =
- new ConstrainedShortestPathIntent(applnIntentId,
- srcSwitchDpid.value(),
- oper.srcSwitchPort(),
- MACAddress.valueOf(oper.matchSrcMac()).toLong(),
- dstSwitchDpid.value(),
- oper.dstSwitchPort(),
- MACAddress.valueOf(oper.matchDstMac()).toLong(),
- oper.bandwidth());
- cspi.setPathFrozen(oper.isStaticPath());
- intentOperations.add(operator, cspi);
- }
+ if (pathRuntime.addApplicationIntents(APPLICATION_ID,
+ Arrays.asList(addOperations))) {
+ setStatus(Status.SUCCESS_CREATED);
+ } else {
+ setStatus(Status.SERVER_ERROR_INTERNAL);
}
- // Apply the Intent Operations
- pathRuntime.executeIntentOperations(intentOperations);
- setStatus(Status.SUCCESS_CREATED);
-
- return toRepresentation(intentOperations, null);
+ return toRepresentation(addOperations, null);
}
/**
@@ -150,11 +103,31 @@
(IPathCalcRuntimeService) getContext().getAttributes()
.get(IPathCalcRuntimeService.class.getCanonicalName());
- // Delete all intents
- // TODO: The implementation below is broken - waiting for the Java API
- // TODO: The deletion should use synchronous Java API?
- pathRuntime.purgeIntents();
- setStatus(Status.SUCCESS_NO_CONTENT);
- return null; // TODO no reply yet from the purge intents call
+ //
+ // Get the optional query values: comma-separated list of Intent IDs
+ //
+ String intentIdValue = getQueryValue("intent_id");
+ boolean success;
+
+ //
+ // Delete the intents
+ //
+ if (intentIdValue != null) {
+ // Delete a collection of intents, specified by Intent IDs
+ List<String> intentIds = Arrays.asList(intentIdValue.split(","));
+ success = pathRuntime.removeApplicationIntents(APPLICATION_ID,
+ intentIds);
+ } else {
+ // Delete all intents
+ success = pathRuntime.removeAllApplicationIntents(APPLICATION_ID);
+ }
+
+ if (success) {
+ setStatus(Status.SUCCESS_NO_CONTENT);
+ } else {
+ setStatus(Status.SERVER_ERROR_INTERNAL);
+ }
+
+ return null;
}
}
diff --git a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java
index c2e6e95..f54463e 100644
--- a/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java
+++ b/src/main/java/net/onrc/onos/core/intent/runtime/web/IntentLowResource.java
@@ -17,7 +17,7 @@
/**
* Gets all low-level intents.
*
- * @return a Representation of a collection of all of the low-level intents.
+ * @return a Representation of a collection of all low-level intents.
*/
@Get("json")
public Representation retrieve() {