final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node);
final NetconfDevice device = new ClusteredNetconfDevice(schemaResourcesDTO, remoteDeviceId, salFacade,
- processingExecutor.getExecutor(), actorSystem, topologyId, nodeId.getValue(), TypedActor.context());
+ processingExecutor.getExecutor(), actorSystem, topologyId, nodeId.getValue(), TypedActor.context(),
+ reconnectOnChangedSchema);
final int rpcMessageLimit =
node.getConcurrentRpcLimit() == null ? DEFAULT_CONCURRENT_RPC_LIMIT : node.getConcurrentRpcLimit();
public ClusteredNetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
final ExecutorService globalProcessingExecutor, final ActorSystem actorSystem, final String topologyId, final String nodeId,
- ActorContext cachedContext) {
- super(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, false);
+ final ActorContext cachedContext, final boolean reconnectOnSchemaChanged) {
+ super(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, reconnectOnSchemaChanged);
this.schemaRepo = (SchemaRepository) schemaResourcesDTO.getSchemaRegistry();
this.actorSystem = actorSystem;
this.topologyId = topologyId;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
import org.opendaylight.netconf.sal.connect.netconf.sal.SchemalessNetconfDeviceRpc;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseRpcSchemalessTransformer;
import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseSchema;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.SchemalessMessageTransformer;
+import org.opendaylight.netconf.sal.connect.util.MessageCounter;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
public class SchemalessNetconfDevice implements
private RemoteDeviceId id;
private RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
+ private final SchemalessMessageTransformer messageTransformer;
+ private final BaseRpcSchemalessTransformer rpcTransformer;
public SchemalessNetconfDevice(final RemoteDeviceId id,
final RemoteDeviceHandler<NetconfSessionPreferences> salFacade) {
this.id = id;
this.salFacade = salFacade;
+ final MessageCounter counter = new MessageCounter();
+ rpcTransformer = new BaseRpcSchemalessTransformer(counter);
+ messageTransformer = new SchemalessMessageTransformer(counter);
}
@Override public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities,
final NetconfDeviceCommunicator netconfDeviceCommunicator) {
-
final SchemalessNetconfDeviceRpc schemalessNetconfDeviceRpc = new SchemalessNetconfDeviceRpc(id,
- netconfDeviceCommunicator);
+ netconfDeviceCommunicator, rpcTransformer, messageTransformer);
salFacade.onDeviceConnected(BaseSchema.BASE_NETCONF_CTX.getSchemaContext(),
remoteSessionCapabilities, schemalessNetconfDeviceRpc);
}
@Override public void onNotification(final NetconfMessage notification) {
- // TODO support for notifications
+ salFacade.onNotification(messageTransformer.toNotification(notification));
}
}
import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseRpcSchemalessTransformer;
import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.SchemalessMessageTransformer;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
-import org.opendaylight.netconf.sal.connect.util.MessageCounter;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.common.RpcResult;
private final SchemalessMessageTransformer schemalessTransformer;
private final RemoteDeviceId deviceId;
- public SchemalessNetconfDeviceRpc(final RemoteDeviceId deviceId,
- final RemoteDeviceCommunicator<NetconfMessage> listener) {
+ public SchemalessNetconfDeviceRpc(RemoteDeviceId deviceId, final RemoteDeviceCommunicator<NetconfMessage> listener,
+ final BaseRpcSchemalessTransformer baseRpcTransformer,
+ final SchemalessMessageTransformer messageTransformer) {
this.deviceId = deviceId;
this.listener = listener;
- final MessageCounter counter = new MessageCounter();
- baseRpcTransformer = new BaseRpcSchemalessTransformer(counter);
- schemalessTransformer = new SchemalessMessageTransformer(counter);
+ this.baseRpcTransformer = baseRpcTransformer;
+ this.schemalessTransformer = messageTransformer;
}
@Nonnull
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.IETF_NETCONF_NOTIFICATIONS;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_URI;
+import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.stripNotification;
import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toPath;
import com.google.common.base.Function;
return new DefaultDOMRpcResult(normalizedNode);
}
- private static class NetconfDeviceNotification implements DOMNotification, DOMEvent {
+ static class NetconfDeviceNotification implements DOMNotification, DOMEvent {
private final ContainerNode content;
private final SchemaPath schemaPath;
private final Date eventTime;
*/
package org.opendaylight.netconf.sal.connect.netconf.schema.mapping;
+import java.util.Date;
+import java.util.Map;
import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.controller.config.util.xml.MissingNameSpaceException;
+import org.opendaylight.controller.config.util.xml.XmlElement;
import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
import org.opendaylight.netconf.sal.connect.api.MessageTransformer;
import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
import org.opendaylight.netconf.sal.connect.util.MessageCounter;
+import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
import org.opendaylight.yangtools.yang.model.api.SchemaPath;
private static final YangInstanceIdentifier.NodeIdentifier REPLY_ID =
new YangInstanceIdentifier.NodeIdentifier(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_QNAME);
+ // TODO maybe we should move this somewhere else as this
+ // might be used in applications using schemaless mountpoints
+ public static final YangInstanceIdentifier.NodeIdentifier SCHEMALESS_NOTIFICATION_PAYLOAD =
+ new YangInstanceIdentifier.NodeIdentifier(QName.create("schemaless-notification-payload"));
private final MessageCounter counter;
@Override
public DOMNotification toNotification(final NetconfMessage message) {
- //TODO add support for notifications
- throw new UnsupportedOperationException("Notifications not supported.");
+ final Map.Entry<Date, XmlElement> stripped = NetconfMessageTransformUtil.stripNotification(message);
+ final QName notificationNoRev;
+ try {
+ notificationNoRev =
+ QName.create(stripped.getValue().getNamespace(), stripped.getValue().getName()).withoutRevision();
+ } catch (final MissingNameSpaceException e) {
+ throw new IllegalArgumentException("Unable to parse notification " + message + ", cannot find namespace", e);
+ }
+
+ final AnyXmlNode notificationPayload = Builders.anyXmlBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(notificationNoRev))
+ .withValue(new DOMSource(stripped.getValue().getDomElement()))
+ .build();
+
+ final ContainerNode notificationBody = Builders.containerBuilder()
+ .withNodeIdentifier(SCHEMALESS_NOTIFICATION_PAYLOAD)
+ .withChild(notificationPayload)
+ .build();
+
+ return new NetconfMessageTransformer.NetconfDeviceNotification(notificationBody, stripped.getKey());
}
@Override
import org.opendaylight.controller.config.util.xml.XmlUtil;
import org.opendaylight.netconf.api.NetconfMessage;
import org.opendaylight.netconf.sal.connect.api.RemoteDeviceCommunicator;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseRpcSchemalessTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.SchemalessMessageTransformer;
+import org.opendaylight.netconf.sal.connect.util.MessageCounter;
import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.RpcResult;
RpcResult<NetconfMessage> msg = null;
ListenableFuture<RpcResult<NetconfMessage>> future = Futures.immediateFuture(msg);
doReturn(future).when(listener).sendRequest(any(), any());
- deviceRpc = new SchemalessNetconfDeviceRpc(new RemoteDeviceId("device1", InetSocketAddress.createUnresolved("0.0.0.0", 17830)), listener);
+ final MessageCounter counter = new MessageCounter();
+ deviceRpc = new SchemalessNetconfDeviceRpc(
+ new RemoteDeviceId("device1", InetSocketAddress.createUnresolved("0.0.0.0", 17830)), listener,
+ new BaseRpcSchemalessTransformer(counter), new SchemalessMessageTransformer(counter));
}
* @return {@link QName}
*/
public static QName makeQNameFromIdentifier(final String identifier) {
+ // check if more than one slash is not used as path separator
+ if (identifier.contains(
+ String.valueOf(RestconfConstants.SLASH).concat(String.valueOf(RestconfConstants.SLASH)))) {
+ LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' " + identifier);
+ throw new RestconfDocumentedException(
+ "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
+ ErrorTag.INVALID_VALUE);
+ }
+
final int mountIndex = identifier.indexOf(RestconfConstants.MOUNT);
String moduleNameAndRevision = "";
if (mountIndex >= 0) {
- moduleNameAndRevision = identifier.substring(mountIndex + RestconfConstants.MOUNT.length());
+ moduleNameAndRevision = identifier.substring(mountIndex + RestconfConstants.MOUNT.length())
+ .replaceFirst(String.valueOf(RestconfConstants.SLASH), "");
} else {
moduleNameAndRevision = identifier;
}
- final Splitter splitter = Splitter.on("/").omitEmptyStrings();
+ final Splitter splitter = Splitter.on(RestconfConstants.SLASH);
final Iterable<String> split = splitter.split(moduleNameAndRevision);
- final List<String> pathArgs = Lists.<String> newArrayList(split);
- if (pathArgs.size() < 2) {
+ final List<String> pathArgs = Lists.newArrayList(split);
+ if (pathArgs.size() != 2) {
LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' " + identifier);
throw new RestconfDocumentedException(
"URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.parser.builder;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.restconf.parser.IdentifierCodec;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Unit tests for {@link IdentifierCodec} mostly according to examples from draft-ietf-netconf-restconf-13
+ */
+public class IdentifierCodecTest {
+
+ private static final String URI_WITH_LIST_AND_LEAF =
+ "/list-test:top/list1=%2C%27" + '"' + "%3A" + '"' + "%20%2F,,foo/list2=a,b/result";
+ private static final String URI_WITH_LEAF_LIST = "/list-test:top/Y=x%3Ay";
+
+ private SchemaContext schemaContext;
+
+ @Before
+ public void init() throws Exception {
+ this.schemaContext = TestRestconfUtils.loadSchemaContext("/restconf/parser");
+ }
+
+ /**
+ * Positive test of deserialization URI <code>String</code> to <code>YangInstanceIdentifier</code> and
+ * serialization of <code>YangInstanceIdentifier</code> to <code>String/code> when original <code>String</code>
+ * URI contains list identifier and leaf identifier.
+ */
+ @Test
+ public void codecListAndLeafTest() {
+ final YangInstanceIdentifier dataYangII = IdentifierCodec.deserialize(
+ this.URI_WITH_LIST_AND_LEAF, this.schemaContext);
+ final String serializedDataYangII = IdentifierCodec.serialize(dataYangII, this.schemaContext);
+
+ assertEquals("Failed codec deserialization and serialization test",
+ this.URI_WITH_LIST_AND_LEAF, serializedDataYangII);
+ }
+
+ /**
+ * Positive test of deserialization URI <code>String</code> to <code>YangInstanceIdentifier</code> and
+ * serialization of <code>YangInstanceIdentifier</code> to <code>String/code> when original <code>String</code>
+ * URI contains leaf list identifier.
+ */
+ @Test
+ public void codecLeafListTest() {
+ final YangInstanceIdentifier dataYangII = IdentifierCodec.deserialize(
+ this.URI_WITH_LEAF_LIST, this.schemaContext);
+ final String serializedDataYangII = IdentifierCodec.serialize(dataYangII, this.schemaContext);
+
+ assertEquals("Failed codec deserialization and serialization test",
+ this.URI_WITH_LEAF_LIST, serializedDataYangII);
+ }
+
+ /**
+ * Positive test of deserialization URI <code>String</code> to <code>YangInstanceIdentifier</code> when
+ * <code>String</code> URI is <code>null</code>. <code>YangInstanceIdentifier.EMPTY</code> is
+ * expected to be returned.
+ */
+ @Test
+ public void codecDeserializeNullTest () {
+ final YangInstanceIdentifier dataYangII = IdentifierCodec.deserialize(null, this.schemaContext);
+ assertEquals("Failed codec deserialization test", YangInstanceIdentifier.EMPTY, dataYangII);
+ }
+
+ /**
+ * Positive test of serialization <code>YangInstanceIdentifier.EMPTY</code>. Single slash is
+ * expected to be returned.
+ */
+ @Test
+ public void codecSerializeEmptyTest () {
+ final String serialized = IdentifierCodec.serialize(YangInstanceIdentifier.EMPTY, this.schemaContext);
+ assertEquals("Failed codec serialization test", "/", serialized);
+ }
+
+ /**
+ * Positive test of serialization <code>YangInstanceIdentifier.EMPTY</code> and deserialization of result back to
+ * <code>YangInstanceIdentifier.EMPTY</code>.
+ */
+ @Test
+ public void codecDeserializeAndSerializeEmptyTest() {
+ final String serialized = IdentifierCodec.serialize(YangInstanceIdentifier.EMPTY, this.schemaContext);
+ assertEquals("Failed codec serialization and deserialization test",
+ YangInstanceIdentifier.EMPTY, IdentifierCodec.deserialize(serialized, this.schemaContext));
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.restconf.parser.builder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Unit tests for {@link YangInstanceIdentifierDeserializer}
+ */
+public class YangInstanceIdentifierDeserializerTest {
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ // schema context
+ private SchemaContext schemaContext;
+
+ @Before
+ public void init() throws Exception {
+ schemaContext = TestRestconfUtils.loadSchemaContext("/restconf/parser/deserializer");
+ }
+
+ /**
+ * Test of deserialization <code>String</code> URI with container to
+ * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+ */
+ @Test
+ public void deserializeContainerTest() {
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+ .create(schemaContext, "/deserializer-test:contA");
+
+ assertEquals("Result does not contains expected number of path arguments", 1, Iterables.size(result));
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")),
+ result.iterator().next());
+ }
+
+ /**
+ * Test of deserialization <code>String</code> URI with container containing leaf to
+ * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+ */
+ @Test
+ public void deserializeContainerWithLeafTest() {
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+ .create(schemaContext, "/deserializer-test:contA/leaf-A");
+
+ assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+ final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")),
+ iterator.next());
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "leaf-A")),
+ iterator.next());
+ }
+
+ /**
+ * Test of deserialization <code>String</code> URI with container containing list with leaf list to
+ * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+ */
+ @Test
+ public void deserializeContainerWithListWithLeafListTest() {
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+ .create(schemaContext, "/deserializer-test:contA/list-A=100/leaf-list-AA=instance");
+
+ assertEquals("Result does not contains expected number of path arguments", 5, Iterables.size(result));
+
+ final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+ // container
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")),
+ iterator.next());
+
+ // list
+ final QName list = QName.create("deserializer:test", "2016-06-06", "list-A");
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(list),
+ iterator.next());
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+ list, QName.create(list, "list-key"), 100).toString(),
+ iterator.next().toString());
+
+ // leaf list
+ final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-AA");
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(leafList),
+ iterator.next());
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.NodeWithValue(leafList, "instance"),
+ iterator.next());
+ }
+
+ /**
+ * Test of deserialization <code>String</code> URI containing list with no keys to
+ * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+ */
+ @Test
+ public void deserializeListWithNoKeysTest() {
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+ .create(schemaContext, "/deserializer-test:list-no-key");
+
+ assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+ final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+ final QName list = QName.create("deserializer:test", "2016-06-06", "list-no-key");
+
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(list),
+ iterator.next());
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(list),
+ iterator.next());
+ }
+
+ /**
+ * Test of deserialization <code>String</code> URI containing list with one key to
+ * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+ */
+ @Test
+ public void deserializeListWithOneKeyTest() {
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+ .create(schemaContext, "/deserializer-test:list-one-key=value");
+
+ assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+ final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+ final QName list = QName.create("deserializer:test", "2016-06-06", "list-one-key");
+
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(list),
+ iterator.next());
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, QName.create(list, "name"), "value"),
+ iterator.next());
+ }
+
+ /**
+ * Test of deserialization <code>String</code> URI containing list with multiple keys to
+ * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+ */
+ @Test
+ public void deserializeListWithMultipleKeysTest() {
+ final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
+ final Map<QName, Object> values = new LinkedHashMap<>();
+ values.put(QName.create(list, "name"), "value");
+ values.put(QName.create(list, "number"), 100);
+ values.put(QName.create(list, "enabled"), false);
+
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+ .create(schemaContext, "/deserializer-test:list-multiple-keys=value,100,false");
+
+ assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+ final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(list),
+ iterator.next());
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, values).toString(),
+ iterator.next().toString());
+ }
+
+ /**
+ * Test of deserialization <code>String</code> URI containing leaf list to
+ * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+ */
+ @Test
+ public void deserializeLeafListTest() {
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+ .create(schemaContext, "/deserializer-test:leaf-list-0=true");
+
+ assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+ final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+ final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-0");
+
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.NodeIdentifier(leafList),
+ iterator.next());
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.NodeWithValue(leafList, true).toString(),
+ iterator.next().toString());
+ }
+
+ /**
+ * Negative test when supplied <code>SchemaContext</code> is null. Test is expected to fail with
+ * <code>NullPointerException</code>.
+ */
+ @Test
+ public void deserializeNullSchemaContextNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ YangInstanceIdentifierDeserializer.create(null, "/deserializer-test:contA");
+ }
+
+ /**
+ * Negative test when supplied <code>String</code> data to deserialize is null. Test is expected to fail with
+ * <code>NullPointerException</code>.
+ */
+ @Test
+ public void nullDataNegativeNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, null);
+ }
+
+ /**
+ * Negative test when empty <code>String</code> is supplied as an input. Test is expected to fail with
+ * <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void deserializeEmptyDataNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "");
+ }
+
+ /**
+ * Negative test when identifier is not followed by slash or equals. Test is expected to fail with
+ * <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void deserializeBadCharMissingSlashOrEqualNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:cont*leaf-A");
+ }
+
+ /**
+ * Negative test of validating identifier when identifier does not start with slash.
+ * <code>IllegalArgumentException</code> is expected.
+ */
+ @Test
+ public void deserializeNoBeginningSlashNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:contA");
+ }
+
+ /**
+ * Positive test of validating identifier when identifier contains slash only. Deserialization should return
+ * empty result.
+ */
+ @Test
+ public void validArgOnlySlashTest() {
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+ .create(schemaContext, "/");
+ assertTrue("Result does not contains expected number of path arguments", Iterables.isEmpty(result));
+ }
+
+ /**
+ * Negative test of validating identifier when there is a slash after container without next identifier. Test
+ * is expected to fail with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void validArgIdentifierContainerEndsWithSlashNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA/");
+ }
+
+ /**
+ * Negative test of validating identifier when there is a slash after list key values without next identifier. Test
+ * is expected to fail with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void validArgIdentifierListEndsWithSlashLNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-one-key=value/");
+ }
+
+ /**
+ * Negative test of creating <code>QName</code> when identifier is empty (example: '//'). Test is expected to fail
+ * with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void prepareQnameEmptyIdentifierNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "//");
+ }
+
+ /**
+ * Negative test of creating <code>QName</code> when two identifiers are separated by two slashes. Test is
+ * expected to fail with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void prepareQnameTwoSlashesNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA//leaf-A");
+ }
+
+ /**
+ * Negative test of creating <code>QName</code> when in identifier there is another sign than colon or equals.
+ * Test is expected to fail with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void prepareQnameBuildPathNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test*contA");
+ }
+
+ /**
+ * Negative test of creating <code>QName</code> when it is not possible to find module for specified prefix. Test is
+ * expected to fail with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void prepareQnameNotExistingPrefixNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/not-existing:contA");
+ }
+
+ /**
+ * Negative test of creating <code>QName</code> when after prefix and colon there is not parsable identifier as
+ * local name. Test is expected to fail with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void prepareQnameNotValidPrefixAndLocalNameNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:*not-parsable-identifier");
+ }
+
+ /**
+ * Negative test of creating <code>QName</code> when data ends after prefix and colon. Test is expected to fail
+ * with <code>StringIndexOutOfBoundsException</code>.
+ */
+ @Test
+ public void prepareQnameErrorParsingNegativeTest() {
+ thrown.expect(StringIndexOutOfBoundsException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:");
+ }
+
+ /**
+ * Negative test of creating <code>QName</code> when after identifier and colon there is node name of unknown
+ * node in current container. Test is expected to fail with <code>RestconfDocumentedException</code> and error
+ * type, error tag and error status code are compared to expected values.
+ */
+ @Test
+ public void prepareQnameNotValidContainerNameNegativeTest() {
+ try {
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA/leafB");
+ fail("Test should fail due to unknown child node in container");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test of creating <code>QName</code> when after identifier and equals there is node name of unknown
+ * node in current list. Test is expected to fail with <code>RestconfDocumentedException</code> and error
+ * type, error tag and error status code are compared to expected values.
+ */
+ @Test
+ public void prepareQnameNotValidListNameNegativeTest() {
+ try {
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-no-key/disabled=false");
+ fail("Test should fail due to unknown child node in list");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test of getting next identifier when current node is keyed entry. Test is expected to
+ * fail with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void prepareIdentifierNotKeyedEntryNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-one-key");
+ }
+
+ /**
+ * Negative test when there is a comma also after the last key. Test is expected to fail with
+ * <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void deserializeKeysEndsWithComaNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierDeserializer.create( schemaContext,
+ "/deserializer-test:list-multiple-keys=value,100,false,");
+ }
+
+ /**
+ * Positive when not all keys of list are encoded. The missing keys should be considered to has empty
+ * <code>String</code> values. Also value of next leaf must not be considered to be missing key value.
+ */
+ @Test
+ public void notAllListKeysEncodedPositiveTest() {
+ final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
+ final Map<QName, Object> values = new LinkedHashMap<>();
+ values.put(QName.create(list, "name"), ":foo");
+ values.put(QName.create(list, "number"), "");
+ values.put(QName.create(list, "enabled"), "");
+
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer.create(
+ schemaContext, "/deserializer-test:list-multiple-keys=%3Afoo,,/string-value");
+
+ assertEquals("Result does not contains expected number of path arguments", 3, Iterables.size(result));
+
+ final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+ // list
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(list),
+ iterator.next());
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, values).toString(),
+ iterator.next().toString());
+
+ // leaf
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.NodeIdentifier(
+ QName.create("deserializer:test", "2016-06-06", "string-value")),
+ iterator.next());
+ }
+
+ /**
+ * Negative test when not all keys of list are encoded and it is not possible to consider missing keys to be empty.
+ * Test is expected to fail with <code>RestconfDocumentedException</code> and error type, error tag and error
+ * status code are compared to expected values.
+ */
+ @Test
+ public void notAllListKeysEncodedNegativeTest() {
+ try {
+ YangInstanceIdentifierDeserializer.create(
+ schemaContext, "/deserializer-test:list-multiple-keys=%3Afoo/string-value");
+ fail("Test should fail due to missing list key values");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.MISSING_ATTRIBUTE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test of preparing node with predicates when it is not possible to get <code>DataSchemaNode</code>.
+ * Test is expected to fail with <code>NullPointerException</code>.
+ */
+ @Ignore
+ @Test
+ public void prepareNodeWithPredicatesNegativeTest() {}
+
+ /**
+ * Test URI with list where key value starts with, ends with or contains percent encoded characters.The encoded
+ * value should be complete also with not percent-encoded parts.
+ */
+ @Test
+ public void percentEncodedKeyEndsWithNoPercentEncodedChars() {
+ final String URI = "/deserializer-test:list-multiple-keys=%3Afoo,bar%3A,foo%3Abar";
+ final YangInstanceIdentifier result = YangInstanceIdentifier.create(
+ YangInstanceIdentifierDeserializer.create(schemaContext, URI));
+
+ final Iterator<Map.Entry<QName, Object>> resultListKeys = ((YangInstanceIdentifier.NodeIdentifierWithPredicates)
+ result.getLastPathArgument()).getKeyValues().entrySet().iterator();
+
+ assertEquals(":foo", resultListKeys.next().getValue());
+ assertEquals("bar:", resultListKeys.next().getValue());
+ assertEquals("foo:bar", resultListKeys.next().getValue());
+ }
+
+ /**
+ * Positive test when all keys of list can be considered to be empty <code>String</code>.
+ */
+ @Test
+ public void deserializeAllKeysEmptyTest() {
+ final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
+ final Map<QName, Object> values = new LinkedHashMap<>();
+ values.put(QName.create(list, "name"), "");
+ values.put(QName.create(list, "number"), "");
+ values.put(QName.create(list, "enabled"), "");
+
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+ .create(schemaContext, "/deserializer-test:list-multiple-keys=,,");
+
+ assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+ final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(list),
+ iterator.next());
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, values).toString(),
+ iterator.next().toString());
+ }
+
+ /**
+ * Negative test of deserialization when for leaf list there is no specified instance value.
+ * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+ * compared to expected values.
+ */
+ @Test
+ public void leafListMissingKeyNegativeTest() {
+ try {
+ YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:leaf-list-0=");
+ fail("Test should fail due to missing instance value");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.MISSING_ATTRIBUTE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Positive test of deserialization when parts of input URI <code>String</code> are defined in another module.
+ */
+ @Test
+ public void deserializePartInOtherModuleTest() {
+ final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer.create(
+ schemaContext, "/deserializer-test-included:augmented-list=100/augmented-leaf");
+
+ assertEquals("Result does not contains expected number of path arguments", 4, Iterables.size(result));
+
+ final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+ final QName list = QName.create("deserializer:test:included", "2016-06-06", "augmented-list");
+ final QName child = QName.create("deserializer:test", "2016-06-06", "augmented-leaf");
+
+ // list
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(list),
+ iterator.next());
+
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, QName.create(list, "list-key"), 100)
+ .toString(),
+ iterator.next()
+ .toString());
+
+ // augmented leaf
+ assertEquals("Not expected path argument",
+ new YangInstanceIdentifier.AugmentationIdentifier(Sets.newHashSet(child)),
+ iterator.next());
+
+ assertEquals("Not expected path argument",
+ YangInstanceIdentifier.NodeIdentifier.create(child),
+ iterator.next());
+ }
+}
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
+
package org.opendaylight.restconf.parser.builder;
-import org.junit.Assert;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.util.LinkedHashMap;
+import java.util.Map;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
-import org.opendaylight.restconf.parser.IdentifierCodec;
+import org.opendaylight.restconf.utils.parser.builder.ParserBuilderConstants;
+import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+/**
+ * Unit tests for {@link YangInstanceIdentifierSerializer}
+ */
public class YangInstanceIdentifierSerializerTest {
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ // schema context with test modules
private SchemaContext schemaContext;
- private final String data = "/list-test:top/list1=%2C%27" + '"' + "%3A" + '"' + "%20%2F,,foo/list2=a,b/result=x";
@Before
public void init() throws Exception {
- this.schemaContext = TestRestconfUtils.loadSchemaContext("/restconf/parser");
+ schemaContext = TestRestconfUtils.loadSchemaContext("/restconf/parser/serializer");
+ }
+
+ /**
+ * Positive test of serialization of <code>YangInstanceIdentifier</code> containing container node to
+ * <code>String</code>. Returned <code>String</code> is compared to have expected value.
+ */
+ @Test
+ public void serializeContainerTest() {
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(QName.create("serializer:test", "2016-06-06", "contA"))
+ .build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+ assertEquals("Serialization not successful",
+ "/serializer-test:contA", result);
}
+ /**
+ * Positive test of serialization of <code>YangInstanceIdentifier</code> containing container with leaf node to
+ * <code>String</code>. Returned <code>String</code> is compared to have expected value.
+ */
@Test
- public void parserTest() {
- final YangInstanceIdentifier dataYangII = YangInstanceIdentifier
- .create(YangInstanceIdentifierDeserializer.create(this.schemaContext, this.data));
+ public void serializeContainerWithLeafTest() {
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(QName.create("serializer:test", "2016-06-06", "contA"))
+ .node(QName.create("serializer:test", "2016-06-06", "leaf-A"))
+ .build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+ assertEquals("Serialization not successful", "/serializer-test:contA/leaf-A", result);
+ }
+
+ /**
+ * Positive test of serialization of <code>YangInstanceIdentifier</code> containing container with list with leaf
+ * list node to <code>String</code>. Returned <code>String</code> is compared to have expected value.
+ */
+ @Test
+ public void serializeContainerWithListWithLeafListTest() {
+ final QName list = QName.create("serializer:test", "2016-06-06", "list-A");
+ final QName leafList = QName.create("serializer:test", "2016-06-06", "leaf-list-AA");
+
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(QName.create("serializer:test", "2016-06-06", "contA"))
+ .node(list)
+ .node(new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+ list, QName.create(list, "list-key"), 100))
+ .node(leafList)
+ .node(new NodeWithValue(leafList, "instance"))
+ .build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+ assertEquals("Serialization not successful",
+ "/serializer-test:contA/list-A=100/leaf-list-AA=instance",
+ result);
+ }
+
+ /**
+ * Positive test of serialization of <code>YangInstanceIdentifier</code> to <code>String</code> when serialized
+ * <code>YangInstanceIdentifier</code> contains list with no keys. Returned <code>String</code> is compared to have
+ * expected value.
+ */
+ @Test
+ public void serializeListWithNoKeysTest() {
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(QName.create("serializer:test", "2016-06-06", "list-no-key"))
+ .nodeWithKey(QName.create("serializer:test", "2016-06-06", "list-no-key"), Maps.newHashMap())
+ .build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+ assertEquals("Serialization not successful", "/serializer-test:list-no-key", result);
+ }
+
+ /**
+ * Positive test of serialization of <code>YangInstanceIdentifier</code> to <code>String</code> when serialized
+ * <code>YangInstanceIdentifier</code> contains list with one key. Returned <code>String</code> is compared to have
+ * expected value.
+ */
+ @Test
+ public void serializeListWithOneKeyTest() {
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(QName.create("serializer:test", "2016-06-06", "list-one-key"))
+ .nodeWithKey(QName.create("serializer:test", "2016-06-06", "list-one-key"),
+ QName.create("serializer:test", "2016-06-06", "list-one-key"), "value")
+ .build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+ assertEquals("Serialization not successful", "/serializer-test:list-one-key=value", result);
+ }
+
+ /**
+ * Positive test of serialization of <code>YangInstanceIdentifier</code> to <code>String</code> when serialized
+ * <code>YangInstanceIdentifier</code> contains list with multiple keys. Returned <code>String</code> is compared
+ * to have expected value.
+ */
+ @Test
+ public void serializeListWithMultipleKeysTest() {
+ final QName list = QName.create("serializer:test", "2016-06-06", "list-multiple-keys");
+ final Map<QName, Object> values = new LinkedHashMap<>();
+ values.put(QName.create(list, "name"), "value-1");
+ values.put(QName.create(list, "number"), "2");
+ values.put(QName.create(list, "enabled"), "true");
+
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(list).nodeWithKey(list, values).build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+ assertEquals("Serialization not successful", "/serializer-test:list-multiple-keys=value-1,2,true", result);
+ }
+
+ /**
+ * Positive test of serialization of <code>YangInstanceIdentifier</code> to <code>String</code> when serialized
+ * <code>YangInstanceIdentifier</code> contains leaf node. Returned <code>String</code> is compared to have
+ * expected value.
+ */
+ @Test
+ public void serializeLeafTest() {
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(QName.create("serializer:test", "2016-06-06", "leaf-0"))
+ .build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+ assertEquals("Serialization not successful", "/serializer-test:leaf-0", result);
+ }
+
+ /**
+ * Positive test of serialization of <code>YangInstanceIdentifier</code> to <code>String</code> when serialized
+ * <code>YangInstanceIdentifier</code> contains leaf list node. Returned <code>String</code> is compared to have
+ * expected value.
+ */
+ @Test
+ public void serializeLeafListTest() {
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(QName.create("serializer:test", "2016-06-06", "leaf-list-0"))
+ .node(new NodeWithValue(QName.create("serializer:test", "2016-06-06", "leaf-list-0"), "instance"))
+ .build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+ assertEquals("Serialization not successful", "/serializer-test:leaf-list-0=instance", result);
+ }
+
+ /**
+ * Negative test of serialization <code>YangInstanceIdentifier</code> to <code>String</code> when
+ * <code>SchemaContext</code> is <code>null</code>. Test is expected to fail with
+ * <code>NullPointerException</code>.
+ */
+ @Test
+ public void serializeNullSchemaContextNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ YangInstanceIdentifierSerializer.create(null, YangInstanceIdentifier.EMPTY);
+ }
+
+ /**
+ * Negative test of serialization <code>YangInstanceIdentifier</code> to <code>String</code> when supplied
+ * <code>YangInstanceIdentifier</code> is <code>null</code>. Test is expected to fail with
+ * <code>NullPointerException</code>.
+ */
+ @Test
+ public void serializeNullDataNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ YangInstanceIdentifierSerializer.create(schemaContext, null);
+ }
+
+ /**
+ * Test of serialization <code>YangInstanceIdentifier</code> to <code>String</code> when supplied
+ * <code>YangInstanceIdentifier</code> is <code>YangInstanceIdentifier.EMPTY</code>.
+ * Single slash is expected as a return value.
+ */
+ @Test
+ public void serializeEmptyDataTest() {
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, YangInstanceIdentifier.EMPTY);
+ assertEquals("Empty identifier is expected", "/", result);
+ }
+
+ /**
+ * Negative test when it is not possible to find child node of current node. Test is expected to fail with
+ * <code>IllegalArgumentException</code> and error message is compared to expected error message.
+ */
+ @Test
+ public void serializeChildNodeNotFoundNegativeTest() {
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(QName.create("serializer:test", "2016-06-06", "contA"))
+ .node(QName.create("serializer:test", "2016-06-06", "not-existing-leaf"))
+ .build();
+
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierSerializer.create(schemaContext, data);
+ }
+
+ /**
+ * Test to verify if all reserved characters according to rfc3986 are considered by serializer implementation to
+ * be percent encoded.
+ */
+ @Test
+ public void verifyReservedCharactersTest() {
+ final char[] genDelims = { ':', '/', '?', '#', '[', ']', '@' };
+ final char[] subDelims = { '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' };
+
+ for (final char c : genDelims) {
+ assertTrue("Current character is reserved and should be percent encoded",
+ ParserBuilderConstants.Serializer.PERCENT_ENCODE_CHARS.matches(c));
+ }
+
+ for (final char c : subDelims) {
+ assertTrue("Current character is reserved and should be percent encoded",
+ ParserBuilderConstants.Serializer.PERCENT_ENCODE_CHARS.matches(c));
+ }
+ }
+
+ /**
+ * Test if URIs with percent encoded characters are all correctly serialized.
+ */
+ @Test
+ public void serializePercentEncodingTest() {
+ final String value = "foo" + ":foo bar/" + "foo,bar/" + "'bar'";
+ final String encoded = "foo%3Afoo%20bar%2Ffoo%2Cbar%2F%27bar%27";
+
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(QName.create("serializer:test", "2016-06-06", "list-one-key"))
+ .nodeWithKey(QName.create("serializer:test", "2016-06-06", "list-one-key"),
+ QName.create("serializer:test", "2016-06-06", "list-one-key"), value)
+ .build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+ assertEquals("Serialization not successful", "/serializer-test:list-one-key=" + encoded, result);
+ }
+
+ /**
+ * Test if URIs with no percent encoded characters are correctly serialized. Input should be untouched.
+ */
+ @Test
+ public void serializeNoPercentEncodingTest() {
+ final String value = "foo\"b\"bar";
+
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(QName.create("serializer:test", "2016-06-06", "list-one-key"))
+ .nodeWithKey(QName.create("serializer:test", "2016-06-06", "list-one-key"),
+ QName.create("serializer:test", "2016-06-06", "list-one-key"), value)
+ .build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+ assertEquals("Serialization not successful", "/serializer-test:list-one-key=" + value, result);
+ }
+
+ /**
+ * Test of serialization when nodes in input <code>YangInstanceIdentifier</code> are defined in two different
+ * modules by using augmentation.
+ */
+ @Test
+ public void serializeIncludedNodesTest() {
+ final QName list = QName.create("serializer:test:included", "2016-06-06", "augmented-list");
+ final QName child = QName.create("serializer:test", "2016-06-06", "augmented-leaf");
+
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(list)
+ .node(new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+ list, QName.create(list, "list-key"), 100))
+ .node(new YangInstanceIdentifier.AugmentationIdentifier(Sets.newHashSet(child)))
+ .node(child)
+ .build();
+
+ final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+
+ assertEquals("Serialization not successful",
+ "/serializer-test-included:augmented-list=100/serializer-test:augmented-leaf", result);
+ }
+
+ /**
+ * Test of serialization when nodes in input <code>YangInstanceIdentifier</code> are defined in two different
+ * modules by using augmentation. Augmented node in data supplied for serialization has wrong namespace.
+ * <code>IllegalArgumentException</code> is expected because augmented node is defined in other module than its
+ * parent and will not be found.
+ */
+ @Test
+ public void serializeIncludedNodesSerializationTest() {
+ final QName list = QName.create("serializer:test:included", "2016-06-06", "augmented-list");
+ // child should has different namespace
+ final QName child = QName.create("serializer:test:included", "2016-06-06", "augmented-leaf");
+
+ final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+ .node(list)
+ .node(new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+ list, QName.create(list, "list-key"), 100))
+ .node(new YangInstanceIdentifier.AugmentationIdentifier(Sets.newHashSet(child)))
+ .node(child)
+ .build();
- final String serializedDataYangII = IdentifierCodec.serialize(dataYangII, this.schemaContext);
- Assert.assertEquals(this.data, serializedDataYangII);
+ thrown.expect(IllegalArgumentException.class);
+ YangInstanceIdentifierSerializer.create(schemaContext, data);
}
}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.restconf.utils.parser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableClassToInstanceMap;
+import com.google.common.collect.Maps;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.controller.md.sal.dom.broker.impl.mount.DOMMountPointServiceImpl;
+import org.opendaylight.controller.md.sal.dom.broker.spi.mount.SimpleDOMMountPoint;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.utils.RestconfConstants;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Unit tests for {@link ParserIdentifier}
+ */
+public class ParserIdentifierTest {
+ // mount point identifier + expected result
+ private static final String MOUNT_POINT_IDENT =
+ "/mount-point:mount-container/point-number" + "/" + RestconfConstants.MOUNT;
+
+ private static final String MOUNT_POINT_IDENT_RESULT =
+ "/(mount:point?revision=2016-06-02)mount-container/point-number";
+
+ // invalid mount point identifier
+ private static final String INVALID_MOUNT_POINT_IDENT =
+ "/mount-point:point-number" + "/" + RestconfConstants.MOUNT;
+
+ // test identifier + expected result
+ private static final String TEST_IDENT =
+ "/parser-identifier:cont1/cont2/listTest/list-in-grouping=name/leaf-A.B";
+
+ private static final String TEST_IDENT_RESULT =
+ "/(parser:identifier?revision=2016-06-02)cont1/cont2/listTest/listTest/list-in-grouping/"
+ + "list-in-grouping[{(parser:identifier?revision=2016-06-02)name=name}]/leaf-A.B";
+
+ // test identifier with nodes defined in other modules using augmentation + expected result
+ private static final String TEST_IDENT_OTHERS =
+ "/parser-identifier-included:list-1=name,2016-06-02/parser-identifier:augment-leaf";
+
+ private static final String TEST_IDENT_OTHERS_RESULT =
+ "/(parser:identifier:included?revision=2016-06-02)list-1/list-1"
+ + "[{(parser:identifier:included?revision=2016-06-02)name=name, "
+ + "(parser:identifier:included?revision=2016-06-02)revision=2016-06-02}]"
+ + "/AugmentationIdentifier{childNames=[(parser:identifier?revision=2016-06-02)augment-leaf]}/"
+ + "(parser:identifier?revision=2016-06-02)augment-leaf";
+
+ // invalid test identifier
+ private static final String INVALID_TEST_IDENT =
+ "/parser-identifier:cont2/listTest/list-in-grouping=name/leaf-A.B";
+
+ // schema context with test modules
+ private SchemaContext schemaContext;
+
+ private static final String TEST_MODULE_NAME = "test-module";
+ private static final String TEST_MODULE_REVISION = "2016-06-02";
+ private static final String TEST_MODULE_NAMESPACE = "test:module";
+ private static final String MOUNT_POINT_IDENT_WITHOUT_SLASH = MOUNT_POINT_IDENT.replaceFirst("/", "");
+
+ // mount point and mount point service
+ private DOMMountPoint mountPoint;
+ private DOMMountPointService mountPointService;
+
+ // mock mount point and mount point service
+ @Mock DOMMountPoint mockMountPoint;
+ @Mock DOMMountPointService mockMountPointService;
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public void setup() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ schemaContext = TestRestconfUtils.loadSchemaContext("/parser-identifier");
+
+ // create and register mount point
+ mountPoint = SimpleDOMMountPoint.create(
+ YangInstanceIdentifier.builder()
+ .node(QName.create("mount:point", "2016-06-02", "mount-container"))
+ .node(QName.create("mount:point", "2016-06-02", "point-number"))
+ .build(),
+ ImmutableClassToInstanceMap.copyOf(Maps.newHashMap()),
+ schemaContext
+ );
+
+ mountPointService = new DOMMountPointServiceImpl();
+ ((DOMMountPointServiceImpl) mountPointService).registerMountPoint(mountPoint);
+
+ // register mount point with null schema context
+ when(mockMountPoint.getSchemaContext()).thenReturn(null);
+ when(mockMountPointService.getMountPoint(YangInstanceIdentifier.EMPTY)).thenReturn(Optional.of(mockMountPoint));
+ }
+
+ /**
+ * {@link ParserIdentifier#toInstanceIdentifier(String, SchemaContext)} tests
+ */
+
+ /**
+ * Positive test of creating <code>InstanceIdentifierContext</code> from identifier when all nodes are defined
+ * in one module.
+ */
+ @Test
+ public void toInstanceIdentifierTest() {
+ final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+ TEST_IDENT, schemaContext);
+
+ assertEquals("Returned not expected identifier",
+ TEST_IDENT_RESULT, context .getInstanceIdentifier().toString());
+ }
+
+ /**
+ * Positive test of creating <code>InstanceIdentifierContext</code> from identifier when nodes are defined in
+ * multiple modules.
+ */
+ @Test
+ public void toInstanceIdentifierOtherModulesTest() {
+ final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+ TEST_IDENT_OTHERS, schemaContext);
+
+ assertEquals("Returned not expected identifier",
+ TEST_IDENT_OTHERS_RESULT, context.getInstanceIdentifier().toString());
+ }
+
+ /**
+ * Positive test of creating <code>InstanceIdentifierContext</code> from identifier containing
+ * {@link RestconfConstants#MOUNT}.
+ */
+ @Test
+ public void toInstanceIdentifierMountPointTest() {
+ final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+ MOUNT_POINT_IDENT, schemaContext);
+
+ assertEquals("Returned not expected identifier",
+ MOUNT_POINT_IDENT_RESULT, context.getInstanceIdentifier().toString());
+ }
+
+ /**
+ * Negative test of creating <code>InstanceIdentifierContext</code> when identifier is <code>null</code>. Test
+ * fails expecting <code>NullPointerException</code>.
+ */
+ @Test
+ public void toInstanceIdentifierNullIdentifierNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ ParserIdentifier.toInstanceIdentifier(null, schemaContext);
+ }
+
+ /**
+ * Negative test of creating <code>InstanceIdentifierContext</code> when <code>SchemaContext</code> is
+ * <code>null</code>. Test fails expecting <code>NullPointerException</code>.
+ */
+ @Test
+ public void toInstanceIdentifierNullSchemaContextNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ ParserIdentifier.toInstanceIdentifier(TEST_IDENT, null);
+ }
+
+ /**
+ * Api path can contains single slash. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
+ */
+ @Test
+ public void toInstanceIdentifierSlashIdentifierTest() {
+ final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier("/", schemaContext);
+ assertEquals("Returned not expected identifier",
+ YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
+ }
+
+ /**
+ * Api path can contains single slash. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
+ * Test when identifier contains {@link RestconfConstants#MOUNT}.
+ */
+ @Test
+ public void toInstanceIdentifierSlashIdentifierMountPointTest() {
+ final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+ "/" + "/" + RestconfConstants.MOUNT, schemaContext);
+ assertEquals("Returned not expected identifier",
+ YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
+ }
+
+ /**
+ * Negative test of creating <code>InstanceIdentifierContext</code> with empty identifier.
+ * <code>IllegalArgumentException</code> is expected.
+ */
+ @Test
+ public void toInstanceIdentifierEmptyIdentifierNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ ParserIdentifier.toInstanceIdentifier("", schemaContext);
+ }
+
+ /**
+ * Negative test of creating <code>InstanceIdentifierContext</code> from identifier containing
+ * {@link RestconfConstants#MOUNT} when identifier part is empty. <code>IllegalArgumentException</code> is expected.
+ */
+ @Test
+ public void toInstanceIdentifierMountPointEmptyIdentifierNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ ParserIdentifier.toInstanceIdentifier("/" + RestconfConstants.MOUNT, schemaContext);
+ }
+
+ /**
+ * Negative test with invalid test identifier. Test should fail with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void toInstanceIdentifierInvalidIdentifierNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ ParserIdentifier.toInstanceIdentifier(INVALID_TEST_IDENT, schemaContext);
+ }
+
+ /**
+ * Negative test when identifier contains {@link RestconfConstants#MOUNT} but identifier part is not valid. Test
+ * should fail with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void toInstanceIdentifierMountPointInvalidIdentifierNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ ParserIdentifier.toInstanceIdentifier(INVALID_MOUNT_POINT_IDENT, schemaContext);
+ }
+
+ /**
+ * {@link ParserIdentifier#makeQNameFromIdentifier(String)} tests
+ */
+
+ /**
+ * Positive test of making <code>QName</code> from identifier and compare values from returned <code>QName</code>
+ * to expected values.
+ */
+ @Test
+ public void makeQNameFromIdentifierTest() {
+ final QName qName = ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION);
+
+ assertNotNull("QName should be created", qName);
+ assertEquals("Returned not expected module name",
+ TEST_MODULE_NAME, qName.getLocalName());
+ assertEquals("Returned not expected module revision",
+ TEST_MODULE_REVISION, qName.getFormattedRevision());
+ }
+
+ /**
+ * Negative test when supplied identifier is in invalid format and then revision is not parsable.
+ * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+ * compared to expected values.
+ */
+ @Test
+ public void makeQNameFromIdentifierInvalidIdentifierNegativeTest() {
+ try {
+ ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME);
+ fail("Test should fail due to invalid identifier format");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test when supplied identifier is too short (contains only module name).
+ * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+ * compared to expected values.
+ */
+ @Test
+ public void makeQNameFromIdentifierTooShortIdentifierNegativeTest() {
+ try {
+ ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_NAME);
+ fail("Test should fail due to too short identifier format");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Positive test of making <code>QName</code> from identifier for module behind mount point. Value from returned
+ * <code>QName</code> are compared to expected values.
+ */
+ @Test
+ public void makeQNameFromIdentifierMountTest() {
+ final QName qName = ParserIdentifier.makeQNameFromIdentifier(
+ MOUNT_POINT_IDENT
+ + "/"
+ + TEST_MODULE_NAME
+ + "/"
+ + TEST_MODULE_REVISION);
+
+ assertNotNull("QName should be created", qName);
+ assertEquals("Returned not expected module name",
+ TEST_MODULE_NAME, qName.getLocalName());
+ assertEquals("Returned not expected module revision",
+ TEST_MODULE_REVISION, qName.getFormattedRevision());
+ }
+
+ /**
+ * Negative test when supplied identifier for module behind mount point is in invalid format and then revision is
+ * not parsable. <code>RestconfDocumentedException</code> is expected and error type, error tag and error status
+ * code are compared to expected values.
+ */
+ @Test
+ public void makeQNameFromIdentifierMountPointInvalidIdentifierNegativeTest() {
+ try {
+ ParserIdentifier.makeQNameFromIdentifier(
+ MOUNT_POINT_IDENT
+ + "/"
+ + TEST_MODULE_REVISION
+ + "/"
+ + TEST_MODULE_NAME);
+
+ fail("Test should fail due to invalid identifier format");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test when supplied identifier for module behind mount point is too short (contains only module name).
+ * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code
+ * are compared to expected values.
+ */
+ @Test
+ public void makeQNameFromIdentifierMountPointTooShortIdentifierNegativeTest() {
+ try {
+ ParserIdentifier.makeQNameFromIdentifier(
+ MOUNT_POINT_IDENT
+ + "/"
+ + TEST_MODULE_NAME);
+
+ fail("Test should fail due to too short identifier format");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test trying to make <code>QName</code> from <code>null</code> identifier. Test is expected to fail with
+ * <code>NullPointerException</code>.
+ */
+ @Test
+ public void makeQNameFromIdentifierNullIdentifierNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ ParserIdentifier.makeQNameFromIdentifier(null);
+ }
+
+ /**
+ * Negative test trying to make <code>QName</code> from empty identifier. Test is expected to fail with
+ * <code>RestconfDocumentedException</code>. Error type, error tag and error status code is compared to expected
+ * values.
+ */
+ @Test
+ public void makeQNameFromIdentifierEmptyIdentifierNegativeTest() {
+ try {
+ ParserIdentifier.makeQNameFromIdentifier("");
+ fail("Test should fail due to empty identifier");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test with identifier containing double slash. Between // there is one empty string which will be
+ * incorrectly considered to be module revision. Test is expected to fail with
+ * <code>RestconfDocumentedException</code> and error type, error tag and error status code are compared to
+ * expected values.
+ */
+ @Test
+ public void makeQNameFromIdentifierDoubleSlashNegativeTest() {
+ try {
+ ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_NAME + "//" + TEST_MODULE_REVISION);
+ fail("Test should fail due to identifier containing double slash");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * {@link ParserIdentifier#toSchemaExportContextFromIdentifier(SchemaContext, String, DOMMountPointService)} tests
+ */
+
+ /**
+ * Positive test of getting <code>SchemaExportContext</code>. Expected module name, revision and namespace are
+ * verified.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierTest() {
+ final SchemaExportContext exportContext = ParserIdentifier.
+ toSchemaExportContextFromIdentifier(schemaContext, TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION, null);
+
+ assertNotNull("Export context should be parsed", exportContext);
+
+ final Module module = exportContext.getModule();
+ assertNotNull("Export context should contains test module", module);
+
+ assertEquals("Returned not expected module name",
+ TEST_MODULE_NAME, module.getName());
+ assertEquals("Returned not expected module revision",
+ TEST_MODULE_REVISION, SimpleDateFormatUtil.getRevisionFormat().format(module.getRevision()));
+ assertEquals("Returned not expected module namespace",
+ TEST_MODULE_NAMESPACE, module.getNamespace().toString());
+ }
+
+ /**
+ * Test of getting <code>SchemaExportContext</code> when desired module is not found.
+ * <code>SchemaExportContext</code> should be created but module should be set to <code>null</code>.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierNotFoundTest() {
+ final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
+ schemaContext,
+ "not-existing-module" + "/" + "2016-01-01",
+ null);
+
+ assertNotNull("Export context should be parsed", exportContext);
+ assertNull("Not-existing module should be null", exportContext.getModule());
+ }
+
+ /**
+ * Negative test trying to get <code>SchemaExportContext</code> with invalid identifier. Test is expected to fail
+ * with <code>RestconfDocumentedException</code> error type, error tag and error status code are compared to
+ * expected values.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierInvalidIdentifierNegativeTest() {
+ try {
+ ParserIdentifier.toSchemaExportContextFromIdentifier(
+ schemaContext, TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME, null);
+ fail("Test should fail due to invalid identifier supplied");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Positive test of getting <code>SchemaExportContext</code> for module behind mount point.
+ * Expected module name, revision and namespace are verified.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierMountPointTest() {
+ final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
+ schemaContext,
+ MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION,
+ mountPointService);
+
+ final Module module = exportContext.getModule();
+ assertNotNull("Export context should contains test module", module);
+
+ assertEquals("Returned not expected module name",
+ TEST_MODULE_NAME, module.getName());
+ assertEquals("Returned not expected module revision",
+ TEST_MODULE_REVISION, SimpleDateFormatUtil.getRevisionFormat().format(module.getRevision()));
+ assertEquals("Returned not expected module namespace",
+ TEST_MODULE_NAMESPACE, module.getNamespace().toString());
+ }
+
+ /**
+ * Negative test of getting <code>SchemaExportContext</code> when desired module is not found behind mount point.
+ * <code>SchemaExportContext</code> should be still created but module should be set to <code>null</code>.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierMountPointNotFoundTest() {
+ final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
+ schemaContext,
+ MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + "not-existing-module" + "/" + "2016-01-01",
+ mountPointService);
+
+ assertNotNull("Export context should be parsed", exportContext);
+ assertNull("Not-existing module should be null", exportContext.getModule());
+ }
+
+ /**
+ * Negative test trying to get <code>SchemaExportContext</code> behind mount point with invalid identifier. Test is
+ * expected to fail with <code>RestconfDocumentedException</code> error type, error tag and error status code are
+ * compared to expected values.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierMountPointInvalidIdentifierNegativeTest() {
+ try {
+ ParserIdentifier.toSchemaExportContextFromIdentifier(
+ schemaContext,
+ MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME,
+ mountPointService);
+
+ fail("Test should fail due to invalid identifier supplied");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Not expected error type",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Not expected error tag",
+ RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+ assertEquals("Not expected error status code",
+ 400, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test of getting <code>SchemaExportContext</code> with identifier beginning with slash defining module
+ * behind mount point. Test is expected to fail with <code>IllegalArgumentException</code>.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierMountPointBeginsWithSlashNegativeTest() {
+ thrown.expect(IllegalArgumentException.class);
+ ParserIdentifier.toSchemaExportContextFromIdentifier(
+ schemaContext,
+ MOUNT_POINT_IDENT + "/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION,
+ mountPointService);
+ }
+
+ /**
+ * Negative test of getting <code>SchemaExportContext</code> when supplied identifier is null.
+ * <code>NullPointerException</code> is expected. <code>DOMMountPointService</code> is not used.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierNullIdentifierNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ ParserIdentifier.toSchemaExportContextFromIdentifier(schemaContext, null, null);
+ }
+
+ /**
+ * Negative test of of getting <code>SchemaExportContext</code> when supplied <code>SchemaContext</code> is
+ * <code>null</code>. Test is expected to fail with <code>NullPointerException</code>.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierNullSchemaContextNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ ParserIdentifier.toSchemaExportContextFromIdentifier(null, TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION, null);
+ }
+
+ /**
+ * Negative test of of getting <code>SchemaExportContext</code> when supplied <code>SchemaContext</code> is
+ * <code>null</code> and identifier specifies module behind mount point. Test is expected to fail with
+ * <code>NullPointerException</code>.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierMountPointNullSchemaContextNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ ParserIdentifier.toSchemaExportContextFromIdentifier(
+ null,
+ MOUNT_POINT_IDENT_WITHOUT_SLASH
+ + "/"
+ + TEST_MODULE_NAME
+ + "/"
+ + TEST_MODULE_REVISION,
+ mountPointService);
+ }
+
+ /**
+ * Negative test of of getting <code>SchemaExportContext</code> when supplied <code>DOMMountPointService</code>
+ * is <code>null</code> and identifier defines module behind mount point. Test is expected to fail with
+ * <code>NullPointerException</code>.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierNullMountPointServiceNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ ParserIdentifier.toSchemaExportContextFromIdentifier(
+ schemaContext,
+ MOUNT_POINT_IDENT_WITHOUT_SLASH
+ + "/"
+ + TEST_MODULE_NAME
+ + "/"
+ + TEST_MODULE_REVISION,
+ null);
+ }
+
+ /**
+ * Negative test of of getting <code>SchemaExportContext</code> when <code>SchemaContext</code> behind mount
+ * point is <code>null</code>. Test is expected to fail with <code>NullPointerException</code>.
+ */
+ @Test
+ public void toSchemaExportContextFromIdentifierNullSchemaContextBehindMountPointNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ ParserIdentifier.toSchemaExportContextFromIdentifier(
+ schemaContext,
+ "/"
+ + RestconfConstants.MOUNT
+ + "/"
+ + TEST_MODULE_NAME
+ + "/"
+ + TEST_MODULE_REVISION,
+ mockMountPointService);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.restconf.utils.schema.context;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.opendaylight.restconf.Draft11.MonitoringModule;
+import static org.opendaylight.restconf.Draft11.RestconfModule;
+
+import com.google.common.collect.Sets;
+import java.util.NoSuchElementException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.Draft11;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+/**
+ * Unit tests for {@link RestconfSchemaUtil}
+ */
+public class RestconfSchemaUtilTest {
+ // schema with testing modules
+ private SchemaContext schemaContext;
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public void setup() throws Exception {
+ schemaContext = TestRestconfUtils.loadSchemaContext("/modules/restconf-module-testing");
+ }
+
+ /**
+ * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+ * {@link RestconfModule#MODULE_LIST_SCHEMA_NODE} when this node can be found.
+ */
+ @Test
+ public void getRestconfSchemaNodeListModuleTest() {
+ final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+ getTestingRestconfModule("ietf-restconf"),
+ RestconfModule.MODULE_LIST_SCHEMA_NODE);
+
+ assertNotNull("Existing schema node "+ RestconfModule.MODULE_LIST_SCHEMA_NODE + " should be found",
+ dataSchemaNode);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getLocalName(), RestconfModule.MODULE_LIST_SCHEMA_NODE);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+ }
+
+ /**
+ * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+ * {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} when this node can be found.
+ */
+ @Test
+ public void getRestconfSchemaNodeListStreamTest() {
+ final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+ getTestingRestconfModule("ietf-restconf"),
+ MonitoringModule.STREAM_LIST_SCHEMA_NODE);
+
+ assertNotNull("Existing schema node " + MonitoringModule.STREAM_LIST_SCHEMA_NODE + " should be found",
+ dataSchemaNode);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getLocalName(), MonitoringModule.STREAM_LIST_SCHEMA_NODE);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+ }
+
+ /**
+ * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+ * {@link RestconfModule#MODULES_CONTAINER_SCHEMA_NODE} when this node can be found.
+ */
+ @Test
+ public void getRestconfSchemaNodeContainerModulesTest() {
+ final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+ getTestingRestconfModule("ietf-restconf"),
+ RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+
+ assertNotNull("Existing schema node " + RestconfModule.MODULES_CONTAINER_SCHEMA_NODE + "should be found",
+ dataSchemaNode);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getLocalName(), RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+ }
+
+ /**
+ * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+ * {@link MonitoringModule#STREAMS_CONTAINER_SCHEMA_NODE} when this node can be found.
+ */
+ @Test
+ public void getRestconfSchemaNodeContainerStreamsTest() {
+ final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+ getTestingRestconfModule("ietf-restconf"),
+ MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE);
+
+ assertNotNull("Existing schema node " + MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE + " should be found",
+ dataSchemaNode);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getLocalName(), MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+ assertEquals("Incorrect schema node was returned",
+ dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+ }
+
+ /**
+ * Negative test for getting <code>DataSchemaNode</code> from Restconf module when Restconf module is
+ * <code>null</code>. Test is expected to fail catching <code>NullPointerException</code>.
+ */
+ @Test
+ public void getRestconfSchemaNodeNullRestconfModuleNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ RestconfSchemaUtil.getRestconfSchemaNode(null, RestconfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
+ }
+
+ /**
+ * Negative test for getting <code>DataSchemaNode</code> from Restconf module when name of the schema node name is
+ * <code>null</code>. Test is expected to fail with <code>NullPointerException</code>.
+ */
+ @Test
+ public void getRestconfSchemaNodeNullSchemaNodeNameNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ RestconfSchemaUtil.getRestconfSchemaNode(getTestingRestconfModule("ietf-restconf"), null);
+ }
+
+ /**
+ * Negative test for getting <code>DataSchemaNode</code> from Restconf module when name of the schema node name
+ * references to not existing node. Test is expected to fail catching code>RestconfDocumentedException</code> and
+ * checking expected error type, error tag and error status code.
+ */
+ @Test
+ public void getRestconfSchemaNodeNotExistingSchemaNodeNameNegativeTest() {
+ try {
+ RestconfSchemaUtil.getRestconfSchemaNode(getTestingRestconfModule("ietf-restconf"), "not-existing-node");
+ fail("Test should fail due to not-existing node name");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+ * {@link RestconfModule#MODULES_CONTAINER_SCHEMA_NODE} when this node cannot be found.
+ * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+ * compared to expected values.
+ */
+ @Test
+ public void getRestconfSchemaNodeContainerModulesNegativeTest() {
+ try {
+ RestconfSchemaUtil.getRestconfSchemaNode(getTestingRestconfModule(
+ "restconf-module-with-missing-container-modules"), RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ fail("Test should fail due to missing " + RestconfModule.MODULES_CONTAINER_SCHEMA_NODE + " node");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+ * {@link RestconfModule#MODULE_LIST_SCHEMA_NODE} when this node cannot be found.
+ * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+ * compared to expected values.
+ */
+ @Test
+ public void getRestconfSchemaNodeListModuleNegativeTest() {
+ try {
+ RestconfSchemaUtil.getRestconfSchemaNode(
+ getTestingRestconfModule("restconf-module-with-missing-list-module"),
+ RestconfModule.MODULE_LIST_SCHEMA_NODE);
+ fail("Test should fail due to missing " + RestconfModule.MODULE_LIST_SCHEMA_NODE + " node");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+ * {@link MonitoringModule#STREAMS_CONTAINER_SCHEMA_NODE} when this node cannot
+ * be found. <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code
+ * are compared to expected values.
+ */
+ @Test
+ public void getRestconfSchemaNodeContainerStreamsNegativeTest() {
+ try {
+ RestconfSchemaUtil.getRestconfSchemaNode(
+ getTestingRestconfModule("restconf-module-with-missing-container-streams"),
+ MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE);
+ fail("Test should fail due to missing " + MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE + " node");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+ * {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} when this node cannot be found.
+ * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code
+ * are compared to expected values.
+ */
+ @Test
+ public void getRestconfSchemaNodeListStreamNegativeTest() {
+ try {
+ RestconfSchemaUtil.getRestconfSchemaNode(
+ getTestingRestconfModule("restconf-module-with-missing-list-stream"),
+ MonitoringModule.STREAM_LIST_SCHEMA_NODE);
+ fail("Test should fail due to missing " + MonitoringModule.STREAM_LIST_SCHEMA_NODE + " node");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test for getting <code>DataSchemaNode</code> from Restconf module when Restconf module does not
+ * contains restconf grouping. Test is expected to fail with <code>RestconfDocumentedException</code> and error
+ * type, error tag and error status code are compared to expected values.
+ */
+ @Test
+ public void getRestconfSchemaNodeMissingRestconfGroupingNegativeTest() {
+ try {
+ RestconfSchemaUtil.getRestconfSchemaNode(
+ getTestingRestconfModule("restconf-module-with-missing-grouping-restconf"),
+ RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ fail("Test should fail due to missing restconf grouping in Restconf module");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test for getting <code>DataSchemaNode</code> from Restconf module when Restconf module contains
+ * restconf grouping which does not contain any child nodes. Test is expected to fail with
+ * <code>NoSuchElementException</code>.
+ */
+ @Test
+ public void getRestconfSchemaNodeEmptyRestconfGroupingNegativeTest() {
+ thrown.expect(NoSuchElementException.class);
+ RestconfSchemaUtil.getRestconfSchemaNode(
+ getTestingRestconfModule("restconf-module-with-empty-grouping-restconf"),
+ RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ }
+
+ /**
+ * Positive test trying to find <code>DataSchemaNode</code> of {@link RestconfModule#RESTCONF_GROUPING_SCHEMA_NODE}
+ * in Restconf module groupings collection.
+ */
+ @Test
+ public void findSchemaNodeInCollectionTest() {
+ final SchemaNode schemaNode = RestconfSchemaUtil.findSchemaNodeInCollection(
+ getTestingRestconfModule("ietf-restconf").getGroupings(),
+ RestconfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+
+ assertNotNull("Restconf grouping schema node should be found", schemaNode);
+ assertEquals("Incorrect grouping was returned",
+ schemaNode.getQName().getLocalName(), RestconfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+ assertEquals("Incorrect grouping was returned",
+ schemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+ assertEquals("Incorrect grouping was returned",
+ schemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+ }
+
+ /**
+ * Negative test trying to find <code>DataSchemaNode</code> of not existing groupings schema node name in Restconf
+ * module grouping collection. Test is expected to fail catching <code>RestconfDocumentedException</code> and
+ * checking for correct error type, error tag and error status code.
+ */
+ @Test
+ public void findSchemaNodeInCollectionNegativeTest() {
+ try {
+ RestconfSchemaUtil.findSchemaNodeInCollection(
+ getTestingRestconfModule("ietf-restconf").getGroupings(), "not-existing-grouping");
+ fail("Test should fail due to missing not-existing grouping in Restconf grouping collection");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test trying to find <code>DataSchemaNode</code> of existing schema node name in <code>null</code>
+ * collection. Test is expected to fail with <code>NullPointerException</code>.
+ */
+ @Test
+ public void findSchemaNodeInCollectionNullCollectionNegativeTest() {
+ thrown.expect(NullPointerException.class);
+ RestconfSchemaUtil.findSchemaNodeInCollection(null, RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ }
+
+ /**
+ * Negative test trying to find <code>DataSchemaNode</code> for schema node name in empty collection. Test is
+ * expected to fail with <code>RestconfDocumentedException</code>. Error type, error tag and error status code
+ * are compared to expected values.
+ */
+ @Test
+ public void findSchemaNodeInCollectionEmptyCollectionNegativeTest() {
+ try {
+ RestconfSchemaUtil.findSchemaNodeInCollection(
+ Sets.newHashSet(), RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+ fail("Test should fail due to empty schema nodes collection");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * Negative test trying to find <code>DataSchemaNode</code> of <code>null</code> schema node name in Restconf module
+ * groupings collection. Test is expected to fail with <code>RestconfDocumentedException</code>. Error type, error
+ * tag and error status code are compared to expected values.
+ */
+ @Test
+ public void findSchemaNodeInCollectionNullSchemaNodeName() {
+ try {
+ RestconfSchemaUtil.findSchemaNodeInCollection(
+ getTestingRestconfModule("ietf-restconf").getGroupings(), null);
+ fail("Test should fail due to null schema node name");
+ } catch (final RestconfDocumentedException e) {
+ assertEquals("Error type is not correct",
+ RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+ assertEquals("Error tag is not correct",
+ RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+ assertEquals("Error status code is not correct",
+ 404, e.getErrors().get(0).getErrorTag().getStatusCode());
+ }
+ }
+
+ /**
+ * There are multiple testing Restconf modules for different test cases. It is possible to distinguish them by
+ * name or by namespace. This method is looking for Restconf test module by its name.
+ * @param s Testing Restconf module name
+ * @return Restconf module
+ */
+ private Module getTestingRestconfModule(final String s) {
+ return schemaContext.findModuleByName(s, Draft11.RestconfModule.IETF_RESTCONF_QNAME.getRevision());
+ }
+}
--- /dev/null
+module restconf-module-with-empty-grouping-restconf {
+ namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-rmwegr";
+ prefix "restconf";
+
+ import ietf-yang-types { prefix yang; }
+ import ietf-inet-types { prefix inet; }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ contact
+ "Editor: Andy Bierman
+ <mailto:andy@yumaworks.com>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>
+
+ Editor: Kent Watsen
+ <mailto:kwatsen@juniper.net>
+
+ Editor: Rex Fernando
+ <mailto:rex@cisco.com>";
+
+ description
+ "This module contains conceptual YANG specifications
+ for the YANG Patch and error content that is used in
+ RESTCONF protocol messages. A conceptual container
+ representing the RESTCONF API nodes (media type
+ application/yang.api).
+
+ Note that the YANG definitions within this module do not
+ represent configuration data of any kind.
+ The YANG grouping statements provide a normative syntax
+ for XML and JSON message encoding purposes.
+ Copyright (c) 2013 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC XXXX; see
+ the RFC itself for full legal notices.";
+
+ // RFC Ed.: replace XXXX with actual RFC number and remove this
+ // note.
+
+ // RFC Ed.: remove this note
+ // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+ // RFC Ed.: update the date below with the date of RFC publication
+ // and remove this note.
+ revision 2013-10-19 {
+ description
+ "Initial revision.";
+ reference
+ "RFC XXXX: RESTCONF Protocol.";
+ }
+
+ typedef data-resource-identifier {
+ type string {
+ length "1 .. max";
+ }
+ description
+ "Contains a Data Resource Identifier formatted string
+ to identify a specific data node. The data node that
+ uses this data type SHOULD define the document root
+ for data resource identifiers. The default document
+ root is the target datastore conceptual root node.
+ Data resource identifiers are defined relative to
+ this document root.";
+ reference
+ "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+ }
+
+ // this typedef is TBD; not currently used
+ typedef datastore-identifier {
+ type union {
+ type enumeration {
+ enum candidate {
+ description
+ "Identifies the NETCONF shared candidate datastore.";
+ reference
+ "RFC 6241, section 8.3";
+ }
+ enum running {
+ description
+ "Identifies the NETCONF running datastore.";
+ reference
+ "RFC 6241, section 5.1";
+ }
+ enum startup {
+ description
+ "Identifies the NETCONF startup datastore.";
+ reference
+ "RFC 6241, section 8.7";
+ }
+ }
+ type string;
+ }
+ description
+ "Contains a string to identify a specific datastore.
+ The enumerated datastore identifier values are
+ reserved for standard datastore names.";
+ }
+
+ typedef revision-identifier {
+ type string {
+ pattern '\d{4}-\d{2}-\d{2}';
+ }
+ description
+ "Represents a specific date in YYYY-MM-DD format.
+ TBD: make pattern more precise to exclude leading zeros.";
+ }
+
+ grouping yang-patch {
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of a
+ YANG Patch edit request message.";
+
+ container yang-patch {
+ description
+ "Represents a conceptual sequence of datastore edits,
+ called a patch. Each patch is given a client-assigned
+ patch identifier. Each edit MUST be applied
+ in ascending order, and all edits MUST be applied.
+ If any errors occur, then the target datastore MUST NOT
+ be changed by the patch operation.
+
+ A patch MUST be validated by the server to be a
+ well-formed message before any of the patch edits
+ are validated or attempted.
+
+ YANG datastore validation (defined in RFC 6020, section
+ 8.3.3) is performed after all edits have been
+ individually validated.
+
+ It is possible for a datastore constraint violation to occur
+ due to any node in the datastore, including nodes not
+ included in the edit list. Any validation errors MUST
+ be reported in the reply message.";
+
+ reference
+ "RFC 6020, section 8.3.";
+
+ leaf patch-id {
+ type string;
+ description
+ "An arbitrary string provided by the client to identify
+ the entire patch. This value SHOULD be present in any
+ audit logging records generated by the server for the
+ patch. Error messages returned by the server pertaining
+ to this patch will be identified by this patch-id value.";
+ }
+
+ leaf comment {
+ type string {
+ length "0 .. 1024";
+ }
+ description
+ "An arbitrary string provided by the client to describe
+ the entire patch. This value SHOULD be present in any
+ audit logging records generated by the server for the
+ patch.";
+ }
+
+ list edit {
+ key edit-id;
+ ordered-by user;
+
+ description
+ "Represents one edit within the YANG Patch
+ request message.";
+ leaf edit-id {
+ type string;
+ description
+ "Arbitrary string index for the edit.
+ Error messages returned by the server pertaining
+ to a specific edit will be identified by this
+ value.";
+ }
+
+ leaf operation {
+ type enumeration {
+ enum create {
+ description
+ "The target data node is created using the
+ supplied value, only if it does not already
+ exist.";
+ }
+ enum delete {
+ description
+ "Delete the target node, only if the data resource
+ currently exists, otherwise return an error.";
+ }
+ enum insert {
+ description
+ "Insert the supplied value into a user-ordered
+ list or leaf-list entry. The target node must
+ represent a new data resource.";
+ }
+ enum merge {
+ description
+ "The supplied value is merged with the target data
+ node.";
+ }
+ enum move {
+ description
+ "Move the target node. Reorder a user-ordered
+ list or leaf-list. The target node must represent
+ an existing data resource.";
+ }
+ enum replace {
+ description
+ "The supplied value is used to replace the target
+ data node.";
+ }
+ enum remove {
+ description
+ "Delete the target node if it currently exists.";
+ }
+ }
+ mandatory true;
+ description
+ "The datastore operation requested for the associated
+ edit entry";
+ }
+
+ leaf target {
+ type data-resource-identifier;
+ mandatory true;
+ description
+ "Identifies the target data resource for the edit
+ operation.";
+ }
+
+ leaf point {
+ when "(../operation = 'insert' or " +
+ "../operation = 'move') and " +
+ "(../where = 'before' or ../where = 'after')" {
+ description
+ "Point leaf only applies for insert or move
+ operations, before or after an existing entry.";
+ }
+ type data-resource-identifier;
+ description
+ "The absolute URL path for the data node that is being
+ used as the insertion point or move point for the
+ target of this edit entry.";
+ }
+
+ leaf where {
+ when "../operation = 'insert' or ../operation = 'move'" {
+ description
+ "Where leaf only applies for insert or move
+ operations.";
+ }
+ type enumeration {
+ enum before {
+ description
+ "Insert or move a data node before the data resource
+ identified by the 'point' parameter.";
+ }
+ enum after {
+ description
+ "Insert or move a data node after the data resource
+ identified by the 'point' parameter.";
+ }
+ enum first {
+ description
+ "Insert or move a data node so it becomes ordered
+ as the first entry.";
+ }
+ enum last {
+ description
+ "Insert or move a data node so it becomes ordered
+ as the last entry.";
+ }
+
+ }
+ default last;
+ description
+ "Identifies where a data resource will be inserted or
+ moved. YANG only allows these operations for
+ list and leaf-list data nodes that are ordered-by
+ user.";
+ }
+
+ anyxml value {
+ when "(../operation = 'create' or " +
+ "../operation = 'merge' " +
+ "or ../operation = 'replace' or " +
+ "../operation = 'insert')" {
+ description
+ "Value node only used for create, merge,
+ replace, and insert operations";
+ }
+ description
+ "Value used for this edit operation.";
+ }
+ }
+ }
+
+ } // grouping yang-patch
+
+
+ grouping yang-patch-status {
+
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of
+ YANG Patch status response message.";
+
+ container yang-patch-status {
+ description
+ "A container representing the response message
+ sent by the server after a YANG Patch edit
+ request message has been processed.";
+
+ leaf patch-id {
+ type string;
+ description
+ "The patch-id value used in the request";
+ }
+
+ choice global-status {
+ description
+ "Report global errors or complete success.
+ If there is no case selected then errors
+ are reported in the edit-status container.";
+
+ case global-errors {
+ uses errors;
+ description
+ "This container will be present if global
+ errors unrelated to a specific edit occurred.";
+ }
+ leaf ok {
+ type empty;
+ description
+ "This leaf will be present if the request succeeded
+ and there are no errors reported in the edit-status
+ container.";
+ }
+ }
+
+ container edit-status {
+ description
+ "This container will be present if there are
+ edit-specific status responses to report.";
+
+ list edit {
+ key edit-id;
+
+ description
+ "Represents a list of status responses,
+ corresponding to edits in the YANG Patch
+ request message. If an edit entry was
+ skipped or not reached by the server,
+ then this list will not contain a corresponding
+ entry for that edit.";
+
+ leaf edit-id {
+ type string;
+ description
+ "Response status is for the edit list entry
+ with this edit-id value.";
+ }
+ choice edit-status-choice {
+ description
+ "A choice between different types of status
+ responses for each edit entry.";
+ leaf ok {
+ type empty;
+ description
+ "This edit entry was invoked without any
+ errors detected by the server associated
+ with this edit.";
+ }
+ leaf location {
+ type inet:uri;
+ description
+ "Contains the Location header value that would be
+ returned if this edit causes a new resource to be
+ created. If the edit identified by the same edit-id
+ value was successfully invoked and a new resource
+ was created, then this field will be returned
+ instead of 'ok'.";
+ }
+ case errors {
+ uses errors;
+ description
+ "The server detected errors associated with the
+ edit identified by the same edit-id value.";
+ }
+ }
+ }
+ }
+ }
+ } // grouping yang-patch-status
+
+
+ grouping errors {
+
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of a
+ YANG Patch errors report within a response message.";
+
+ container errors {
+ config false; // needed so list error does not need a key
+ description
+ "Represents an error report returned by the server if
+ a request results in an error.";
+
+ list error {
+ description
+ "An entry containing information about one
+ specific error that occurred while processing
+ a RESTCONF request.";
+ reference "RFC 6241, Section 4.3";
+
+ leaf error-type {
+ type enumeration {
+ enum transport {
+ description "The transport layer";
+ }
+ enum rpc {
+ description "The rpc or notification layer";
+ }
+ enum protocol {
+ description "The protocol operation layer";
+ }
+ enum application {
+ description "The server application layer";
+ }
+ }
+ mandatory true;
+ description
+ "The protocol layer where the error occurred.";
+ }
+
+ leaf error-tag {
+ type string;
+ mandatory true;
+ description
+ "The enumerated error tag.";
+ }
+
+ leaf error-app-tag {
+ type string;
+ description
+ "The application-specific error tag.";
+ }
+
+ leaf error-path {
+ type data-resource-identifier;
+ description
+ "The target data resource identifier associated
+ with the error, if any.";
+ }
+ leaf error-message {
+ type string;
+ description
+ "A message describing the error.";
+ }
+
+ container error-info {
+ description
+ "A container allowing additional information
+ to be included in the error report.";
+ // arbitrary anyxml content here
+ }
+ }
+ }
+ } // grouping errors
+
+ /** This grouping is empty for testing purposes **/
+ grouping restconf {}
+
+ grouping notification {
+ description
+ "Contains the notification message wrapper definition.";
+
+ container notification {
+ description
+ "RESTCONF notification message wrapper.";
+ leaf event-time {
+ type yang:date-and-time;
+ mandatory true;
+ description
+ "The time the event was generated by the
+ event source.";
+ reference
+ "RFC 5277, section 4, <eventTime> element.";
+ }
+
+ /* The YANG-specific notification container is encoded
+ * after the 'event-time' element. The format
+ * corresponds to the notificationContent element
+ * in RFC 5277, section 4. For example:
+ *
+ * module example-one {
+ * ...
+ * notification event1 { ... }
+ *
+ * }
+ *
+ * Encoded as element 'event1' in the namespace
+ * for module 'example-one'.
+ */
+ }
+ } // grouping notification
+
+ }
\ No newline at end of file
--- /dev/null
+module restconf-module-with-missing-container-modules {
+ namespace "urn:ietf:params:xml:ns:yang:ietf-restcon-rmwmcmf";
+ prefix "restconf";
+
+ import ietf-yang-types { prefix yang; }
+ import ietf-inet-types { prefix inet; }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ contact
+ "Editor: Andy Bierman
+ <mailto:andy@yumaworks.com>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>
+
+ Editor: Kent Watsen
+ <mailto:kwatsen@juniper.net>
+
+ Editor: Rex Fernando
+ <mailto:rex@cisco.com>";
+
+ description
+ "This module contains conceptual YANG specifications
+ for the YANG Patch and error content that is used in
+ RESTCONF protocol messages. A conceptual container
+ representing the RESTCONF API nodes (media type
+ application/yang.api).
+
+ Note that the YANG definitions within this module do not
+ represent configuration data of any kind.
+ The YANG grouping statements provide a normative syntax
+ for XML and JSON message encoding purposes.
+ Copyright (c) 2013 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC XXXX; see
+ the RFC itself for full legal notices.";
+
+ // RFC Ed.: replace XXXX with actual RFC number and remove this
+ // note.
+
+ // RFC Ed.: remove this note
+ // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+ // RFC Ed.: update the date below with the date of RFC publication
+ // and remove this note.
+ revision 2013-10-19 {
+ description
+ "Initial revision.";
+ reference
+ "RFC XXXX: RESTCONF Protocol.";
+ }
+
+ typedef data-resource-identifier {
+ type string {
+ length "1 .. max";
+ }
+ description
+ "Contains a Data Resource Identifier formatted string
+ to identify a specific data node. The data node that
+ uses this data type SHOULD define the document root
+ for data resource identifiers. The default document
+ root is the target datastore conceptual root node.
+ Data resource identifiers are defined relative to
+ this document root.";
+ reference
+ "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+ }
+
+ // this typedef is TBD; not currently used
+ typedef datastore-identifier {
+ type union {
+ type enumeration {
+ enum candidate {
+ description
+ "Identifies the NETCONF shared candidate datastore.";
+ reference
+ "RFC 6241, section 8.3";
+ }
+ enum running {
+ description
+ "Identifies the NETCONF running datastore.";
+ reference
+ "RFC 6241, section 5.1";
+ }
+ enum startup {
+ description
+ "Identifies the NETCONF startup datastore.";
+ reference
+ "RFC 6241, section 8.7";
+ }
+ }
+ type string;
+ }
+ description
+ "Contains a string to identify a specific datastore.
+ The enumerated datastore identifier values are
+ reserved for standard datastore names.";
+ }
+
+ typedef revision-identifier {
+ type string {
+ pattern '\d{4}-\d{2}-\d{2}';
+ }
+ description
+ "Represents a specific date in YYYY-MM-DD format.
+ TBD: make pattern more precise to exclude leading zeros.";
+ }
+
+ grouping yang-patch {
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of a
+ YANG Patch edit request message.";
+
+ container yang-patch {
+ description
+ "Represents a conceptual sequence of datastore edits,
+ called a patch. Each patch is given a client-assigned
+ patch identifier. Each edit MUST be applied
+ in ascending order, and all edits MUST be applied.
+ If any errors occur, then the target datastore MUST NOT
+ be changed by the patch operation.
+
+ A patch MUST be validated by the server to be a
+ well-formed message before any of the patch edits
+ are validated or attempted.
+
+ YANG datastore validation (defined in RFC 6020, section
+ 8.3.3) is performed after all edits have been
+ individually validated.
+
+ It is possible for a datastore constraint violation to occur
+ due to any node in the datastore, including nodes not
+ included in the edit list. Any validation errors MUST
+ be reported in the reply message.";
+
+ reference
+ "RFC 6020, section 8.3.";
+
+ leaf patch-id {
+ type string;
+ description
+ "An arbitrary string provided by the client to identify
+ the entire patch. This value SHOULD be present in any
+ audit logging records generated by the server for the
+ patch. Error messages returned by the server pertaining
+ to this patch will be identified by this patch-id value.";
+ }
+
+ leaf comment {
+ type string {
+ length "0 .. 1024";
+ }
+ description
+ "An arbitrary string provided by the client to describe
+ the entire patch. This value SHOULD be present in any
+ audit logging records generated by the server for the
+ patch.";
+ }
+
+ list edit {
+ key edit-id;
+ ordered-by user;
+
+ description
+ "Represents one edit within the YANG Patch
+ request message.";
+ leaf edit-id {
+ type string;
+ description
+ "Arbitrary string index for the edit.
+ Error messages returned by the server pertaining
+ to a specific edit will be identified by this
+ value.";
+ }
+
+ leaf operation {
+ type enumeration {
+ enum create {
+ description
+ "The target data node is created using the
+ supplied value, only if it does not already
+ exist.";
+ }
+ enum delete {
+ description
+ "Delete the target node, only if the data resource
+ currently exists, otherwise return an error.";
+ }
+ enum insert {
+ description
+ "Insert the supplied value into a user-ordered
+ list or leaf-list entry. The target node must
+ represent a new data resource.";
+ }
+ enum merge {
+ description
+ "The supplied value is merged with the target data
+ node.";
+ }
+ enum move {
+ description
+ "Move the target node. Reorder a user-ordered
+ list or leaf-list. The target node must represent
+ an existing data resource.";
+ }
+ enum replace {
+ description
+ "The supplied value is used to replace the target
+ data node.";
+ }
+ enum remove {
+ description
+ "Delete the target node if it currently exists.";
+ }
+ }
+ mandatory true;
+ description
+ "The datastore operation requested for the associated
+ edit entry";
+ }
+
+ leaf target {
+ type data-resource-identifier;
+ mandatory true;
+ description
+ "Identifies the target data resource for the edit
+ operation.";
+ }
+
+ leaf point {
+ when "(../operation = 'insert' or " +
+ "../operation = 'move') and " +
+ "(../where = 'before' or ../where = 'after')" {
+ description
+ "Point leaf only applies for insert or move
+ operations, before or after an existing entry.";
+ }
+ type data-resource-identifier;
+ description
+ "The absolute URL path for the data node that is being
+ used as the insertion point or move point for the
+ target of this edit entry.";
+ }
+
+ leaf where {
+ when "../operation = 'insert' or ../operation = 'move'" {
+ description
+ "Where leaf only applies for insert or move
+ operations.";
+ }
+ type enumeration {
+ enum before {
+ description
+ "Insert or move a data node before the data resource
+ identified by the 'point' parameter.";
+ }
+ enum after {
+ description
+ "Insert or move a data node after the data resource
+ identified by the 'point' parameter.";
+ }
+ enum first {
+ description
+ "Insert or move a data node so it becomes ordered
+ as the first entry.";
+ }
+ enum last {
+ description
+ "Insert or move a data node so it becomes ordered
+ as the last entry.";
+ }
+
+ }
+ default last;
+ description
+ "Identifies where a data resource will be inserted or
+ moved. YANG only allows these operations for
+ list and leaf-list data nodes that are ordered-by
+ user.";
+ }
+
+ anyxml value {
+ when "(../operation = 'create' or " +
+ "../operation = 'merge' " +
+ "or ../operation = 'replace' or " +
+ "../operation = 'insert')" {
+ description
+ "Value node only used for create, merge,
+ replace, and insert operations";
+ }
+ description
+ "Value used for this edit operation.";
+ }
+ }
+ }
+
+ } // grouping yang-patch
+
+
+ grouping yang-patch-status {
+
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of
+ YANG Patch status response message.";
+
+ container yang-patch-status {
+ description
+ "A container representing the response message
+ sent by the server after a YANG Patch edit
+ request message has been processed.";
+
+ leaf patch-id {
+ type string;
+ description
+ "The patch-id value used in the request";
+ }
+
+ choice global-status {
+ description
+ "Report global errors or complete success.
+ If there is no case selected then errors
+ are reported in the edit-status container.";
+
+ case global-errors {
+ uses errors;
+ description
+ "This container will be present if global
+ errors unrelated to a specific edit occurred.";
+ }
+ leaf ok {
+ type empty;
+ description
+ "This leaf will be present if the request succeeded
+ and there are no errors reported in the edit-status
+ container.";
+ }
+ }
+
+ container edit-status {
+ description
+ "This container will be present if there are
+ edit-specific status responses to report.";
+
+ list edit {
+ key edit-id;
+
+ description
+ "Represents a list of status responses,
+ corresponding to edits in the YANG Patch
+ request message. If an edit entry was
+ skipped or not reached by the server,
+ then this list will not contain a corresponding
+ entry for that edit.";
+
+ leaf edit-id {
+ type string;
+ description
+ "Response status is for the edit list entry
+ with this edit-id value.";
+ }
+ choice edit-status-choice {
+ description
+ "A choice between different types of status
+ responses for each edit entry.";
+ leaf ok {
+ type empty;
+ description
+ "This edit entry was invoked without any
+ errors detected by the server associated
+ with this edit.";
+ }
+ leaf location {
+ type inet:uri;
+ description
+ "Contains the Location header value that would be
+ returned if this edit causes a new resource to be
+ created. If the edit identified by the same edit-id
+ value was successfully invoked and a new resource
+ was created, then this field will be returned
+ instead of 'ok'.";
+ }
+ case errors {
+ uses errors;
+ description
+ "The server detected errors associated with the
+ edit identified by the same edit-id value.";
+ }
+ }
+ }
+ }
+ }
+ } // grouping yang-patch-status
+
+
+ grouping errors {
+
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of a
+ YANG Patch errors report within a response message.";
+
+ container errors {
+ config false; // needed so list error does not need a key
+ description
+ "Represents an error report returned by the server if
+ a request results in an error.";
+
+ list error {
+ description
+ "An entry containing information about one
+ specific error that occurred while processing
+ a RESTCONF request.";
+ reference "RFC 6241, Section 4.3";
+
+ leaf error-type {
+ type enumeration {
+ enum transport {
+ description "The transport layer";
+ }
+ enum rpc {
+ description "The rpc or notification layer";
+ }
+ enum protocol {
+ description "The protocol operation layer";
+ }
+ enum application {
+ description "The server application layer";
+ }
+ }
+ mandatory true;
+ description
+ "The protocol layer where the error occurred.";
+ }
+
+ leaf error-tag {
+ type string;
+ mandatory true;
+ description
+ "The enumerated error tag.";
+ }
+
+ leaf error-app-tag {
+ type string;
+ description
+ "The application-specific error tag.";
+ }
+
+ leaf error-path {
+ type data-resource-identifier;
+ description
+ "The target data resource identifier associated
+ with the error, if any.";
+ }
+ leaf error-message {
+ type string;
+ description
+ "A message describing the error.";
+ }
+
+ container error-info {
+ description
+ "A container allowing additional information
+ to be included in the error report.";
+ // arbitrary anyxml content here
+ }
+ }
+ }
+ } // grouping errors
+
+
+ grouping restconf {
+
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of
+ the RESTCONF API resource.";
+
+ container restconf {
+ description
+ "Conceptual container representing the
+ application/yang.api resource type.";
+
+ container config {
+ description
+ "Container representing the application/yang.datastore
+ resource type. Represents the conceptual root of the
+ unified configuration datastore containing YANG data
+ nodes. The child nodes of this container are
+ configuration data resources (application/yang.data)
+ defined as top-level YANG data nodes from the modules
+ advertised by the server in /restconf/modules.";
+ }
+
+ container operational {
+ description
+ "Container representing the application/yang.datastore
+ resource type. Represents the conceptual root of the
+ operational data supported by the server. The child
+ nodes of this container are operational data resources
+ (application/yang.data) defined as top-level
+ YANG data nodes from the modules advertised by
+ the server in /restconf/modules.";
+ }
+
+ // removed container modules for testing purposes
+
+ container operations {
+ description
+ "Container for all operation resources
+ (application/yang.operation),
+
+ Each resource is represented as an empty leaf with the
+ name of the RPC operation from the YANG rpc statement.
+
+ E.g.;
+
+ POST /restconf/operations/show-log-errors
+
+ leaf show-log-errors {
+ type empty;
+ }
+ ";
+ }
+
+ container streams {
+ description
+ "Container representing the notification event streams
+ supported by the server.";
+ reference
+ "RFC 5277, Section 3.4, <streams> element.";
+
+ list stream {
+ key name;
+ description
+ "Each entry describes an event stream supported by
+ the server.";
+
+ leaf name {
+ type string;
+ description "The stream name";
+ reference "RFC 5277, Section 3.4, <name> element.";
+ }
+
+ leaf description {
+ type string;
+ description "Description of stream content";
+ reference
+ "RFC 5277, Section 3.4, <description> element.";
+ }
+
+ leaf replay-support {
+ type boolean;
+ description
+ "Indicates if replay buffer supported for this stream";
+ reference
+ "RFC 5277, Section 3.4, <replaySupport> element.";
+ }
+
+ leaf replay-log-creation-time {
+ type yang:date-and-time;
+ description
+ "Indicates the time the replay log for this stream
+ was created.";
+ reference
+ "RFC 5277, Section 3.4, <replayLogCreationTime>
+ element.";
+ }
+
+ leaf events {
+ type empty;
+ description
+ "Represents the entry point for establishing
+ notification delivery via server sent events.";
+ }
+ }
+ }
+
+ leaf version {
+ type enumeration {
+ enum "1.0" {
+ description
+ "Version 1.0 of the RESTCONF protocol.";
+ }
+ }
+ config false;
+ description
+ "Contains the RESTCONF protocol version.";
+ }
+ }
+ } // grouping restconf
+
+
+ grouping notification {
+ description
+ "Contains the notification message wrapper definition.";
+
+ container notification {
+ description
+ "RESTCONF notification message wrapper.";
+ leaf event-time {
+ type yang:date-and-time;
+ mandatory true;
+ description
+ "The time the event was generated by the
+ event source.";
+ reference
+ "RFC 5277, section 4, <eventTime> element.";
+ }
+
+ /* The YANG-specific notification container is encoded
+ * after the 'event-time' element. The format
+ * corresponds to the notificationContent element
+ * in RFC 5277, section 4. For example:
+ *
+ * module example-one {
+ * ...
+ * notification event1 { ... }
+ *
+ * }
+ *
+ * Encoded as element 'event1' in the namespace
+ * for module 'example-one'.
+ */
+ }
+ } // grouping notification
+
+ }
\ No newline at end of file
--- /dev/null
+module restconf-module-with-missing-grouping-restconf {
+ namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-rmwmgr";
+ prefix "restconf";
+
+ import ietf-yang-types { prefix yang; }
+ import ietf-inet-types { prefix inet; }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ contact
+ "Editor: Andy Bierman
+ <mailto:andy@yumaworks.com>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>
+
+ Editor: Kent Watsen
+ <mailto:kwatsen@juniper.net>
+
+ Editor: Rex Fernando
+ <mailto:rex@cisco.com>";
+
+ description
+ "This module contains conceptual YANG specifications
+ for the YANG Patch and error content that is used in
+ RESTCONF protocol messages. A conceptual container
+ representing the RESTCONF API nodes (media type
+ application/yang.api).
+
+ Note that the YANG definitions within this module do not
+ represent configuration data of any kind.
+ The YANG grouping statements provide a normative syntax
+ for XML and JSON message encoding purposes.
+ Copyright (c) 2013 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC XXXX; see
+ the RFC itself for full legal notices.";
+
+ // RFC Ed.: replace XXXX with actual RFC number and remove this
+ // note.
+
+ // RFC Ed.: remove this note
+ // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+ // RFC Ed.: update the date below with the date of RFC publication
+ // and remove this note.
+ revision 2013-10-19 {
+ description
+ "Initial revision.";
+ reference
+ "RFC XXXX: RESTCONF Protocol.";
+ }
+
+ typedef data-resource-identifier {
+ type string {
+ length "1 .. max";
+ }
+ description
+ "Contains a Data Resource Identifier formatted string
+ to identify a specific data node. The data node that
+ uses this data type SHOULD define the document root
+ for data resource identifiers. The default document
+ root is the target datastore conceptual root node.
+ Data resource identifiers are defined relative to
+ this document root.";
+ reference
+ "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+ }
+
+ // this typedef is TBD; not currently used
+ typedef datastore-identifier {
+ type union {
+ type enumeration {
+ enum candidate {
+ description
+ "Identifies the NETCONF shared candidate datastore.";
+ reference
+ "RFC 6241, section 8.3";
+ }
+ enum running {
+ description
+ "Identifies the NETCONF running datastore.";
+ reference
+ "RFC 6241, section 5.1";
+ }
+ enum startup {
+ description
+ "Identifies the NETCONF startup datastore.";
+ reference
+ "RFC 6241, section 8.7";
+ }
+ }
+ type string;
+ }
+ description
+ "Contains a string to identify a specific datastore.
+ The enumerated datastore identifier values are
+ reserved for standard datastore names.";
+ }
+
+ typedef revision-identifier {
+ type string {
+ pattern '\d{4}-\d{2}-\d{2}';
+ }
+ description
+ "Represents a specific date in YYYY-MM-DD format.
+ TBD: make pattern more precise to exclude leading zeros.";
+ }
+
+ grouping yang-patch {
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of a
+ YANG Patch edit request message.";
+
+ container yang-patch {
+ description
+ "Represents a conceptual sequence of datastore edits,
+ called a patch. Each patch is given a client-assigned
+ patch identifier. Each edit MUST be applied
+ in ascending order, and all edits MUST be applied.
+ If any errors occur, then the target datastore MUST NOT
+ be changed by the patch operation.
+
+ A patch MUST be validated by the server to be a
+ well-formed message before any of the patch edits
+ are validated or attempted.
+
+ YANG datastore validation (defined in RFC 6020, section
+ 8.3.3) is performed after all edits have been
+ individually validated.
+
+ It is possible for a datastore constraint violation to occur
+ due to any node in the datastore, including nodes not
+ included in the edit list. Any validation errors MUST
+ be reported in the reply message.";
+
+ reference
+ "RFC 6020, section 8.3.";
+
+ leaf patch-id {
+ type string;
+ description
+ "An arbitrary string provided by the client to identify
+ the entire patch. This value SHOULD be present in any
+ audit logging records generated by the server for the
+ patch. Error messages returned by the server pertaining
+ to this patch will be identified by this patch-id value.";
+ }
+
+ leaf comment {
+ type string {
+ length "0 .. 1024";
+ }
+ description
+ "An arbitrary string provided by the client to describe
+ the entire patch. This value SHOULD be present in any
+ audit logging records generated by the server for the
+ patch.";
+ }
+
+ list edit {
+ key edit-id;
+ ordered-by user;
+
+ description
+ "Represents one edit within the YANG Patch
+ request message.";
+ leaf edit-id {
+ type string;
+ description
+ "Arbitrary string index for the edit.
+ Error messages returned by the server pertaining
+ to a specific edit will be identified by this
+ value.";
+ }
+
+ leaf operation {
+ type enumeration {
+ enum create {
+ description
+ "The target data node is created using the
+ supplied value, only if it does not already
+ exist.";
+ }
+ enum delete {
+ description
+ "Delete the target node, only if the data resource
+ currently exists, otherwise return an error.";
+ }
+ enum insert {
+ description
+ "Insert the supplied value into a user-ordered
+ list or leaf-list entry. The target node must
+ represent a new data resource.";
+ }
+ enum merge {
+ description
+ "The supplied value is merged with the target data
+ node.";
+ }
+ enum move {
+ description
+ "Move the target node. Reorder a user-ordered
+ list or leaf-list. The target node must represent
+ an existing data resource.";
+ }
+ enum replace {
+ description
+ "The supplied value is used to replace the target
+ data node.";
+ }
+ enum remove {
+ description
+ "Delete the target node if it currently exists.";
+ }
+ }
+ mandatory true;
+ description
+ "The datastore operation requested for the associated
+ edit entry";
+ }
+
+ leaf target {
+ type data-resource-identifier;
+ mandatory true;
+ description
+ "Identifies the target data resource for the edit
+ operation.";
+ }
+
+ leaf point {
+ when "(../operation = 'insert' or " +
+ "../operation = 'move') and " +
+ "(../where = 'before' or ../where = 'after')" {
+ description
+ "Point leaf only applies for insert or move
+ operations, before or after an existing entry.";
+ }
+ type data-resource-identifier;
+ description
+ "The absolute URL path for the data node that is being
+ used as the insertion point or move point for the
+ target of this edit entry.";
+ }
+
+ leaf where {
+ when "../operation = 'insert' or ../operation = 'move'" {
+ description
+ "Where leaf only applies for insert or move
+ operations.";
+ }
+ type enumeration {
+ enum before {
+ description
+ "Insert or move a data node before the data resource
+ identified by the 'point' parameter.";
+ }
+ enum after {
+ description
+ "Insert or move a data node after the data resource
+ identified by the 'point' parameter.";
+ }
+ enum first {
+ description
+ "Insert or move a data node so it becomes ordered
+ as the first entry.";
+ }
+ enum last {
+ description
+ "Insert or move a data node so it becomes ordered
+ as the last entry.";
+ }
+
+ }
+ default last;
+ description
+ "Identifies where a data resource will be inserted or
+ moved. YANG only allows these operations for
+ list and leaf-list data nodes that are ordered-by
+ user.";
+ }
+
+ anyxml value {
+ when "(../operation = 'create' or " +
+ "../operation = 'merge' " +
+ "or ../operation = 'replace' or " +
+ "../operation = 'insert')" {
+ description
+ "Value node only used for create, merge,
+ replace, and insert operations";
+ }
+ description
+ "Value used for this edit operation.";
+ }
+ }
+ }
+
+ } // grouping yang-patch
+
+
+ grouping yang-patch-status {
+
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of
+ YANG Patch status response message.";
+
+ container yang-patch-status {
+ description
+ "A container representing the response message
+ sent by the server after a YANG Patch edit
+ request message has been processed.";
+
+ leaf patch-id {
+ type string;
+ description
+ "The patch-id value used in the request";
+ }
+
+ choice global-status {
+ description
+ "Report global errors or complete success.
+ If there is no case selected then errors
+ are reported in the edit-status container.";
+
+ case global-errors {
+ uses errors;
+ description
+ "This container will be present if global
+ errors unrelated to a specific edit occurred.";
+ }
+ leaf ok {
+ type empty;
+ description
+ "This leaf will be present if the request succeeded
+ and there are no errors reported in the edit-status
+ container.";
+ }
+ }
+
+ container edit-status {
+ description
+ "This container will be present if there are
+ edit-specific status responses to report.";
+
+ list edit {
+ key edit-id;
+
+ description
+ "Represents a list of status responses,
+ corresponding to edits in the YANG Patch
+ request message. If an edit entry was
+ skipped or not reached by the server,
+ then this list will not contain a corresponding
+ entry for that edit.";
+
+ leaf edit-id {
+ type string;
+ description
+ "Response status is for the edit list entry
+ with this edit-id value.";
+ }
+ choice edit-status-choice {
+ description
+ "A choice between different types of status
+ responses for each edit entry.";
+ leaf ok {
+ type empty;
+ description
+ "This edit entry was invoked without any
+ errors detected by the server associated
+ with this edit.";
+ }
+ leaf location {
+ type inet:uri;
+ description
+ "Contains the Location header value that would be
+ returned if this edit causes a new resource to be
+ created. If the edit identified by the same edit-id
+ value was successfully invoked and a new resource
+ was created, then this field will be returned
+ instead of 'ok'.";
+ }
+ case errors {
+ uses errors;
+ description
+ "The server detected errors associated with the
+ edit identified by the same edit-id value.";
+ }
+ }
+ }
+ }
+ }
+ } // grouping yang-patch-status
+
+
+ grouping errors {
+
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of a
+ YANG Patch errors report within a response message.";
+
+ container errors {
+ config false; // needed so list error does not need a key
+ description
+ "Represents an error report returned by the server if
+ a request results in an error.";
+
+ list error {
+ description
+ "An entry containing information about one
+ specific error that occurred while processing
+ a RESTCONF request.";
+ reference "RFC 6241, Section 4.3";
+
+ leaf error-type {
+ type enumeration {
+ enum transport {
+ description "The transport layer";
+ }
+ enum rpc {
+ description "The rpc or notification layer";
+ }
+ enum protocol {
+ description "The protocol operation layer";
+ }
+ enum application {
+ description "The server application layer";
+ }
+ }
+ mandatory true;
+ description
+ "The protocol layer where the error occurred.";
+ }
+
+ leaf error-tag {
+ type string;
+ mandatory true;
+ description
+ "The enumerated error tag.";
+ }
+
+ leaf error-app-tag {
+ type string;
+ description
+ "The application-specific error tag.";
+ }
+
+ leaf error-path {
+ type data-resource-identifier;
+ description
+ "The target data resource identifier associated
+ with the error, if any.";
+ }
+ leaf error-message {
+ type string;
+ description
+ "A message describing the error.";
+ }
+
+ container error-info {
+ description
+ "A container allowing additional information
+ to be included in the error report.";
+ // arbitrary anyxml content here
+ }
+ }
+ }
+ } // grouping errors
+
+ /** grouping restconf removed for testing purposes **/
+
+ grouping notification {
+ description
+ "Contains the notification message wrapper definition.";
+
+ container notification {
+ description
+ "RESTCONF notification message wrapper.";
+ leaf event-time {
+ type yang:date-and-time;
+ mandatory true;
+ description
+ "The time the event was generated by the
+ event source.";
+ reference
+ "RFC 5277, section 4, <eventTime> element.";
+ }
+
+ /* The YANG-specific notification container is encoded
+ * after the 'event-time' element. The format
+ * corresponds to the notificationContent element
+ * in RFC 5277, section 4. For example:
+ *
+ * module example-one {
+ * ...
+ * notification event1 { ... }
+ *
+ * }
+ *
+ * Encoded as element 'event1' in the namespace
+ * for module 'example-one'.
+ */
+ }
+ } // grouping notification
+
+ }
\ No newline at end of file
--- /dev/null
+module restconf-module-with-missing-list-module {
+ namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-rmwmlm";
+ prefix "restconf";
+
+ import ietf-yang-types { prefix yang; }
+ import ietf-inet-types { prefix inet; }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+
+ contact
+ "Editor: Andy Bierman
+ <mailto:andy@yumaworks.com>
+
+ Editor: Martin Bjorklund
+ <mailto:mbj@tail-f.com>
+
+ Editor: Kent Watsen
+ <mailto:kwatsen@juniper.net>
+
+ Editor: Rex Fernando
+ <mailto:rex@cisco.com>";
+
+ description
+ "This module contains conceptual YANG specifications
+ for the YANG Patch and error content that is used in
+ RESTCONF protocol messages. A conceptual container
+ representing the RESTCONF API nodes (media type
+ application/yang.api).
+
+ Note that the YANG definitions within this module do not
+ represent configuration data of any kind.
+ The YANG grouping statements provide a normative syntax
+ for XML and JSON message encoding purposes.
+ Copyright (c) 2013 IETF Trust and the persons identified as
+ authors of the code. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, is permitted pursuant to, and subject
+ to the license terms contained in, the Simplified BSD License
+ set forth in Section 4.c of the IETF Trust's Legal Provisions
+ Relating to IETF Documents
+ (http://trustee.ietf.org/license-info).
+
+ This version of this YANG module is part of RFC XXXX; see
+ the RFC itself for full legal notices.";
+
+ // RFC Ed.: replace XXXX with actual RFC number and remove this
+ // note.
+
+ // RFC Ed.: remove this note
+ // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+ // RFC Ed.: update the date below with the date of RFC publication
+ // and remove this note.
+ revision 2013-10-19 {
+ description
+ "Initial revision.";
+ reference
+ "RFC XXXX: RESTCONF Protocol.";
+ }
+
+ typedef data-resource-identifier {
+ type string {
+ length "1 .. max";
+ }
+ description
+ "Contains a Data Resource Identifier formatted string
+ to identify a specific data node. The data node that
+ uses this data type SHOULD define the document root
+ for data resource identifiers. The default document
+ root is the target datastore conceptual root node.
+ Data resource identifiers are defined relative to
+ this document root.";
+ reference
+ "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+ }
+
+ // this typedef is TBD; not currently used
+ typedef datastore-identifier {
+ type union {
+ type enumeration {
+ enum candidate {
+ description
+ "Identifies the NETCONF shared candidate datastore.";
+ reference
+ "RFC 6241, section 8.3";
+ }
+ enum running {
+ description
+ "Identifies the NETCONF running datastore.";
+ reference
+ "RFC 6241, section 5.1";
+ }
+ enum startup {
+ description
+ "Identifies the NETCONF startup datastore.";
+ reference
+ "RFC 6241, section 8.7";
+ }
+ }
+ type string;
+ }
+ description
+ "Contains a string to identify a specific datastore.
+ The enumerated datastore identifier values are
+ reserved for standard datastore names.";
+ }
+
+ typedef revision-identifier {
+ type string {
+ pattern '\d{4}-\d{2}-\d{2}';
+ }
+ description
+ "Represents a specific date in YYYY-MM-DD format.
+ TBD: make pattern more precise to exclude leading zeros.";
+ }
+
+ grouping yang-patch {
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of a
+ YANG Patch edit request message.";
+
+ container yang-patch {
+ description
+ "Represents a conceptual sequence of datastore edits,
+ called a patch. Each patch is given a client-assigned
+ patch identifier. Each edit MUST be applied
+ in ascending order, and all edits MUST be applied.
+ If any errors occur, then the target datastore MUST NOT
+ be changed by the patch operation.
+
+ A patch MUST be validated by the server to be a
+ well-formed message before any of the patch edits
+ are validated or attempted.
+
+ YANG datastore validation (defined in RFC 6020, section
+ 8.3.3) is performed after all edits have been
+ individually validated.
+
+ It is possible for a datastore constraint violation to occur
+ due to any node in the datastore, including nodes not
+ included in the edit list. Any validation errors MUST
+ be reported in the reply message.";
+
+ reference
+ "RFC 6020, section 8.3.";
+
+ leaf patch-id {
+ type string;
+ description
+ "An arbitrary string provided by the client to identify
+ the entire patch. This value SHOULD be present in any
+ audit logging records generated by the server for the
+ patch. Error messages returned by the server pertaining
+ to this patch will be identified by this patch-id value.";
+ }
+
+ leaf comment {
+ type string {
+ length "0 .. 1024";
+ }
+ description
+ "An arbitrary string provided by the client to describe
+ the entire patch. This value SHOULD be present in any
+ audit logging records generated by the server for the
+ patch.";
+ }
+
+ list edit {
+ key edit-id;
+ ordered-by user;
+
+ description
+ "Represents one edit within the YANG Patch
+ request message.";
+ leaf edit-id {
+ type string;
+ description
+ "Arbitrary string index for the edit.
+ Error messages returned by the server pertaining
+ to a specific edit will be identified by this
+ value.";
+ }
+
+ leaf operation {
+ type enumeration {
+ enum create {
+ description
+ "The target data node is created using the
+ supplied value, only if it does not already
+ exist.";
+ }
+ enum delete {
+ description
+ "Delete the target node, only if the data resource
+ currently exists, otherwise return an error.";
+ }
+ enum insert {
+ description
+ "Insert the supplied value into a user-ordered
+ list or leaf-list entry. The target node must
+ represent a new data resource.";
+ }
+ enum merge {
+ description
+ "The supplied value is merged with the target data
+ node.";
+ }
+ enum move {
+ description
+ "Move the target node. Reorder a user-ordered
+ list or leaf-list. The target node must represent
+ an existing data resource.";
+ }
+ enum replace {
+ description
+ "The supplied value is used to replace the target
+ data node.";
+ }
+ enum remove {
+ description
+ "Delete the target node if it currently exists.";
+ }
+ }
+ mandatory true;
+ description
+ "The datastore operation requested for the associated
+ edit entry";
+ }
+
+ leaf target {
+ type data-resource-identifier;
+ mandatory true;
+ description
+ "Identifies the target data resource for the edit
+ operation.";
+ }
+
+ leaf point {
+ when "(../operation = 'insert' or " +
+ "../operation = 'move') and " +
+ "(../where = 'before' or ../where = 'after')" {
+ description
+ "Point leaf only applies for insert or move
+ operations, before or after an existing entry.";
+ }
+ type data-resource-identifier;
+ description
+ "The absolute URL path for the data node that is being
+ used as the insertion point or move point for the
+ target of this edit entry.";
+ }
+
+ leaf where {
+ when "../operation = 'insert' or ../operation = 'move'" {
+ description
+ "Where leaf only applies for insert or move
+ operations.";
+ }
+ type enumeration {
+ enum before {
+ description
+ "Insert or move a data node before the data resource
+ identified by the 'point' parameter.";
+ }
+ enum after {
+ description
+ "Insert or move a data node after the data resource
+ identified by the 'point' parameter.";
+ }
+ enum first {
+ description
+ "Insert or move a data node so it becomes ordered
+ as the first entry.";
+ }
+ enum last {
+ description
+ "Insert or move a data node so it becomes ordered
+ as the last entry.";
+ }
+
+ }
+ default last;
+ description
+ "Identifies where a data resource will be inserted or
+ moved. YANG only allows these operations for
+ list and leaf-list data nodes that are ordered-by
+ user.";
+ }
+
+ anyxml value {
+ when "(../operation = 'create' or " +
+ "../operation = 'merge' " +
+ "or ../operation = 'replace' or " +
+ "../operation = 'insert')" {
+ description
+ "Value node only used for create, merge,
+ replace, and insert operations";
+ }
+ description
+ "Value used for this edit operation.";
+ }
+ }
+ }
+
+ } // grouping yang-patch
+
+
+ grouping yang-patch-status {
+
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of
+ YANG Patch status response message.";
+
+ container yang-patch-status {
+ description
+ "A container representing the response message
+ sent by the server after a YANG Patch edit
+ request message has been processed.";
+
+ leaf patch-id {
+ type string;
+ description
+ "The patch-id value used in the request";
+ }
+
+ choice global-status {
+ description
+ "Report global errors or complete success.
+ If there is no case selected then errors
+ are reported in the edit-status container.";
+
+ case global-errors {
+ uses errors;
+ description
+ "This container will be present if global
+ errors unrelated to a specific edit occurred.";
+ }
+ leaf ok {
+ type empty;
+ description
+ "This leaf will be present if the request succeeded
+ and there are no errors reported in the edit-status
+ container.";
+ }
+ }
+
+ container edit-status {
+ description
+ "This container will be present if there are
+ edit-specific status responses to report.";
+
+ list edit {
+ key edit-id;
+
+ description
+ "Represents a list of status responses,
+ corresponding to edits in the YANG Patch
+ request message. If an edit entry was
+ skipped or not reached by the server,
+ then this list will not contain a corresponding
+ entry for that edit.";
+
+ leaf edit-id {
+ type string;
+ description
+ "Response status is for the edit list entry
+ with this edit-id value.";
+ }
+ choice edit-status-choice {
+ description
+ "A choice between different types of status
+ responses for each edit entry.";
+ leaf ok {
+ type empty;
+ description
+ "This edit entry was invoked without any
+ errors detected by the server associated
+ with this edit.";
+ }
+ leaf location {
+ type inet:uri;
+ description
+ "Contains the Location header value that would be
+ returned if this edit causes a new resource to be
+ created. If the edit identified by the same edit-id
+ value was successfully invoked and a new resource
+ was created, then this field will be returned
+ instead of 'ok'.";
+ }
+ case errors {
+ uses errors;
+ description
+ "The server detected errors associated with the
+ edit identified by the same edit-id value.";
+ }
+ }
+ }
+ }
+ }
+ } // grouping yang-patch-status
+
+
+ grouping errors {
+
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of a
+ YANG Patch errors report within a response message.";
+
+ container errors {
+ config false; // needed so list error does not need a key
+ description
+ "Represents an error report returned by the server if
+ a request results in an error.";
+
+ list error {
+ description
+ "An entry containing information about one
+ specific error that occurred while processing
+ a RESTCONF request.";
+ reference "RFC 6241, Section 4.3";
+
+ leaf error-type {
+ type enumeration {
+ enum transport {
+ description "The transport layer";
+ }
+ enum rpc {
+ description "The rpc or notification layer";
+ }
+ enum protocol {
+ description "The protocol operation layer";
+ }
+ enum application {
+ description "The server application layer";
+ }
+ }
+ mandatory true;
+ description
+ "The protocol layer where the error occurred.";
+ }
+
+ leaf error-tag {
+ type string;
+ mandatory true;
+ description
+ "The enumerated error tag.";
+ }
+
+ leaf error-app-tag {
+ type string;
+ description
+ "The application-specific error tag.";
+ }
+
+ leaf error-path {
+ type data-resource-identifier;
+ description
+ "The target data resource identifier associated
+ with the error, if any.";
+ }
+ leaf error-message {
+ type string;
+ description
+ "A message describing the error.";
+ }
+
+ container error-info {
+ description
+ "A container allowing additional information
+ to be included in the error report.";
+ // arbitrary anyxml content here
+ }
+ }
+ }
+ } // grouping errors
+
+
+ grouping restconf {
+
+ description
+ "A grouping that contains a YANG container
+ representing the syntax and semantics of
+ the RESTCONF API resource.";
+
+ container restconf {
+ description
+ "Conceptual container representing the
+ application/yang.api resource type.";
+
+ container config {
+ description
+ "Container representing the application/yang.datastore
+ resource type. Represents the conceptual root of the
+ unified configuration datastore containing YANG data
+ nodes. The child nodes of this container are
+ configuration data resources (application/yang.data)
+ defined as top-level YANG data nodes from the modules
+ advertised by the server in /restconf/modules.";
+ }
+
+ container operational {
+ description
+ "Container representing the application/yang.datastore
+ resource type. Represents the conceptual root of the
+ operational data supported by the server. The child
+ nodes of this container are operational data resources
+ (application/yang.data) defined as top-level
+ YANG data nodes from the modules advertised by
+ the server in /restconf/modules.";
+ }
+
+ container modules {
+ description
+ "Contains a list of module description entries.
+ These modules are currently loaded into the server.";
+
+ // removed list module for testing purposes + added list test-list
+ list test-list {
+ leaf test-leaf {
+ type string;
+ }
+ }
+ }
+
+ container operations {
+ description
+ "Container for all operation resources
+ (application/yang.operation),
+
+ Each resource is represented as an empty leaf with the
+ name of the RPC operation from the YANG rpc statement.
+
+ E.g.;
+
+ POST /restconf/operations/show-log-errors
+
+ leaf show-log-errors {
+ type empty;
+ }
+ ";
+ }
+
+ container streams {
+ description
+ "Container representing the notification event streams
+ supported by the server.";
+ reference
+ "RFC 5277, Section 3.4, <streams> element.";
+
+ list stream {
+ key name;
+ description
+ "Each entry describes an event stream supported by
+ the server.";
+
+ leaf name {
+ type string;
+ description "The stream name";
+ reference "RFC 5277, Section 3.4, <name> element.";
+ }
+
+ leaf description {
+ type string;
+ description "Description of stream content";
+ reference
+ "RFC 5277, Section 3.4, <description> element.";
+ }
+
+ leaf replay-support {
+ type boolean;
+ description
+ "Indicates if replay buffer supported for this stream";
+ reference
+ "RFC 5277, Section 3.4, <replaySupport> element.";
+ }
+
+ leaf replay-log-creation-time {
+ type yang:date-and-time;
+ description
+ "Indicates the time the replay log for this stream
+ was created.";
+ reference
+ "RFC 5277, Section 3.4, <replayLogCreationTime>
+ element.";
+ }
+
+ leaf events {
+ type empty;
+ description
+ "Represents the entry point for establishing
+ notification delivery via server sent events.";
+ }
+ }
+ }
+
+ leaf version {
+ type enumeration {
+ enum "1.0" {
+ description
+ "Version 1.0 of the RESTCONF protocol.";
+ }
+ }
+ config false;
+ description
+ "Contains the RESTCONF protocol version.";
+ }
+ }
+ } // grouping restconf
+
+
+ grouping notification {
+ description
+ "Contains the notification message wrapper definition.";
+
+ container notification {
+ description
+ "RESTCONF notification message wrapper.";
+ leaf event-time {
+ type yang:date-and-time;
+ mandatory true;
+ description
+ "The time the event was generated by the
+ event source.";
+ reference
+ "RFC 5277, section 4, <eventTime> element.";
+ }
+
+ /* The YANG-specific notification container is encoded
+ * after the 'event-time' element. The format
+ * corresponds to the notificationContent element
+ * in RFC 5277, section 4. For example:
+ *
+ * module example-one {
+ * ...
+ * notification event1 { ... }
+ *
+ * }
+ *
+ * Encoded as element 'event1' in the namespace
+ * for module 'example-one'.
+ */
+ }
+ } // grouping notification
+
+ }
\ No newline at end of file
reference
"RFC 5277, Section 3.4, <streams> element.";
- /** deleted list stream for testing purposes **/
+ /** deleted list stream for testing purposes + added list test-list **/
+ list test-list {
+ leaf test-leaf {
+ type string;
+ }
+ }
}
leaf version {
--- /dev/null
+module mount-point {
+ namespace "mount:point";
+ prefix "mountp";
+ yang-version 1;
+
+ import parser-identifier-included { prefix pii; revision-date 2016-06-02; }
+
+ revision 2016-06-02 {
+ description "Initial revision.";
+ }
+
+ container mount-container {
+ leaf point-number {
+ type uint8;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+module parser-identifier-included {
+ namespace "parser:identifier:included";
+ prefix "parseridinc";
+ yang-version 1;
+
+ revision 2016-06-02 {
+ description
+ "Initial revision.";
+ }
+
+ list list-1 {
+ key "name revision";
+ description "List in grouping";
+
+ leaf name {
+ type string;
+ }
+
+ leaf revision {
+ type string;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+module parser-identifier {
+ namespace "parser:identifier";
+ prefix "parserid";
+ yang-version 1;
+
+ import parser-identifier-included { prefix pii; revision-date 2016-06-02; }
+
+ revision 2016-06-02 {
+ description
+ "Initial revision.";
+ }
+
+ container cont1 {
+ container cont2 {
+ list listTest {
+ uses group;
+ }
+ }
+ }
+
+ grouping group {
+ list list-in-grouping {
+ key name;
+
+ leaf name {
+ type string;
+ }
+
+ leaf leaf-A.B {
+ type uint8;
+ }
+ }
+ }
+
+ augment "/pii:list-1" {
+ leaf augment-leaf {
+ type string;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+module test-module {
+ namespace "test:module";
+ prefix "testm";
+ yang-version 1;
+
+ revision 2016-06-02 {
+ description "Initial revision.";
+ }
+}
\ No newline at end of file
--- /dev/null
+module deserializer-test-included {
+ namespace "deserializer:test:included";
+ prefix "dti";
+ yang-version 1;
+
+ revision 2016-06-06 {
+ description
+ "Initial revision.";
+ }
+
+ list augmented-list {
+ key list-key;
+
+ leaf list-key {
+ type uint16;
+ }
+
+ leaf list-value {
+ type string;
+ }
+ }
+}
--- /dev/null
+module deserializer-test {
+ namespace "deserializer:test";
+ prefix "dt";
+ yang-version 1;
+
+ import deserializer-test-included { prefix dti; revision-date 2016-06-06; }
+
+ revision 2016-06-06 {
+ description
+ "Initial revision.";
+ }
+
+ container contA {
+ leaf-list leaf-list-A {
+ type string;
+ }
+
+ leaf leaf-A {
+ type string;
+ }
+
+ list list-A {
+ key list-key;
+
+ leaf list-key {
+ type uint8;
+ }
+
+ leaf-list leaf-list-AA {
+ type string;
+ }
+ }
+ }
+
+ leaf-list leaf-list-0 {
+ type boolean;
+ }
+
+ leaf leaf-0 {
+ type string;
+ }
+
+ list list-no-key {
+ leaf name {
+ type string;
+ }
+
+ leaf number {
+ type uint8;
+ }
+ }
+
+ list list-one-key {
+ key name;
+
+ leaf name {
+ type string;
+ }
+
+ leaf number {
+ type uint8;
+ }
+ }
+
+ list list-multiple-keys {
+ key "name number enabled";
+
+ leaf name {
+ type string;
+ }
+
+ leaf number {
+ type uint8;
+ }
+
+ leaf enabled {
+ type boolean;
+ }
+
+ leaf string-value {
+ type string;
+ }
+ }
+
+ augment "/dti:augmented-list" {
+ leaf augmented-leaf {
+ type string;
+ }
+ }
+}
leaf key5 {
type string;
}
- leaf result{
+ leaf result {
type string;
}
}
--- /dev/null
+module serializer-test-included {
+ namespace "serializer:test:included";
+ prefix "sti";
+ yang-version 1;
+
+ revision 2016-06-06 {
+ description
+ "Initial revision.";
+ }
+
+ list augmented-list {
+ key list-key;
+
+ leaf list-key {
+ type uint16;
+ }
+
+ leaf list-value {
+ type string;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+module serializer-test {
+ namespace "serializer:test";
+ prefix "st";
+ yang-version 1;
+
+ import serializer-test-included { prefix sti; revision-date 2016-06-06; }
+
+ revision 2016-06-06 {
+ description
+ "Initial revision.";
+ }
+
+ container contA {
+ leaf-list leaf-list-A {
+ type string;
+ }
+
+ leaf leaf-A {
+ type string;
+ }
+
+ list list-A {
+ key list-key;
+
+ leaf list-key {
+ type uint8;
+ }
+
+ leaf-list leaf-list-AA {
+ type string;
+ }
+ }
+ }
+
+ leaf-list leaf-list-0 {
+ type boolean;
+ }
+
+ leaf leaf-0 {
+ type string;
+ }
+
+ list list-no-key {
+ leaf name {
+ type string;
+ }
+
+ leaf number {
+ type uint8;
+ }
+ }
+
+ list list-one-key {
+ key name;
+
+ leaf name {
+ type string;
+ }
+
+ leaf number {
+ type uint8;
+ }
+ }
+
+ list list-multiple-keys {
+ key "name number enabled";
+
+ leaf name {
+ type string;
+ }
+
+ leaf number {
+ type uint8;
+ }
+
+ leaf enabled {
+ type boolean;
+ }
+ }
+
+ augment "/sti:augmented-list" {
+ leaf augmented-leaf {
+ type string;
+ }
+ }
+}
\ No newline at end of file