From 830b5dc3c7fa23b1e452882538833c7fdf8bcc2f Mon Sep 17 00:00:00 2001 From: Henry Yu Date: Thu, 16 Nov 2017 10:44:45 -0500 Subject: [PATCH] Add device proxy support to RESTCONF Change-Id: I2e309ca7c5f7e2a183a5f2cef11627286647d6b7 --- .../org/onosproject/d/config/ResourceIds.java | 91 ++++++--- .../restconfmanager/DataResourceLocator.java | 188 ++++++++++++++++++ .../restconfmanager/RestconfManager.java | 37 ++-- .../restconf/utils/RestconfUtils.java | 3 +- 4 files changed, 269 insertions(+), 50 deletions(-) create mode 100644 apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/DataResourceLocator.java diff --git a/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java b/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java index a08c04a81a..95c8276818 100644 --- a/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java +++ b/apps/config/src/main/java/org/onosproject/d/config/ResourceIds.java @@ -15,12 +15,7 @@ */ package org.onosproject.d.config; -import static com.google.common.base.Preconditions.checkArgument; -import static org.slf4j.LoggerFactory.getLogger; - -import java.util.Iterator; -import java.util.Objects; - +import com.google.common.annotations.Beta; import org.onosproject.yang.model.DataNode; import org.onosproject.yang.model.KeyLeaf; import org.onosproject.yang.model.LeafListKey; @@ -30,7 +25,11 @@ import org.onosproject.yang.model.ResourceId; import org.onosproject.yang.model.SchemaId; import org.slf4j.Logger; -import com.google.common.annotations.Beta; +import java.util.Iterator; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.slf4j.LoggerFactory.getLogger; /** * Utility related to ResourceId. @@ -52,7 +51,7 @@ public abstract class ResourceIds { * Builds the ResourceId of specified {@code node}. * * @param parent ResourceId of {@code node} parent - * @param node to create ResourceId. + * @param node to create ResourceId. * @return ResourceId of {@code node} */ public static ResourceId resourceId(ResourceId parent, DataNode node) { @@ -69,27 +68,27 @@ public abstract class ResourceIds { SchemaId sid = node.key().schemaId(); switch (node.type()) { - case MULTI_INSTANCE_LEAF_VALUE_NODE: - builder.addLeafListBranchPoint(sid.name(), sid.namespace(), - ((LeafListKey) node.key()).asString()); - break; + case MULTI_INSTANCE_LEAF_VALUE_NODE: + builder.addLeafListBranchPoint(sid.name(), sid.namespace(), + ((LeafListKey) node.key()).asString()); + break; - case MULTI_INSTANCE_NODE: - builder.addBranchPointSchema(sid.name(), sid.namespace()); - for (KeyLeaf keyLeaf : ((ListKey) node.key()).keyLeafs()) { - builder.addKeyLeaf(keyLeaf.leafSchema().name(), - keyLeaf.leafSchema().namespace(), - keyLeaf.leafValAsString()); - } - break; + case MULTI_INSTANCE_NODE: + builder.addBranchPointSchema(sid.name(), sid.namespace()); + for (KeyLeaf keyLeaf : ((ListKey) node.key()).keyLeafs()) { + builder.addKeyLeaf(keyLeaf.leafSchema().name(), + keyLeaf.leafSchema().namespace(), + keyLeaf.leafValAsString()); + } + break; - case SINGLE_INSTANCE_LEAF_VALUE_NODE: - case SINGLE_INSTANCE_NODE: - builder.addBranchPointSchema(sid.name(), sid.namespace()); - break; + case SINGLE_INSTANCE_LEAF_VALUE_NODE: + case SINGLE_INSTANCE_NODE: + builder.addBranchPointSchema(sid.name(), sid.namespace()); + break; - default: - throw new IllegalArgumentException("Unknown type " + node); + default: + throw new IllegalArgumentException("Unknown type " + node); } @@ -100,7 +99,7 @@ public abstract class ResourceIds { * Concats {@code path} after {@code prefix}. * * @param prefix path - * @param path to append after {@code path} + * @param path to append after {@code path} * @return concatenated ResouceId */ public static ResourceId concat(ResourceId prefix, ResourceId path) { @@ -118,7 +117,7 @@ public abstract class ResourceIds { /** * Returns {@code child} as relative ResourceId against {@code base}. * - * @param base ResourceId + * @param base ResourceId * @param child ResourceId to relativize * @return relative ResourceId */ @@ -135,7 +134,7 @@ public abstract class ResourceIds { checkArgument(Objects.equals(b, c), "%s is not a prefix of %s.\n" + - "b:%s != c:%s", + "b:%s != c:%s", base, child, b, c); } @@ -144,17 +143,47 @@ public abstract class ResourceIds { child.nodeKeys().size())).build(); } + /** + * Removes the root node from {@code path}. + * + * @param path given resource ID + * @return resource ID without root node + */ + public static ResourceId removeRootNode(ResourceId path) { + if (!startsWithRootNode(path)) { + return path; + } + + return ResourceId.builder().append(path.nodeKeys().subList(1, + path.nodeKeys().size())).build(); + } + + /** + * Returns the resource ID of the parent data node pointed by {@code path}. + * + * @param path resource ID of the given data node + * @return resource ID of the parent data node + */ + public static ResourceId parentOf(ResourceId path) { + try { + return path.copyBuilder().removeLastKey().build(); + } catch (CloneNotSupportedException e) { + log.error("Could not copy {}", path, e); + throw new IllegalArgumentException("Could not copy " + path, e); + } + } + /** * Tests if {@code child} starts with {@code prefix}. * * @param prefix expected - * @param child to test + * @param child to test * @return true if {@code child} starts with {@code prefix} */ public static boolean isPrefix(ResourceId prefix, ResourceId child) { return child.nodeKeys().size() >= prefix.nodeKeys().size() && - prefix.nodeKeys().equals(child.nodeKeys().subList(0, prefix.nodeKeys().size())); + prefix.nodeKeys().equals(child.nodeKeys().subList(0, prefix.nodeKeys().size())); } public static boolean startsWithRootNode(ResourceId path) { diff --git a/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/DataResourceLocator.java b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/DataResourceLocator.java new file mode 100644 index 0000000000..ce4c5cd9a4 --- /dev/null +++ b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/DataResourceLocator.java @@ -0,0 +1,188 @@ +/* + * Copyright 2017-present Open Networking Foundation + * + * Licensed 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.onosproject.restconf.restconfmanager; + +import org.onosproject.net.DeviceId; +import org.onosproject.yang.model.ResourceId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ws.rs.core.UriBuilder; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; + +import static org.onosproject.d.config.DeviceResourceIds.toResourceId; +import static org.onosproject.d.config.ResourceIds.concat; +import static org.onosproject.d.config.ResourceIds.removeRootNode; +import static org.onosproject.restconf.utils.RestconfUtils.convertUriToRid; + +/** + * Representation of the data resource identifiers used by the RESTCONF manager. + *

+ * For a data resource under the device hierarchy, the restconf manager needs + * to maintain 2 separate resource IDs, one used by the + * the Dynamic Config, and the other by the Yang + * Runtime. (i.e., The resource IDs used by the dyn-config contain the + * "/devices/device" prefix, whereas the ones used by Yang Runtime do not.) + * This class provides the interface for the RESTCONF manager to use these + * 2 resource IDs. + */ +public final class DataResourceLocator { + + private static final Logger log = LoggerFactory.getLogger(DataResourceLocator.class); + + private static final String DATA_ROOT_DIR = "/onos/restconf/data"; + private static final String DEVICE_REGEX = "/devices/device=[^/]+"; + private static final String DEVICE_URI_PREFIX = DATA_ROOT_DIR + "/devices/device="; + + /** + * The resource ID used by Yang Runtime to refer to + * a data node. + */ + private ResourceId yrtResourceId; + + /** + * The resource ID used by the Dynamic Config to refer + * to a data node. + */ + private ResourceId dcsResourceId; + + /** + * URI used by RESTCONF to refer to a data node. + */ + private URI uriForRestconf; + + /** + * URI used by Yang Runtime to refer to a data node. + */ + private URI uriForYangRuntime; + + // Suppresses default constructor, ensuring non-instantiability. + private DataResourceLocator() { + } + + private DataResourceLocator(ResourceId yrtResourceId, + ResourceId dcsResourceId, + URI uriForRestconf, + URI uriForYangRuntime) { + this.yrtResourceId = yrtResourceId; + this.dcsResourceId = dcsResourceId; + this.uriForRestconf = uriForRestconf; + this.uriForYangRuntime = uriForYangRuntime; + } + + public ResourceId ridForDynConfig() { + return dcsResourceId; + } + + public ResourceId ridForYangRuntime() { + return yrtResourceId; + } + + public URI uriForRestconf() { + return uriForRestconf; + } + + public URI uriForYangRuntime() { + return uriForYangRuntime; + } + + /** + * Creates a DataResourceLocator object based on a given URI. + * + * @param uri given URI + * @return instantiated DataResourceLocator object + */ + public static DataResourceLocator newInstance(URI uri) { + URI uriForYangRuntime = uriForYangRuntime(uri); + ResourceId yrtResourceId = ridForYangRuntime(uriForYangRuntime); + /* + * If the given URI starts with "devices/device" prefix, then form the + * resource ID used by dyn-config by adding the prefix to the resource ID + * used by YANG runtime. Otherwise the two resource IDs are the same. + */ + ResourceId dcsResourceId = isDeviceResource(uri) ? + addDevicePrefix(yrtResourceId, getDeviceId(uri)) : yrtResourceId; + + return new DataResourceLocator(yrtResourceId, dcsResourceId, + uri, uriForYangRuntime); + } + + private static URI uriForYangRuntime(URI uriForRestconf) { + return isDeviceResource(uriForRestconf) ? + removeDeviceProxyPrefix(uriForRestconf) : uriForRestconf; + } + + private static ResourceId ridForYangRuntime(URI uriForYangRuntime) { + ResourceId yrtResourceId = convertUriToRid(uriForYangRuntime); + if (yrtResourceId == null) { + yrtResourceId = ResourceId.builder().addBranchPointSchema("/", null).build(); + } + + return yrtResourceId; + } + + private static URI removeDeviceProxyPrefix(URI uri) { + if (uri == null) { + return null; + } + UriBuilder builder = UriBuilder.fromUri(uri); + String newPath = rmDeviceStr(uri.getRawPath()); + builder.replacePath(newPath); + + return builder.build(); + } + + private static String rmDeviceStr(String uriStr) { + if (uriStr == null) { + return null; + + } + return uriStr.replaceFirst(DEVICE_REGEX, ""); + } + + private static DeviceId getDeviceId(URI uri) { + return DeviceId.deviceId(deviceIdStr(uri.getRawPath())); + } + + private static String deviceIdStr(String rawPath) { + String[] segments = rawPath.split("/"); + try { + for (String s : segments) { + if (s.startsWith("device=")) { + return URLDecoder.decode(s.substring("device=".length()), "utf-8"); + } + } + } catch (UnsupportedEncodingException e) { + log.error("deviceIdStr: caught UnsupportedEncodingException"); + log.debug("deviceIdStr: ", e); + } + return null; + } + + private static ResourceId addDevicePrefix(ResourceId rid, DeviceId did) { + return concat(toResourceId(did), removeRootNode(rid)); + } + + private static boolean isDeviceResource(URI uri) { + if (uri == null) { + return false; + } + return uri.getRawPath().startsWith(DEVICE_URI_PREFIX); + } +} diff --git a/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java index 356306db20..5334cd058e 100644 --- a/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java +++ b/apps/restconf/restconfmgr/src/main/java/org/onosproject/restconf/restconfmanager/RestconfManager.java @@ -54,9 +54,9 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; +import static org.onosproject.d.config.ResourceIds.parentOf; import static org.onosproject.restconf.utils.RestconfUtils.convertDataNodeToJson; import static org.onosproject.restconf.utils.RestconfUtils.convertJsonToDataNode; -import static org.onosproject.restconf.utils.RestconfUtils.convertUriToRid; import static org.onosproject.restconf.utils.RestconfUtils.rmLastPathSegment; import static org.onosproject.yang.model.DataNode.Type.MULTI_INSTANCE_NODE; import static org.onosproject.yang.model.DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE; @@ -107,29 +107,30 @@ public class RestconfManager implements RestconfService { @Override public ObjectNode runGetOperationOnDataResource(URI uri) throws RestconfException { - ResourceId rid = convertUriToRid(uri); + DataResourceLocator rl = DataResourceLocator.newInstance(uri); // TODO: define Filter (if there is any requirement). Filter filter = Filter.builder().build(); DataNode dataNode; try { - if (!dynamicConfigService.nodeExist(rid)) { + if (!dynamicConfigService.nodeExist(rl.ridForDynConfig())) { return null; } - dataNode = dynamicConfigService.readNode(rid, filter); + dataNode = dynamicConfigService.readNode(rl.ridForDynConfig(), filter); } catch (FailedException e) { log.error("ERROR: DynamicConfigService: ", e); throw new RestconfException("ERROR: DynamicConfigService", INTERNAL_SERVER_ERROR); } - ObjectNode rootNode = convertDataNodeToJson(rid, dataNode); + ObjectNode rootNode = convertDataNodeToJson(rl.ridForYangRuntime(), dataNode); return rootNode; } @Override public void runPostOperationOnDataResource(URI uri, ObjectNode rootNode) throws RestconfException { - ResourceData receivedData = convertJsonToDataNode(uri, rootNode); + DataResourceLocator rl = DataResourceLocator.newInstance(uri); + ResourceData receivedData = convertJsonToDataNode(rl.uriForYangRuntime(), rootNode); ResourceId rid = receivedData.resourceId(); List dataNodeList = receivedData.dataNodes(); if (dataNodeList.size() > 1) { @@ -143,7 +144,7 @@ public class RestconfManager implements RestconfService { } try { - dynamicConfigService.createNode(rid, dataNode); + dynamicConfigService.createNode(rl.ridForDynConfig(), dataNode); } catch (FailedException e) { log.error("ERROR: DynamicConfigService: ", e); throw new RestconfException("ERROR: DynamicConfigService", @@ -154,9 +155,8 @@ public class RestconfManager implements RestconfService { @Override public void runPutOperationOnDataResource(URI uri, ObjectNode rootNode) throws RestconfException { - ResourceId rid = convertUriToRid(uri); - ResourceData receivedData = convertJsonToDataNode(rmLastPathSegment(uri), rootNode); - ResourceId parentRid = receivedData.resourceId(); + DataResourceLocator rl = DataResourceLocator.newInstance(uri); + ResourceData receivedData = convertJsonToDataNode(rmLastPathSegment(rl.uriForYangRuntime()), rootNode); List dataNodeList = receivedData.dataNodes(); if (dataNodeList.size() > 1) { log.warn("There are more than one Data Node can be proceed: {}", dataNodeList.size()); @@ -168,10 +168,10 @@ public class RestconfManager implements RestconfService { * If the data node already exists, then replace it. * Otherwise, create it. */ - if (dynamicConfigService.nodeExist(rid)) { - dynamicConfigService.replaceNode(parentRid, dataNode); + if (dynamicConfigService.nodeExist(rl.ridForDynConfig())) { + dynamicConfigService.replaceNode(parentOf(rl.ridForDynConfig()), dataNode); } else { - dynamicConfigService.createNode(parentRid, dataNode); + dynamicConfigService.createNode(parentOf(rl.ridForDynConfig()), dataNode); } } catch (FailedException e) { @@ -184,10 +184,10 @@ public class RestconfManager implements RestconfService { @Override public void runDeleteOperationOnDataResource(URI uri) throws RestconfException { - ResourceId rid = convertUriToRid(uri); + DataResourceLocator rl = DataResourceLocator.newInstance(uri); try { - if (dynamicConfigService.nodeExist(rid)) { - dynamicConfigService.deleteNode(rid); + if (dynamicConfigService.nodeExist(rl.ridForDynConfig())) { + dynamicConfigService.deleteNode(rl.ridForDynConfig()); } } catch (FailedException e) { log.error("ERROR: DynamicConfigService: ", e); @@ -199,7 +199,8 @@ public class RestconfManager implements RestconfService { @Override public void runPatchOperationOnDataResource(URI uri, ObjectNode rootNode) throws RestconfException { - ResourceData receivedData = convertJsonToDataNode(rmLastPathSegment(uri), rootNode); + DataResourceLocator rl = DataResourceLocator.newInstance(uri); + ResourceData receivedData = convertJsonToDataNode(rmLastPathSegment(rl.uriForYangRuntime()), rootNode); ResourceId rid = receivedData.resourceId(); List dataNodeList = receivedData.dataNodes(); if (dataNodeList.size() > 1) { @@ -213,7 +214,7 @@ public class RestconfManager implements RestconfService { } try { - dynamicConfigService.updateNode(rid, dataNode); + dynamicConfigService.updateNode(parentOf(rl.ridForDynConfig()), dataNode); } catch (FailedException e) { log.error("ERROR: DynamicConfigService: ", e); throw new RestconfException("ERROR: DynamicConfigService", diff --git a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java index e14477feeb..cd0b42803e 100644 --- a/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java +++ b/apps/restconf/utils/src/main/java/org/onosproject/restconf/utils/RestconfUtils.java @@ -107,7 +107,8 @@ public final class RestconfUtils { } /** - * Convert URI to ResourceId. + * Convert URI to ResourceId. If the URI represents the datastore resource + * (i.e., the root of datastore), a null is returned. * * @param uri URI of the data resource * @return resource identifier