Add device proxy support to RESTCONF

Change-Id: I2e309ca7c5f7e2a183a5f2cef11627286647d6b7
This commit is contained in:
Henry Yu 2017-11-16 10:44:45 -05:00
parent 89e60d7688
commit 830b5dc3c7
4 changed files with 269 additions and 50 deletions

View File

@ -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.
@ -144,6 +143,36 @@ 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}.
*

View File

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

View File

@ -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<DataNode> 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<DataNode> 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<DataNode> 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",

View File

@ -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