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