<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
- <version>${slf4j.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>binding-generator-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-test-model</artifactId>
+ <scope>test</scope>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
<build>
*/
package org.opendaylight.controller.md.sal.dom.store.impl;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Multimap;
+import static org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.builder;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-import static org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.builder;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
/**
* Resolve Data Change Events based on modifications and listeners
Builder subtree = builder(DataChangeScope.SUBTREE).
setBefore(modification.getDataBefore().get()).
setAfter(modification.getDataAfter().get());
-
+ boolean oneModified = false;
for (DataTreeCandidateNode childMod : modification.getChildNodes()) {
PathArgument childId = childMod.getIdentifier();
InstanceIdentifier childPath = path.node(childId);
Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
+
switch (childMod.getModificationType()) {
case WRITE:
case MERGE:
case DELETE:
one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod));
+ oneModified = true;
break;
case SUBTREE_MODIFIED:
subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod));
break;
}
}
- DOMImmutableDataChangeEvent oneChangeEvent = one.build();
- subtree.merge(oneChangeEvent);
+ final DOMImmutableDataChangeEvent oneChangeEvent;
+ if(oneModified) {
+ one.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
+ oneChangeEvent = one.build();
+ subtree.merge(oneChangeEvent);
+ } else {
+ oneChangeEvent = null;
+ subtree.addUpdated(path, modification.getDataBefore().get(), modification.getDataAfter().get());
+ }
DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
if (!listeners.isEmpty()) {
- addPartialTask(listeners, oneChangeEvent);
+ if(oneChangeEvent != null) {
+ addPartialTask(listeners, oneChangeEvent);
+ }
addPartialTask(listeners, subtreeEvent);
}
return subtreeEvent;
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.opendaylight.controller.md.sal.dom.store.impl.DatastoreTestTask.WriteTransactionCustomizer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.Top;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.TwoLevelList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.top.level.list.NestedList;
+import org.opendaylight.yangtools.sal.binding.generator.impl.ModuleInfoBackedContext;
+import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
+import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+import com.google.common.util.concurrent.MoreExecutors;
+
+public abstract class AbstractDataChangeListenerTest {
+
+ protected static final InstanceIdentifier TOP_LEVEL = InstanceIdentifier
+ .of(Top.QNAME);
+ private static final QName NAME_QNAME = QName.create(Top.QNAME, "name");
+ protected static final String FOO = "foo";
+ protected static final String BAR = "bar";
+ protected static final String BAZ = "baz";
+
+ private InMemoryDOMDataStore datastore;
+ private SchemaContext schemaContext;
+
+ @Before
+ public final void setup() throws Exception {
+ YangModuleInfo moduleInfo = BindingReflections
+ .getModuleInfo(TwoLevelList.class);
+ ModuleInfoBackedContext context = ModuleInfoBackedContext.create();
+ context.registerModuleInfo(moduleInfo);
+ schemaContext = context.tryToCreateSchemaContext().get();
+ datastore = new InMemoryDOMDataStore("TEST",
+ MoreExecutors.sameThreadExecutor());
+ datastore.onGlobalContextUpdated(schemaContext);
+ }
+
+ public final DatastoreTestTask newTestTask() {
+ return new DatastoreTestTask(datastore).cleanup(DatastoreTestTask
+ .simpleDelete(TOP_LEVEL));
+ }
+
+
+ public static final InstanceIdentifier path(final String topName,
+ final String nestedName) {
+ return path(topName).node(NestedList.QNAME).node(
+ new NodeIdentifierWithPredicates(NestedList.QNAME, NAME_QNAME,
+ nestedName));
+ }
+
+ public static final InstanceIdentifier path(final String topName) {
+ return TOP_LEVEL.node(TopLevelList.QNAME).node(
+ new NodeIdentifierWithPredicates(TopLevelList.QNAME,
+ NAME_QNAME, topName));
+ }
+
+ protected static DataContainerNodeAttrBuilder<NodeIdentifier, ContainerNode> top() {
+ return Builders.containerBuilder().withNodeIdentifier(
+ new NodeIdentifier(Top.QNAME));
+ }
+
+
+
+ protected static void assertEmpty(final Collection<?> set) {
+ Assert.assertTrue(set.isEmpty());
+ }
+
+ protected static void assertEmpty(final Map<?,?> set) {
+ Assert.assertTrue(set.isEmpty());
+ }
+
+ protected static <K> void assertContains(final Collection<K> set, final K... values) {
+ for (K key : values) {
+ Assert.assertTrue(set.contains(key));
+ }
+
+ }
+
+ protected static <K> void assertNotContains(final Collection<K> set, final K... values) {
+ for (K key : values) {
+ Assert.assertFalse(set.contains(key));
+ }
+ }
+
+ protected static <K> void assertContains(final Map<K,?> map, final K... values) {
+ for (K key : values) {
+ Assert.assertTrue(map.containsKey(key));
+ }
+ }
+
+ protected static <K> void assertNotContains(final Map<K,?> map, final K... values) {
+ for (K key : values) {
+ Assert.assertFalse(map.containsKey(key));
+ }
+ }
+
+ protected static CollectionNodeBuilder<MapEntryNode, MapNode> topLevelMap() {
+ return ImmutableNodes.mapNodeBuilder(TopLevelList.QNAME);
+ }
+
+ protected static CollectionNodeBuilder<MapEntryNode, OrderedMapNode> nestedMap() {
+ return Builders.orderedMapBuilder().withNodeIdentifier(new NodeIdentifier(NestedList.QNAME));
+ }
+
+ public static DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> topLevelList(
+ final String key) {
+ return ImmutableNodes.mapEntryBuilder(TopLevelList.QNAME, NAME_QNAME,
+ key);
+ }
+
+ public static DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> nestedList(
+ final String key) {
+ return ImmutableNodes
+ .mapEntryBuilder(NestedList.QNAME, NAME_QNAME, key);
+ }
+
+ public static final WriteTransactionCustomizer writeOneTopMultipleNested(
+ final String topName, final String... nestedName) {
+ CollectionNodeBuilder<MapEntryNode, OrderedMapNode> nestedMapBuilder = nestedMap();
+ for (String nestedItem : nestedName) {
+ nestedMapBuilder.addChild(nestedList(nestedItem).build());
+ }
+
+ final ContainerNode data = top().addChild(
+ topLevelMap().addChild(
+ topLevelList(topName)
+ .addChild(nestedMapBuilder.build()).build())
+ .build()).build();
+
+ return DatastoreTestTask.simpleWrite(TOP_LEVEL, data);
+ }
+
+ public static final WriteTransactionCustomizer deleteNested(final String topName,
+ final String nestedName) {
+ return DatastoreTestTask.simpleDelete(path(topName, nestedName));
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
+import org.opendaylight.controller.sal.core.spi.data.DOMStore;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.SettableFuture;
+
+public class DatastoreTestTask {
+
+ private final DOMStore store;
+ private AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>> changeListener;
+
+ private WriteTransactionCustomizer setup;
+ private WriteTransactionCustomizer write;
+ private ReadTransactionVerifier read;
+ private WriteTransactionCustomizer cleanup;
+ private InstanceIdentifier changePath;
+ private DataChangeScope changeScope;
+ private boolean postSetup = false;
+ private final ChangeEventListener internalListener;
+
+ public DatastoreTestTask(final DOMStore datastore) {
+ this.store = datastore;
+ internalListener = new ChangeEventListener();
+ }
+
+ public DatastoreTestTask changeListener(final InstanceIdentifier path, final DataChangeScope scope,
+ final AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>> changeListener) {
+ this.changeListener = changeListener;
+ this.changePath = path;
+ this.changeScope = scope;
+ return this;
+ }
+
+ public DatastoreTestTask changeListener(final InstanceIdentifier path, final DataChangeScope scope) {
+ this.changePath = path;
+ this.changeScope = scope;
+ return this;
+ }
+
+ public DatastoreTestTask setup(final WriteTransactionCustomizer setup) {
+ this.setup = setup;
+ return this;
+ }
+
+ public DatastoreTestTask test(final WriteTransactionCustomizer write) {
+ this.write = write;
+ return this;
+ }
+
+ public DatastoreTestTask read(final ReadTransactionVerifier read) {
+ this.read = read;
+ return this;
+ }
+
+ public DatastoreTestTask cleanup(final WriteTransactionCustomizer cleanup) {
+ this.cleanup = cleanup;
+ return this;
+ }
+
+ public void run() throws InterruptedException, ExecutionException {
+ if (setup != null) {
+ execute(setup);
+ }
+ ListenerRegistration<ChangeEventListener> registration = null;
+ if (changePath != null) {
+ registration = store.registerChangeListener(changePath, internalListener, changeScope);
+ }
+
+ Preconditions.checkState(write != null, "Write Transaction must be set.");
+ postSetup = true;
+ execute(write);
+ if (registration != null) {
+ registration.close();
+ }
+ if (changeListener != null) {
+ changeListener.onDataChanged(internalListener.receivedChange.get());
+ }
+ if (read != null) {
+ read.verify(store.newReadOnlyTransaction());
+ }
+ if (cleanup != null) {
+ execute(cleanup);
+ }
+ }
+
+ public Future<AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>>> getChangeEvent() {
+ return internalListener.receivedChange;
+ }
+
+ private void execute(final WriteTransactionCustomizer writeCustomizer) throws InterruptedException,
+ ExecutionException {
+ DOMStoreReadWriteTransaction tx = store.newReadWriteTransaction();
+ writeCustomizer.customize(tx);
+ DOMStoreThreePhaseCommitCohort cohort = tx.ready();
+ assertTrue(cohort.canCommit().get());
+ cohort.preCommit().get();
+ cohort.commit().get();
+ }
+
+ public interface WriteTransactionCustomizer {
+ public void customize(DOMStoreReadWriteTransaction tx);
+ }
+
+ public interface ReadTransactionVerifier {
+ public void verify(DOMStoreReadTransaction tx);
+ }
+
+ private final class ChangeEventListener implements
+ AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>> {
+
+ protected final SettableFuture<AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>>> receivedChange = SettableFuture
+ .create();
+
+ @Override
+ public void onDataChanged(final AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change) {
+ if (postSetup) {
+ receivedChange.set(change);
+ }
+ }
+ }
+
+ public static final WriteTransactionCustomizer simpleWrite(final InstanceIdentifier path,
+ final NormalizedNode<?, ?> data) {
+ return new WriteTransactionCustomizer() {
+
+ @Override
+ public void customize(final DOMStoreReadWriteTransaction tx) {
+ tx.write(path, data);
+ }
+ };
+ }
+
+ public static final WriteTransactionCustomizer simpleMerge(final InstanceIdentifier path,
+ final NormalizedNode<?, ?> data) {
+ return new WriteTransactionCustomizer() {
+
+ @Override
+ public void customize(final DOMStoreReadWriteTransaction tx) {
+ tx.merge(path, data);
+ }
+ };
+ }
+
+ public static final WriteTransactionCustomizer simpleDelete(final InstanceIdentifier path) {
+ return new WriteTransactionCustomizer() {
+ @Override
+ public void customize(final DOMStoreReadWriteTransaction tx) {
+ tx.delete(path);
+ }
+ };
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import java.util.concurrent.ExecutionException;
+
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.store.impl.DatastoreTestTask.WriteTransactionCustomizer;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
+
+public abstract class DefaultDataChangeListenerTestSuite extends AbstractDataChangeListenerTest {
+
+ protected static final String FOO_SIBLING = "foo-sibling";
+
+ abstract protected void customizeTask(DatastoreTestTask task);
+
+ @Test
+ public final void putTopLevelOneNested() throws InterruptedException, ExecutionException {
+
+ DatastoreTestTask task = newTestTask().test(writeOneTopMultipleNested(FOO, BAR));
+ customizeTask(task);
+ task.run();
+ putTopLevelOneNested(task);
+ }
+
+ @Test
+ public final void existingTopWriteSibling() throws InterruptedException, ExecutionException {
+ DatastoreTestTask task = newTestTask().setup(writeOneTopMultipleNested(FOO)).test(
+ new WriteTransactionCustomizer() {
+ @Override
+ public void customize(final DOMStoreReadWriteTransaction tx) {
+ tx.write(path(FOO_SIBLING), topLevelList(FOO_SIBLING).build());
+ }
+ });
+ customizeTask(task);
+ task.run();
+ existingTopWriteSibling(task);
+ }
+
+ protected abstract void existingTopWriteSibling(DatastoreTestTask task) throws InterruptedException, ExecutionException;
+
+
+ @Test
+ public final void existingTopWriteTwoNested() throws InterruptedException, ExecutionException {
+ DatastoreTestTask task = newTestTask().setup(writeOneTopMultipleNested(FOO)).test(
+ new WriteTransactionCustomizer() {
+ @Override
+ public void customize(final DOMStoreReadWriteTransaction tx) {
+ tx.write(path(FOO,BAR), nestedList(BAR).build());
+ tx.write(path(FOO,BAZ), nestedList(BAZ).build());
+ }
+ });
+ customizeTask(task);
+ task.run();
+ existingTopWriteTwoNested(task);
+ }
+
+ protected abstract void existingTopWriteTwoNested(DatastoreTestTask task) throws InterruptedException, ExecutionException;
+
+
+ @Test
+ public final void existingOneNestedWriteAdditionalNested() throws InterruptedException, ExecutionException {
+ DatastoreTestTask task = newTestTask().setup(writeOneTopMultipleNested(FOO, BAR)).test(
+ new WriteTransactionCustomizer() {
+ @Override
+ public void customize(final DOMStoreReadWriteTransaction tx) {
+ tx.write(path(FOO,BAZ), nestedList(BAZ).build());
+ }
+ });
+ customizeTask(task);
+ task.run();
+ existingOneNestedWriteAdditionalNested(task);
+ }
+
+ protected abstract void existingOneNestedWriteAdditionalNested(DatastoreTestTask task) throws InterruptedException, ExecutionException;
+
+ protected abstract void putTopLevelOneNested(DatastoreTestTask task) throws InterruptedException,
+ ExecutionException;
+
+ @Test
+ public final void replaceTopLevelNestedChanged() throws InterruptedException, ExecutionException {
+ DatastoreTestTask task = newTestTask().setup(writeOneTopMultipleNested(FOO, BAR)).test(
+ writeOneTopMultipleNested(FOO, BAZ));
+ customizeTask(task);
+ task.run();
+ replaceTopLevelNestedChanged(task);
+ }
+
+ protected abstract void replaceTopLevelNestedChanged(DatastoreTestTask task) throws InterruptedException,
+ ExecutionException;
+
+ @Test
+ public final void putTopLevelWithTwoNested() throws InterruptedException, ExecutionException {
+
+ DatastoreTestTask task = newTestTask().test(writeOneTopMultipleNested(FOO, BAR, BAZ));
+ customizeTask(task);
+ task.run();
+ putTopLevelWithTwoNested(task);
+ }
+
+ protected abstract void putTopLevelWithTwoNested(DatastoreTestTask task) throws InterruptedException,
+ ExecutionException;
+
+ @Test
+ public final void twoNestedExistsOneIsDeleted() throws InterruptedException, ExecutionException {
+
+ DatastoreTestTask task = newTestTask().setup(writeOneTopMultipleNested(FOO, BAR, BAZ)).test(
+ deleteNested(FOO, BAZ));
+ customizeTask(task);
+ task.run();
+ twoNestedExistsOneIsDeleted(task);
+ }
+
+ protected abstract void twoNestedExistsOneIsDeleted(DatastoreTestTask task) throws InterruptedException,
+ ExecutionException;
+
+ @Test
+ public final void nestedListExistsRootDeleted() throws InterruptedException, ExecutionException {
+
+ DatastoreTestTask task = newTestTask().cleanup(null).setup(writeOneTopMultipleNested(FOO, BAR, BAZ))
+ .test(DatastoreTestTask.simpleDelete(TOP_LEVEL));
+ customizeTask(task);
+ task.run();
+ nestedListExistsRootDeleted(task);
+ }
+
+ protected abstract void nestedListExistsRootDeleted(DatastoreTestTask task) throws InterruptedException,
+ ExecutionException;
+}
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
-import com.google.common.base.Optional;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.MoreExecutors;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.ExecutionException;
+
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-import java.util.concurrent.ExecutionException;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
public class InMemoryDataStoreTest {
/**
* Reads /test from readTx Read should return container.
*/
- private static Optional<NormalizedNode<?, ?>> assertTestContainerExists(DOMStoreReadTransaction readTx)
+ private static Optional<NormalizedNode<?, ?>> assertTestContainerExists(final DOMStoreReadTransaction readTx)
throws InterruptedException, ExecutionException {
ListenableFuture<Optional<NormalizedNode<?, ?>>> writeTxContainer = readTx.read(TestModel.TEST_PATH);
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import java.util.concurrent.ExecutionException;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class RootScopeSubtreeTest extends DefaultDataChangeListenerTestSuite {
+
+ @Override
+ protected void customizeTask(final DatastoreTestTask task) {
+ task.changeListener(TOP_LEVEL, DataChangeScope.SUBTREE);
+ }
+
+ @Override
+ public void putTopLevelOneNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), TOP_LEVEL, path(FOO), path(FOO, BAR));
+ assertEmpty(change.getUpdatedData());
+ assertEmpty(change.getRemovedPaths());
+ }
+
+ @Override
+ public void replaceTopLevelNestedChanged(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), path(FOO, BAZ));
+ assertContains(change.getUpdatedData(), TOP_LEVEL, path(FOO));
+ assertContains(change.getRemovedPaths(), path(FOO, BAR));
+ }
+
+ @Override
+ protected void putTopLevelWithTwoNested(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), TOP_LEVEL, path(FOO), path(FOO, BAR), path(FOO, BAZ));
+ assertEmpty(change.getUpdatedData());
+ assertEmpty(change.getRemovedPaths());
+ }
+
+ @Override
+ protected void twoNestedExistsOneIsDeleted(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertEmpty(change.getCreatedData());
+ assertContains(change.getUpdatedData(), TOP_LEVEL, path(FOO));
+ assertContains(change.getRemovedPaths(), path(FOO, BAZ));
+ }
+
+ @Override
+ protected void nestedListExistsRootDeleted(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertEmpty(change.getCreatedData());
+ assertEmpty(change.getUpdatedData());
+ assertContains(change.getRemovedPaths(), TOP_LEVEL, path(FOO), path(FOO, BAR), path(FOO, BAZ));
+ }
+
+ @Override
+ protected void existingOneNestedWriteAdditionalNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), path(FOO,BAZ));
+ assertNotContains(change.getCreatedData(), path(FOO,BAR));
+ assertContains(change.getUpdatedData(), TOP_LEVEL, path(FOO));
+ assertEmpty(change.getRemovedPaths());
+ }
+
+ @Override
+ protected void existingTopWriteTwoNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), path(FOO,BAR),path(FOO,BAZ));
+ assertContains(change.getUpdatedData(), TOP_LEVEL, path(FOO));
+ assertNotContains(change.getUpdatedData(), path(FOO,BAR));
+ assertEmpty(change.getRemovedPaths());
+ }
+
+ @Override
+ protected void existingTopWriteSibling(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), path(FOO_SIBLING));
+ assertContains(change.getUpdatedData(), TOP_LEVEL);
+ assertNotContains(change.getUpdatedData(), path(FOO));
+ assertEmpty(change.getRemovedPaths());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class WildcardedScopeBaseTest extends DefaultDataChangeListenerTestSuite {
+
+ private static final InstanceIdentifier TOP_LEVEL_LIST_ALL = TOP_LEVEL.node(TopLevelList.QNAME).node(
+ TopLevelList.QNAME);
+
+ @Override
+ protected void customizeTask(final DatastoreTestTask task) {
+ task.changeListener(TOP_LEVEL_LIST_ALL, DataChangeScope.BASE);
+ }
+
+ @Override
+ public void putTopLevelOneNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertNotNull(change);
+
+ assertNotContains(change.getCreatedData(), TOP_LEVEL);
+ assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR));
+
+ assertEmpty(change.getUpdatedData());
+ assertEmpty(change.getRemovedPaths());
+
+ }
+
+ @Override
+ public void replaceTopLevelNestedChanged(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+ assertNotNull(change);
+
+ assertContains(change.getCreatedData(), path(FOO, BAZ));
+ assertContains(change.getUpdatedData(), path(FOO));
+ assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+ assertContains(change.getRemovedPaths(), path(FOO, BAR));
+
+ }
+
+ @Override
+ protected void putTopLevelWithTwoNested(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+ assertNotNull(change);
+ assertFalse(change.getCreatedData().isEmpty());
+
+ assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR), path(FOO, BAZ));
+ assertNotContains(change.getCreatedData(), TOP_LEVEL);
+ assertEmpty(change.getUpdatedData());
+ assertEmpty(change.getRemovedPaths());
+
+ }
+
+ @Override
+ protected void twoNestedExistsOneIsDeleted(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ Future<?> future = task.getChangeEvent();
+ /*
+ * Base listener should be notified only and only if actual node changed its state,
+ * since deletion of child, did not result in change of node we are listening
+ * for, we should not be getting data change event
+ * and this means settable future containing receivedDataChangeEvent is not done.
+ *
+ */
+ assertFalse(future.isDone());
+ }
+
+ @Override
+ public void nestedListExistsRootDeleted(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertEmpty(change.getCreatedData());
+ assertEmpty(change.getUpdatedData());
+
+ assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+ assertContains(change.getRemovedPaths(), path(FOO),path(FOO, BAZ),path(FOO,BAR));
+ }
+
+ @Override
+ protected void existingOneNestedWriteAdditionalNested(final DatastoreTestTask task) {
+ Future<?> future = task.getChangeEvent();
+ /*
+ * One listener should be notified only and only if actual node changed its state,
+ * since deletion of nested child (in this case /nested-list/nested-list[foo],
+ * did not result in change of node we are listening
+ * for, we should not be getting data change event
+ * and this means settable future containing receivedDataChangeEvent is not done.
+ *
+ */
+ assertFalse(future.isDone());
+ }
+
+ @Override
+ protected void existingTopWriteTwoNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ Future<?> future = task.getChangeEvent();
+ /*
+ * One listener should be notified only and only if actual node changed its state,
+ * since deletion of nested child (in this case /nested-list/nested-list[foo],
+ * did not result in change of node we are listening
+ * for, we should not be getting data change event
+ * and this means settable future containing receivedDataChangeEvent is not done.
+ *
+ */
+ assertFalse(future.isDone());
+ }
+
+ @Override
+ protected void existingTopWriteSibling(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), path(FOO_SIBLING));
+ assertNotContains(change.getUpdatedData(), path(FOO), TOP_LEVEL);
+ assertEmpty(change.getRemovedPaths());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class WildcardedScopeOneTest extends DefaultDataChangeListenerTestSuite {
+
+ private static final InstanceIdentifier TOP_LEVEL_LIST_ALL = TOP_LEVEL.node(TopLevelList.QNAME).node(
+ TopLevelList.QNAME);
+
+ @Override
+ protected void customizeTask(final DatastoreTestTask task) {
+ task.changeListener(TOP_LEVEL_LIST_ALL, DataChangeScope.ONE);
+ }
+
+ @Override
+ public void putTopLevelOneNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertNotNull(change);
+
+ assertNotContains(change.getCreatedData(), TOP_LEVEL);
+ assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR));
+
+ assertEmpty(change.getUpdatedData());
+ assertEmpty(change.getRemovedPaths());
+
+ }
+
+ @Override
+ public void replaceTopLevelNestedChanged(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+ assertNotNull(change);
+
+ assertContains(change.getCreatedData(), path(FOO, BAZ));
+ assertContains(change.getUpdatedData(), path(FOO));
+ assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+ assertContains(change.getRemovedPaths(), path(FOO, BAR));
+
+ }
+
+ @Override
+ protected void putTopLevelWithTwoNested(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+ assertNotNull(change);
+ assertFalse(change.getCreatedData().isEmpty());
+
+ assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR), path(FOO, BAZ));
+ assertNotContains(change.getCreatedData(), TOP_LEVEL);
+ assertEmpty(change.getUpdatedData());
+ assertEmpty(change.getRemovedPaths());
+
+ }
+
+ @Override
+ protected void twoNestedExistsOneIsDeleted(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ Future<?> future = task.getChangeEvent();
+ /*
+ * One listener should be notified only and only if actual node changed its state,
+ * since deletion of nested child (in this case /nested-list/nested-list[foo],
+ * did not result in change of node we are listening
+ * for, we should not be getting data change event
+ * and this means settable future containing receivedDataChangeEvent is not done.
+ *
+ */
+ assertFalse(future.isDone());
+ }
+
+ @Override
+ public void nestedListExistsRootDeleted(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertEmpty(change.getCreatedData());
+ assertEmpty(change.getUpdatedData());
+
+ assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+ assertContains(change.getRemovedPaths(), path(FOO),path(FOO, BAZ),path(FOO,BAR));
+ }
+
+ @Override
+ protected void existingOneNestedWriteAdditionalNested(final DatastoreTestTask task) {
+ Future<?> future = task.getChangeEvent();
+ /*
+ * One listener should be notified only and only if actual node changed its state,
+ * since deletion of nested child (in this case /nested-list/nested-list[foo],
+ * did not result in change of node we are listening
+ * for, we should not be getting data change event
+ * and this means settable future containing receivedDataChangeEvent is not done.
+ *
+ */
+ assertFalse(future.isDone());
+ }
+
+ @Override
+ protected void existingTopWriteTwoNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ Future<?> future = task.getChangeEvent();
+ /*
+ * One listener should be notified only and only if actual node changed its state,
+ * since deletion of nested child (in this case /nested-list/nested-list[foo],
+ * did not result in change of node we are listening
+ * for, we should not be getting data change event
+ * and this means settable future containing receivedDataChangeEvent is not done.
+ *
+ */
+ assertFalse(future.isDone());
+ }
+
+ @Override
+ protected void existingTopWriteSibling(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), path(FOO_SIBLING));
+ assertNotContains(change.getUpdatedData(),path(FOO), TOP_LEVEL);
+ assertEmpty(change.getRemovedPaths());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 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.controller.md.sal.dom.store.impl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.ExecutionException;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.md.sal.test.list.rev140701.two.level.list.TopLevelList;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class WildcardedScopeSubtreeTest extends DefaultDataChangeListenerTestSuite {
+
+ private static final InstanceIdentifier TOP_LEVEL_LIST_ALL = TOP_LEVEL.node(TopLevelList.QNAME).node(
+ TopLevelList.QNAME);
+
+ @Override
+ protected void customizeTask(final DatastoreTestTask task) {
+ task.changeListener(TOP_LEVEL_LIST_ALL, DataChangeScope.SUBTREE);
+ }
+
+ @Override
+ public void putTopLevelOneNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertNotContains(change.getCreatedData(), TOP_LEVEL);
+ assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR));
+ assertEmpty(change.getUpdatedData());
+ assertEmpty(change.getRemovedPaths());
+
+ }
+
+ @Override
+ public void replaceTopLevelNestedChanged(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+ assertNotNull(change);
+
+ assertContains(change.getCreatedData(), path(FOO, BAZ));
+ assertContains(change.getUpdatedData(), path(FOO));
+ assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+ assertContains(change.getRemovedPaths(), path(FOO, BAR));
+
+ }
+
+ @Override
+ protected void putTopLevelWithTwoNested(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+ assertNotNull(change);
+ assertFalse(change.getCreatedData().isEmpty());
+
+ assertContains(change.getCreatedData(), path(FOO), path(FOO, BAR), path(FOO, BAZ));
+ assertNotContains(change.getCreatedData(), TOP_LEVEL);
+ assertEmpty(change.getUpdatedData());
+ assertEmpty(change.getRemovedPaths());
+
+ }
+
+ @Override
+ protected void twoNestedExistsOneIsDeleted(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+ assertNotNull(change);
+ assertTrue(change.getCreatedData().isEmpty());
+ assertContains(change.getUpdatedData(), path(FOO));
+ assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+ assertContains(change.getRemovedPaths(),path(FOO, BAZ));
+ }
+
+ @Override
+ public void nestedListExistsRootDeleted(final DatastoreTestTask task) throws InterruptedException,
+ ExecutionException {
+
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertEmpty(change.getCreatedData());
+ assertEmpty(change.getUpdatedData());
+
+ assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+ assertContains(change.getRemovedPaths(), path(FOO),path(FOO, BAZ),path(FOO,BAR));
+ }
+
+ @Override
+ protected void existingOneNestedWriteAdditionalNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), path(FOO,BAZ));
+ assertNotContains(change.getCreatedData(), path(FOO,BAR));
+ assertContains(change.getUpdatedData(), path(FOO));
+ assertNotContains(change.getUpdatedData(), TOP_LEVEL);
+ assertEmpty(change.getRemovedPaths());
+ }
+
+ @Override
+ protected void existingTopWriteTwoNested(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), path(FOO,BAR),path(FOO,BAZ));
+ assertContains(change.getUpdatedData(), path(FOO));
+ assertNotContains(change.getUpdatedData(), TOP_LEVEL, path(FOO,BAR));
+ assertEmpty(change.getRemovedPaths());
+ }
+
+ @Override
+ protected void existingTopWriteSibling(final DatastoreTestTask task) throws InterruptedException, ExecutionException {
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change = task.getChangeEvent().get();
+
+ assertContains(change.getCreatedData(), path(FOO_SIBLING));
+ assertNotContains(change.getUpdatedData(), path(FOO), TOP_LEVEL);
+ assertEmpty(change.getRemovedPaths());
+ }
+}
--- /dev/null
+module opendaylight-mdsal-list-test {
+
+ namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:test:list";
+ prefix list-test;
+
+
+ description
+ "This module contains a collection of YANG definitions used for
+ some test cases.";
+
+ revision 2014-07-01 {
+ description
+ "Test model for testing data broker with nested lists.";
+ }
+
+ grouping two-level-list {
+ list top-level-list {
+ description
+ "Top Level List";
+ key "name";
+ leaf name {
+ type string;
+ }
+ list nested-list {
+ key "name";
+ leaf name {
+ type string;
+ }
+ leaf type {
+ type string;
+ mandatory true;
+ description
+ "Mandatory type of list.";
+ }
+ ordered-by user;
+ description
+ "A list of service functions that compose the service chain";
+ }
+ }
+ }
+
+ container top {
+ uses two-level-list;
+ }
+
+ rpc put-top {
+ input {
+ uses two-level-list;
+ }
+ }
+}
\ No newline at end of file