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