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