Merge branch 'master' of ../controller
[yangtools.git] / yang / yang-parser-impl / src / test / java / org / opendaylight / yangtools / yang / parser / repo / SharedSchemaRepositoryTest.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.parser.repo;
9
10 import static org.hamcrest.CoreMatchers.both;
11 import static org.hamcrest.CoreMatchers.hasItem;
12 import static org.hamcrest.MatcherAssert.assertThat;
13 import static org.junit.Assert.assertEquals;
14 import static org.junit.Assert.assertFalse;
15 import static org.junit.Assert.assertNotNull;
16 import static org.junit.Assert.assertSame;
17 import static org.junit.Assert.assertTrue;
18 import static org.junit.Assert.fail;
19 import static org.mockito.Mockito.spy;
20 import static org.mockito.Mockito.times;
21 import static org.mockito.Mockito.verify;
22 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
23
24 import com.google.common.base.MoreObjects.ToStringHelper;
25 import com.google.common.collect.Lists;
26 import com.google.common.io.Files;
27 import com.google.common.util.concurrent.FutureCallback;
28 import com.google.common.util.concurrent.Futures;
29 import com.google.common.util.concurrent.ListenableFuture;
30 import com.google.common.util.concurrent.MoreExecutors;
31 import java.io.ByteArrayInputStream;
32 import java.io.File;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.nio.charset.StandardCharsets;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.List;
39 import java.util.Optional;
40 import java.util.concurrent.ExecutionException;
41 import org.junit.Test;
42 import org.opendaylight.yangtools.yang.common.Revision;
43 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
44 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
45 import org.opendaylight.yangtools.yang.model.repo.api.EffectiveModelContextFactory;
46 import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException;
47 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
48 import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation;
49 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
50 import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
51 import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource;
52 import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceListener;
53 import org.opendaylight.yangtools.yang.model.repo.util.FilesystemSchemaSourceCache;
54 import org.opendaylight.yangtools.yang.parser.rfc7950.repo.ASTSchemaSource;
55 import org.opendaylight.yangtools.yang.parser.rfc7950.repo.TextToASTTransformer;
56
57 public class SharedSchemaRepositoryTest {
58
59     @Test
60     public void testSourceWithAndWithoutRevision() throws Exception {
61         final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository("netconf-mounts");
62
63         final SourceIdentifier idNoRevision = loadAndRegisterSource(sharedSchemaRepository,
64             "/no-revision/imported.yang");
65         final SourceIdentifier id2 = loadAndRegisterSource(sharedSchemaRepository,
66             "/no-revision/imported@2012-12-12.yang");
67
68         ListenableFuture<ASTSchemaSource> source = sharedSchemaRepository.getSchemaSource(idNoRevision,
69             ASTSchemaSource.class);
70         assertEquals(idNoRevision, source.get().getIdentifier());
71         source = sharedSchemaRepository.getSchemaSource(id2, ASTSchemaSource.class);
72         assertEquals(id2, source.get().getIdentifier());
73     }
74
75     private static SourceIdentifier loadAndRegisterSource(final SharedSchemaRepository sharedSchemaRepository,
76             final String resourceName) throws Exception {
77         final SettableSchemaProvider<ASTSchemaSource> sourceProvider = getImmediateYangSourceProviderFromResource(
78             resourceName);
79         sourceProvider.setResult();
80         final SourceIdentifier idNoRevision = sourceProvider.getId();
81         sourceProvider.register(sharedSchemaRepository);
82         return idNoRevision;
83     }
84
85     @Test
86     public void testSimpleSchemaContext() throws Exception {
87         final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository("netconf-mounts");
88
89         final SettableSchemaProvider<ASTSchemaSource> remoteInetTypesYang = getImmediateYangSourceProviderFromResource(
90             "/ietf/ietf-inet-types@2010-09-24.yang");
91         remoteInetTypesYang.register(sharedSchemaRepository);
92         final ListenableFuture<ASTSchemaSource> registeredSourceFuture = sharedSchemaRepository.getSchemaSource(
93             remoteInetTypesYang.getId(), ASTSchemaSource.class);
94         assertFalse(registeredSourceFuture.isDone());
95
96         final EffectiveModelContextFactory fact = sharedSchemaRepository.createEffectiveModelContextFactory();
97         final ListenableFuture<EffectiveModelContext> schemaContextFuture =
98                 fact.createEffectiveModelContext(remoteInetTypesYang.getId());
99
100         assertFalse(schemaContextFuture.isDone());
101
102         // Make source appear
103         remoteInetTypesYang.setResult();
104         assertEquals(remoteInetTypesYang.getSchemaSourceRepresentation(), registeredSourceFuture.get());
105
106         // Verify schema created successfully
107         assertTrue(schemaContextFuture.isDone());
108         final SchemaContext firstSchemaContext = schemaContextFuture.get();
109         assertSchemaContext(firstSchemaContext, 1);
110
111         // Try same schema second time
112         final ListenableFuture<EffectiveModelContext> secondSchemaFuture =
113                 sharedSchemaRepository.createEffectiveModelContextFactory().createEffectiveModelContext(
114                     remoteInetTypesYang.getId());
115
116         // Verify second schema created successfully immediately
117         assertTrue(secondSchemaFuture.isDone());
118         // Assert same context instance is returned from first and second attempt
119         assertSame(firstSchemaContext, secondSchemaFuture.get());
120     }
121
122     @Test
123     public void testTwoSchemaContextsSharingSource() throws Exception {
124         final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository("netconf-mounts");
125
126         final SettableSchemaProvider<ASTSchemaSource> remoteInetTypesYang = getImmediateYangSourceProviderFromResource(
127             "/ietf/ietf-inet-types@2010-09-24.yang");
128         remoteInetTypesYang.register(sharedSchemaRepository);
129         remoteInetTypesYang.setResult();
130         final SettableSchemaProvider<ASTSchemaSource> remoteTopologyYang = getImmediateYangSourceProviderFromResource(
131             "/ietf/network-topology@2013-10-21.yang");
132         remoteTopologyYang.register(sharedSchemaRepository);
133         remoteTopologyYang.setResult();
134         final SettableSchemaProvider<ASTSchemaSource> remoteModuleNoRevYang =
135                 getImmediateYangSourceProviderFromResource("/no-revision/module-without-revision.yang");
136         remoteModuleNoRevYang.register(sharedSchemaRepository);
137
138         final EffectiveModelContextFactory fact = sharedSchemaRepository.createEffectiveModelContextFactory();
139         final ListenableFuture<EffectiveModelContext> inetAndTopologySchemaContextFuture = fact
140                 .createEffectiveModelContext(remoteInetTypesYang.getId(), remoteTopologyYang.getId());
141         assertTrue(inetAndTopologySchemaContextFuture.isDone());
142         assertSchemaContext(inetAndTopologySchemaContextFuture.get(), 2);
143
144         final ListenableFuture<EffectiveModelContext> inetAndNoRevSchemaContextFuture =
145                 fact.createEffectiveModelContext(remoteInetTypesYang.getId(), remoteModuleNoRevYang.getId());
146         assertFalse(inetAndNoRevSchemaContextFuture.isDone());
147
148         remoteModuleNoRevYang.setResult();
149         assertTrue(inetAndNoRevSchemaContextFuture.isDone());
150         assertSchemaContext(inetAndNoRevSchemaContextFuture.get(), 2);
151     }
152
153     @Test
154     public void testFailedSchemaContext() throws Exception {
155         final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository("netconf-mounts");
156
157         final SettableSchemaProvider<ASTSchemaSource> remoteInetTypesYang = getImmediateYangSourceProviderFromResource(
158             "/ietf/ietf-inet-types@2010-09-24.yang");
159         remoteInetTypesYang.register(sharedSchemaRepository);
160
161         final EffectiveModelContextFactory fact = sharedSchemaRepository.createEffectiveModelContextFactory();
162
163         // Make source appear
164         final Throwable ex = new IllegalStateException("failed schema");
165         remoteInetTypesYang.setException(ex);
166
167         final ListenableFuture<EffectiveModelContext> schemaContextFuture = fact.createEffectiveModelContext(
168             remoteInetTypesYang.getId());
169
170         try {
171             schemaContextFuture.get();
172         } catch (final ExecutionException e) {
173             assertNotNull(e.getCause());
174             assertNotNull(e.getCause().getCause());
175             assertSame(ex, e.getCause().getCause());
176             return;
177         }
178
179         fail("Schema context creation should have failed");
180     }
181
182     @Test
183     public void testDifferentCosts() throws Exception {
184         final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository("netconf-mounts");
185
186         final SettableSchemaProvider<ASTSchemaSource> immediateInetTypesYang = spy(
187             getImmediateYangSourceProviderFromResource("/ietf/ietf-inet-types@2010-09-24.yang"));
188         immediateInetTypesYang.register(sharedSchemaRepository);
189         immediateInetTypesYang.setResult();
190
191         final SettableSchemaProvider<ASTSchemaSource> remoteInetTypesYang = spy(
192             getRemoteYangSourceProviderFromResource("/ietf/ietf-inet-types@2010-09-24.yang"));
193         remoteInetTypesYang.register(sharedSchemaRepository);
194         remoteInetTypesYang.setResult();
195
196         final EffectiveModelContextFactory fact = sharedSchemaRepository.createEffectiveModelContextFactory();
197         final ListenableFuture<EffectiveModelContext> schemaContextFuture =
198                 fact.createEffectiveModelContext(remoteInetTypesYang.getId());
199
200         assertSchemaContext(schemaContextFuture.get(), 1);
201
202         final SourceIdentifier id = immediateInetTypesYang.getId();
203         verify(remoteInetTypesYang, times(0)).getSource(id);
204         verify(immediateInetTypesYang).getSource(id);
205     }
206
207     @Test
208     public void testWithCacheStartup() throws Exception {
209         final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository("netconf-mounts");
210
211         class CountingSchemaListener implements SchemaSourceListener {
212             List<PotentialSchemaSource<?>> registeredSources = new ArrayList<>();
213
214             @Override
215             public void schemaSourceEncountered(final SchemaSourceRepresentation source) {
216             }
217
218             @Override
219             public void schemaSourceRegistered(final Iterable<PotentialSchemaSource<?>> sources) {
220                 for (final PotentialSchemaSource<?> source : sources) {
221                     registeredSources.add(source);
222                 }
223             }
224
225             @Override
226             public void schemaSourceUnregistered(final PotentialSchemaSource<?> source) {
227             }
228         }
229
230         final File storageDir = Files.createTempDir();
231
232         final CountingSchemaListener listener = new CountingSchemaListener();
233         sharedSchemaRepository.registerSchemaSourceListener(listener);
234
235         final File test = new File(storageDir, "test.yang");
236         Files.asCharSink(test, StandardCharsets.UTF_8).write("content-test");
237
238         final File test2 = new File(storageDir, "test@2012-12-12.yang");
239         Files.asCharSink(test2, StandardCharsets.UTF_8).write("content-test-2012");
240
241         final File test3 = new File(storageDir, "test@2013-12-12.yang");
242         Files.asCharSink(test3, StandardCharsets.UTF_8).write("content-test-2013");
243
244         final File test4 = new File(storageDir, "module@2010-12-12.yang");
245         Files.asCharSink(test4, StandardCharsets.UTF_8).write("content-module-2010");
246
247         final FilesystemSchemaSourceCache<YangTextSchemaSource> cache = new FilesystemSchemaSourceCache<>(
248                 sharedSchemaRepository, YangTextSchemaSource.class, storageDir);
249         sharedSchemaRepository.registerSchemaSourceListener(cache);
250
251         assertEquals(4, listener.registeredSources.size());
252
253         assertThat(Lists.transform(listener.registeredSources, PotentialSchemaSource::getSourceIdentifier),
254                 both(hasItem(RevisionSourceIdentifier.create("test", Optional.empty())))
255                         .and(hasItem(RevisionSourceIdentifier.create("test", Revision.of("2012-12-12"))))
256                         .and(hasItem(RevisionSourceIdentifier.create("test", Revision.of("2013-12-12"))))
257                         .and(hasItem(RevisionSourceIdentifier.create("module", Revision.of("2010-12-12"))))
258         );
259     }
260
261     @Test
262     public void testWithCacheRunning() throws Exception {
263         final SharedSchemaRepository sharedSchemaRepository = new SharedSchemaRepository("netconf-mounts");
264
265         final File storageDir = Files.createTempDir();
266
267         final FilesystemSchemaSourceCache<YangTextSchemaSource> cache = new FilesystemSchemaSourceCache<>(
268                 sharedSchemaRepository, YangTextSchemaSource.class, storageDir);
269         sharedSchemaRepository.registerSchemaSourceListener(cache);
270
271         final SourceIdentifier runningId = RevisionSourceIdentifier.create("running", Revision.of("2012-12-12"));
272
273         sharedSchemaRepository.registerSchemaSource(sourceIdentifier -> immediateFluentFuture(
274             new YangTextSchemaSource(runningId) {
275                 @Override
276                 protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
277                     return toStringHelper;
278                 }
279
280                 @Override
281                 public InputStream openStream() throws IOException {
282                     return new ByteArrayInputStream("running".getBytes(StandardCharsets.UTF_8));
283                 }
284             }), PotentialSchemaSource.create(runningId, YangTextSchemaSource.class,
285                 PotentialSchemaSource.Costs.REMOTE_IO.getValue()));
286
287         final TextToASTTransformer transformer = TextToASTTransformer.create(sharedSchemaRepository,
288             sharedSchemaRepository);
289         sharedSchemaRepository.registerSchemaSourceListener(transformer);
290
291         // Request schema to make repository notify the cache
292         final ListenableFuture<EffectiveModelContext> schemaFuture = sharedSchemaRepository
293                 .createEffectiveModelContextFactory()
294                 .createEffectiveModelContext(runningId);
295         Futures.addCallback(schemaFuture, new FutureCallback<SchemaContext>() {
296             @Override
297             public void onSuccess(final SchemaContext result) {
298                 fail("Creation of schema context should fail from non-regular sources");
299             }
300
301             @Override
302             public void onFailure(final Throwable cause) {
303                 // Creation of schema context fails, since we do not provide regular sources, but we just want
304                 // to check cache
305                 final List<File> cachedSchemas = Arrays.asList(storageDir.listFiles());
306                 assertEquals(1, cachedSchemas.size());
307                 assertEquals(Files.getNameWithoutExtension(cachedSchemas.get(0).getName()), "running@2012-12-12");
308             }
309         }, MoreExecutors.directExecutor());
310
311         try {
312             schemaFuture.get();
313         } catch (final ExecutionException e) {
314             assertNotNull(e.getCause());
315             assertEquals(MissingSchemaSourceException.class, e.getCause().getClass());
316             return;
317         }
318
319         fail("Creation of schema context should fail from non-regular sources");
320     }
321
322     private static void assertSchemaContext(final SchemaContext schemaContext, final int moduleSize) {
323         assertNotNull(schemaContext);
324         assertEquals(moduleSize, schemaContext.getModules().size());
325     }
326
327     static SettableSchemaProvider<ASTSchemaSource> getRemoteYangSourceProviderFromResource(final String resourceName)
328             throws Exception {
329         final YangTextSchemaSource yangSource = YangTextSchemaSource.forResource(resourceName);
330         return SettableSchemaProvider.createRemote(TextToASTTransformer.transformText(yangSource),
331             ASTSchemaSource.class);
332     }
333
334     static SettableSchemaProvider<ASTSchemaSource> getImmediateYangSourceProviderFromResource(final String resourceName)
335             throws Exception {
336         final YangTextSchemaSource yangSource = YangTextSchemaSource.forResource(resourceName);
337         return SettableSchemaProvider.createImmediate(TextToASTTransformer.transformText(yangSource),
338             ASTSchemaSource.class);
339     }
340 }