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