/* * 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.mdsal.dom.broker; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.anyCollection; import static org.mockito.Matchers.anyMap; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.mdsal.dom.api.DOMDataTreeListener; import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor; import org.opendaylight.mdsal.dom.broker.util.TestModel; import org.opendaylight.mdsal.dom.store.inmemory.InMemoryDOMDataTreeShard; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShardedDOMDataTreeTest { private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeProducerMultiShardTest.class); private static SchemaContext schemaContext = null; static { try { schemaContext = TestModel.createTestContext(); } catch (final ReactorException e) { LOG.error("Unable to create schema context for TestModel", e); } } private static final DOMDataTreeIdentifier ROOT_ID = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, YangInstanceIdentifier.EMPTY); private static final DOMDataTreeIdentifier TEST_ID = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, TestModel.TEST_PATH); private static final DOMDataTreeIdentifier INNER_CONTAINER_ID = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, TestModel.INNER_CONTAINER_PATH); private InMemoryDOMDataTreeShard rootShard; private ShardedDOMDataTree dataTreeService; private ListenerRegistration rootShardReg; private final ExecutorService executor = Executors.newSingleThreadExecutor(); @Captor private ArgumentCaptor> captorForChanges; @Captor private ArgumentCaptor>> captorForSubtrees; private final ContainerNode crossShardContainer = createCrossShardContainer(); @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); rootShard = InMemoryDOMDataTreeShard.create(ROOT_ID, executor, 1); rootShard.onGlobalContextUpdated(schemaContext); final ShardedDOMDataTree dataTree = new ShardedDOMDataTree(); final DOMDataTreeProducer shardRegProducer = dataTree.createProducer(Collections.singletonList(ROOT_ID)); rootShardReg = dataTree.registerDataTreeShard(ROOT_ID, rootShard, shardRegProducer); shardRegProducer.close(); dataTreeService = dataTree; } @Test(expected = IllegalArgumentException.class) public void testProducerPathContention() throws Exception { dataTreeService.createProducer(Collections.singletonList(ROOT_ID)); dataTreeService.createProducer(Collections.singletonList(TEST_ID)); } @Test public void testShardRegistrationClose() throws Exception { rootShardReg.close(); final InMemoryDOMDataTreeShard newRootShard = InMemoryDOMDataTreeShard.create(ROOT_ID, executor, 1); newRootShard.onGlobalContextUpdated(schemaContext); final DOMDataTreeProducer shardRegProducer = dataTreeService.createProducer(Collections.singletonList(ROOT_ID)); final ListenerRegistration newRootShardReg = dataTreeService.registerDataTreeShard(ROOT_ID, rootShard, shardRegProducer); shardRegProducer.close(); final InMemoryDOMDataTreeShard innerShard = InMemoryDOMDataTreeShard.create(INNER_CONTAINER_ID, executor, 1); innerShard.onGlobalContextUpdated(schemaContext); final DOMDataTreeProducer shardRegProducer2 = dataTreeService.createProducer(Collections.singletonList(INNER_CONTAINER_ID)); ListenerRegistration innerShardReg = dataTreeService.registerDataTreeShard(INNER_CONTAINER_ID, innerShard, shardRegProducer2); innerShardReg.close(); // try to register the shard again innerShardReg = dataTreeService.registerDataTreeShard(INNER_CONTAINER_ID, innerShard, shardRegProducer2); final DOMDataTreeCursorAwareTransaction tx = shardRegProducer2.createTransaction(false); final DOMDataTreeWriteCursor cursor = tx.createCursor(INNER_CONTAINER_ID); assertNotNull(cursor); cursor.close(); tx.cancel(); shardRegProducer2.close(); innerShardReg.close(); newRootShardReg.close(); } @Test public void testSingleShardWrite() throws Exception { final DOMDataTreeListener mockedDataTreeListener = Mockito.mock(DOMDataTreeListener.class); doNothing().when(mockedDataTreeListener).onDataTreeChanged(anyCollection(), anyMap()); dataTreeService.registerListener(mockedDataTreeListener, Collections.singletonList(INNER_CONTAINER_ID), true, Collections.emptyList()); final DOMDataTreeProducer producer = dataTreeService.createProducer(Collections.singletonList(ROOT_ID)); DOMDataTreeCursorAwareTransaction tx = producer.createTransaction(false); DOMDataTreeWriteCursor cursor = tx.createCursor(ROOT_ID); assertNotNull(cursor); cursor.write(TEST_ID.getRootIdentifier().getLastPathArgument(), crossShardContainer); try { tx.submit().checkedGet(); fail("There's still an open cursor"); } catch (final IllegalStateException e) { assertTrue(e.getMessage().contains("cursor open")); } cursor.close(); tx.submit().checkedGet(); tx = producer.createTransaction(false); cursor = tx.createCursor(TEST_ID); assertNotNull(cursor); cursor.delete(TestModel.INNER_CONTAINER_PATH.getLastPathArgument()); cursor.close(); tx.submit().checkedGet(); verify(mockedDataTreeListener, timeout(1000).times(3)).onDataTreeChanged(captorForChanges.capture(), captorForSubtrees.capture()); final List> capturedValue = captorForChanges.getAllValues(); assertTrue(capturedValue.size() == 3); final ContainerNode capturedChange = (ContainerNode) capturedValue.get(1).iterator().next().getRootNode().getDataAfter().get(); final ContainerNode innerContainerVerify = (ContainerNode) crossShardContainer.getChild( TestModel.INNER_CONTAINER_PATH.getLastPathArgument()).get(); assertEquals(innerContainerVerify, capturedChange); verifyNoMoreInteractions(mockedDataTreeListener); } @Test public void testMultipleProducerCursorCreation() throws Exception { final DOMDataTreeProducer rootProducer = dataTreeService.createProducer(Collections.singletonList(ROOT_ID)); DOMDataTreeCursorAwareTransaction rootTx = rootProducer.createTransaction(false); //check if we can create cursor where the new producer will be DOMDataTreeWriteCursor rootTxCursor = rootTx.createCursor(INNER_CONTAINER_ID); assertNotNull(rootTxCursor); rootTxCursor.close(); try { rootProducer.createProducer(Collections.singletonList(INNER_CONTAINER_ID)); fail("Should've failed there is still a tx open"); } catch (final IllegalStateException e) { assertTrue(e.getMessage().contains("open")); } assertTrue(rootTx.cancel()); final DOMDataTreeProducer innerContainerProducer = rootProducer.createProducer( Collections.singletonList(INNER_CONTAINER_ID)); rootTx = rootProducer.createTransaction(false); try { rootTx.createCursor(INNER_CONTAINER_ID); fail("Subtree should not be available to this producer"); } catch (final IllegalArgumentException e) { assertTrue(e.getMessage().contains("delegated to child producer")); } rootTxCursor = rootTx.createCursor(TEST_ID); assertNotNull(rootTxCursor); try { rootTxCursor.enter(INNER_CONTAINER_ID.getRootIdentifier().getLastPathArgument()); fail("Cursor should not have this subtree available"); } catch (final IllegalArgumentException e) { assertTrue(e.getMessage().contains("not available to this cursor")); } try { rootTxCursor.write(TestModel.INNER_CONTAINER_PATH.getLastPathArgument(), ImmutableContainerNodeBuilder.create() .withNodeIdentifier(new NodeIdentifier(TestModel.INNER_CONTAINER)) .build()); fail("Cursor should not have this subtree available"); } catch (final IllegalArgumentException e) { assertTrue(e.getMessage().contains("not available to this cursor")); } final DOMDataTreeCursorAwareTransaction innerShardTx = innerContainerProducer.createTransaction(false); final DOMDataTreeWriteCursor innerShardCursor = innerShardTx.createCursor(INNER_CONTAINER_ID); assertNotNull(innerShardCursor); } private ContainerNode createCrossShardContainer() { final LeafNode shardedValue1 = ImmutableLeafNodeBuilder.create().withNodeIdentifier(new NodeIdentifier( TestModel.SHARDED_VALUE_1)).withValue("sharded value 1").build(); final LeafNode shardedValue2 = ImmutableLeafNodeBuilder.create().withNodeIdentifier(new NodeIdentifier( TestModel.SHARDED_VALUE_2)).withValue("sharded value 2").build(); final ContainerNode lowerShardContainer = ImmutableContainerNodeBuilder.create() .withNodeIdentifier(new NodeIdentifier(TestModel.ANOTHER_SHARD_CONTAINER)) .withChild(ImmutableLeafNodeBuilder.create() .withNodeIdentifier(new NodeIdentifier(TestModel.ANOTHER_SHARD_VALUE)) .withValue("testing-value") .build()) .build(); final ContainerNode containerNode = ImmutableContainerNodeBuilder.create() .withNodeIdentifier(new NodeIdentifier(TestModel.INNER_CONTAINER)) .withChild(shardedValue1) .withChild(shardedValue2) .withChild(lowerShardContainer) .build(); final ContainerNode testContainer = ImmutableContainerNodeBuilder.create() .withNodeIdentifier(new NodeIdentifier(TestModel.TEST_QNAME)) .withChild(containerNode) .build(); return testContainer; } }