be65e1c574e41b5ace867cdb1f1fc3bdfd511c82
[odlparent.git] / features-test / src / main / java / org / opendaylight / odlparent / featuretest / SingleFeatureTest.java
1 /*
2  * Copyright © 2014, 2017 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.odlparent.featuretest;
10
11 import static java.util.concurrent.TimeUnit.SECONDS;
12 import static org.opendaylight.odlparent.featuretest.Constants.ORG_OPENDAYLIGHT_FEATURETEST_FEATURENAME_PROP;
13 import static org.opendaylight.odlparent.featuretest.Constants.ORG_OPENDAYLIGHT_FEATURETEST_FEATUREVERSION_PROP;
14 import static org.opendaylight.odlparent.featuretest.Constants.ORG_OPENDAYLIGHT_FEATURETEST_URI_PROP;
15 import static org.ops4j.pax.exam.CoreOptions.maven;
16 import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
17 import static org.ops4j.pax.exam.CoreOptions.propagateSystemProperty;
18 import static org.ops4j.pax.exam.CoreOptions.systemPackages;
19 import static org.ops4j.pax.exam.CoreOptions.when;
20 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureConsole;
21 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut;
22 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;
23 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;
24 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;
25
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.net.URI;
30 import java.net.URISyntaxException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.EnumSet;
34 import java.util.List;
35 import java.util.Properties;
36 import javax.inject.Inject;
37 import org.apache.karaf.bundle.core.BundleService;
38 import org.apache.karaf.features.Feature;
39 import org.apache.karaf.features.FeaturesService;
40 import org.apache.karaf.features.Repository;
41 import org.awaitility.Awaitility;
42 import org.eclipse.jdt.annotation.NonNull;
43 import org.junit.Assert;
44 import org.junit.Before;
45 import org.junit.Test;
46 import org.junit.runner.RunWith;
47 import org.opendaylight.odlparent.bundlestest.lib.TestBundleDiag;
48 import org.ops4j.pax.exam.Configuration;
49 import org.ops4j.pax.exam.Option;
50 import org.ops4j.pax.exam.ProbeBuilder;
51 import org.ops4j.pax.exam.TestProbeBuilder;
52 import org.ops4j.pax.exam.karaf.options.LogLevelOption.LogLevel;
53 import org.ops4j.pax.exam.options.extra.VMOption;
54 import org.osgi.framework.BundleContext;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 @RunWith(PerRepoTestRunner.class)
59 public class SingleFeatureTest {
60
61     private static final String MAVEN_REPO_LOCAL = "maven.repo.local";
62     private static final String ORG_OPS4J_PAX_URL_MVN_LOCAL_REPOSITORY = "org.ops4j.pax.url.mvn.localRepository";
63     private static final String ORG_OPS4J_PAX_URL_MVN_REPOSITORIES = "org.ops4j.pax.url.mvn.repositories";
64     private static final String ETC_ORG_OPS4J_PAX_URL_MVN_CFG = "etc/org.ops4j.pax.url.mvn.cfg";
65     private static final String ETC_ORG_OPS4J_PAX_LOGGING_CFG = "etc/org.ops4j.pax.logging.cfg";
66
67     private static final String KEEP_UNPACK_DIRECTORY_PROP = "karaf.keep.unpack";
68     private static final String PROFILE_PROP = "karaf.featureTest.profile";
69     private static final String BUNDLES_DIAG_SKIP_PROP = "sft.diag.skip";
70     private static final String BUNDLES_DIAG_FORCE_PROP = "sft.diag.force";
71     private static final String BUNDLES_DIAG_TIMEOUT_PROP = "sft.diag.timeout";
72
73     // Maximum heap size
74     private static final String HEAP_MAX_PROP = "sft.heap.max";
75     private static final String DEFAULT_HEAP_MAX = "2g";
76
77     private static final String LOG4J_LOGGER_ORG_OPENDAYLIGHT_YANGTOOLS_FEATURETEST =
78             "log4j.logger.org.opendaylight.odlparent.featuretest";
79     private static final Logger LOG = LoggerFactory.getLogger(SingleFeatureTest.class);
80
81     /*
82      * File name to add our logging config property too.
83      */
84     private static final String ORG_OPS4J_PAX_LOGGING_CFG = "etc/org.ops4j.pax.logging.cfg";
85
86     /*
87      * Default values for karaf distro type, groupId, and artifactId
88      */
89     private static final String KARAF_DISTRO_TYPE = "zip";
90     private static final String KARAF_DISTRO_ARTIFACTID = "opendaylight-karaf-empty";
91     private static final String KARAF_DISTRO_GROUPID = "org.opendaylight.odlparent";
92
93     /*
94      * Property names to override defaults for karaf distro artifactId, groupId, version, and type
95      */
96     private static final String KARAF_DISTRO_VERSION_PROP = "karaf.distro.version";
97     private static final String KARAF_DISTRO_TYPE_PROP = "karaf.distro.type";
98     private static final String KARAF_DISTRO_ARTIFACTID_PROP = "karaf.distro.artifactId";
99     private static final String KARAF_DISTRO_GROUPID_PROP = "karaf.distro.groupId";
100
101     /**
102      * Property file used to store the Karaf distribution version.
103      */
104     private static final String PROPERTIES_FILENAME = "singlefeaturetest.properties";
105
106     /**
107      * <p>List of Karaf 3.0.4 default maven repositories with snapshot repositories excluded.</p>
108      * <p>Unfortunately this must be hard-coded since declarative model which uses Options,
109      * does not allow us to read value, parse it (properties has allways
110      * problems with lists) and construct replacement string which does
111      * not contains snapshots.</p>
112      * <p>When updating Karaf, check this against org.ops4j.pax.url.mvn.cfg in the Karaf distribution.</p>
113      */
114     private static final String EXTERNAL_DEFAULT_REPOSITORIES = "http://repo1.maven.org/maven2@id=central, "
115             + "http://repository.springsource.com/maven/bundles/release@id=spring.ebr.release, "
116             + "http://repository.springsource.com/maven/bundles/external@id=spring.ebr.external, "
117             + "http://zodiac.springsource.com/maven/bundles/release@id=gemini ";
118
119     @Inject @NonNull
120     private BundleContext bundleContext;
121
122     @Inject @NonNull
123     private BundleService bundleService; // NOT BundleStateService, see checkBundleStatesDiag()
124
125     @Inject @NonNull
126     private FeaturesService featuresService;
127
128     private String karafVersion;
129     private String karafDistroVersion;
130
131     @ProbeBuilder
132     public TestProbeBuilder probeConfiguration(final TestProbeBuilder probe) {
133         // add this to test Karaf Commands, according to green Karaf book
134         // also see http://iocanel.blogspot.ch/2012/01/advanced-integration-testing-with-pax.html
135         // probe.setHeader(org.osgi.framework.Constants.DYNAMICIMPORT_PACKAGE, "*;status=provisional");
136
137         // adding these so that loading of TestBundleDiag and its dependencies works
138         // NB that here in the features4-test this works completely differently than
139         // in the original features-test for Karaf 3; there we installed the bundle in
140         // config() whereas here we embed dependencies into a single JAR to simplify
141         // problems we've had in distribution jobs with custom local Maven repos;
142         // but because of this we have to "help" Pax Exam with what classes need
143         // to be bundled with its probe:
144         ReflectionUtil.addAllClassesInSameAndSubPackageOfClass(probe, TestBundleDiag.class);
145         ReflectionUtil.addAllClassesInSameAndSubPackageOfClass(probe, Awaitility.class);
146         ReflectionUtil.addAllClassesInSameAndSubPackageOfPackage(probe, "com.google.common");
147
148         return probe;
149     }
150
151     /**
152      * Returns the required configuration.
153      *
154      * @return The Pax Exam configuration.
155      * @throws IOException if an error occurs.
156      */
157     @Configuration
158     public Option[] config() throws IOException {
159         final String envMaxHeap = System.getenv(HEAP_MAX_PROP);
160         final String maxHeap = envMaxHeap != null ? envMaxHeap : DEFAULT_HEAP_MAX;
161
162         return new Option[] {
163             new VMOption("-Xmx" + maxHeap),
164             new VMOption("-XX:+HeapDumpOnOutOfMemoryError"),
165             new VMOption("-XX:OnOutOfMemoryError=\"kill -3 %p\""),
166             // inspired by org.apache.commons.lang.SystemUtils
167             when(System.getProperty("os.name").toLowerCase().startsWith("linux")).useOptions(
168                 // This prevents low entropy issues on Linux to affect Java random numbers
169                 // which can block crypto such as the SSH server in netconf
170                 // see https://bugs.opendaylight.org/show_bug.cgi?id=6790
171                 new VMOption("-Djava.security.egd=file:/dev/./urandom")
172             ),
173             when(Boolean.getBoolean(PROFILE_PROP)).useOptions(
174                 new VMOption("-XX:+UnlockCommercialFeatures"),
175                 new VMOption("-XX:+FlightRecorder"),
176                 new VMOption("-XX:FlightRecorderOptions=defaultrecording=true,dumponexit=true,dumponexitpath="
177                                + getNewJFRFile())
178             ),
179             getKarafDistroOption(),
180             when(Boolean.getBoolean(KEEP_UNPACK_DIRECTORY_PROP)).useOptions(keepRuntimeFolder()),
181             configureConsole().ignoreLocalConsole(),
182             logLevel(LogLevel.INFO),
183             mvnLocalRepoOption(),
184             mavenBundle("org.apache.aries.quiesce", "org.apache.aries.quiesce.api", "1.0.0"),
185             editConfigurationFilePut(ORG_OPS4J_PAX_LOGGING_CFG, LOG4J_LOGGER_ORG_OPENDAYLIGHT_YANGTOOLS_FEATURETEST,
186                     LogLevel.INFO.name()),
187             editConfigurationFilePut("etc/config.properties", "karaf.framework", "equinox"),
188             editConfigurationFilePut(ETC_ORG_OPS4J_PAX_LOGGING_CFG, "log4j.rootLogger", "INFO, stdout, osgi:*"),
189
190              /*
191               * Disables external snapshot repositories.
192               *
193               * Pax URL and Karaf by default always search for new version of snapshots
194               * in all snapshots repository, even if that snapshots does not belong to that
195               * repository and maven is invoked even with -nsu (no snapshot update)
196               * or offline mode.
197               *
198               * This is also true for OpenDaylight snapshot artefacts - pax url tries
199               * to resolve them from third-party repositories, even if they are not present
200               * there - this increases time which takes for features to install.
201               *
202               * For more complex projects this actually means several HTTP GETs for each
203               * snapshot bundle referenced, even if the bundle is already present
204               * in local maven repository.
205               *
206               * In order to speed-up installation and remove unnecessary network traffic,
207               * which fails for obvious reasons, external snapshot repositories are
208               * removed.
209               */
210             disableExternalSnapshotRepositories(),
211             propagateSystemProperty(ORG_OPENDAYLIGHT_FEATURETEST_URI_PROP),
212             propagateSystemProperty(ORG_OPENDAYLIGHT_FEATURETEST_FEATURENAME_PROP),
213             propagateSystemProperty(ORG_OPENDAYLIGHT_FEATURETEST_FEATUREVERSION_PROP),
214             propagateSystemProperty(BUNDLES_DIAG_SKIP_PROP),
215             propagateSystemProperty(BUNDLES_DIAG_FORCE_PROP),
216             propagateSystemProperty(BUNDLES_DIAG_TIMEOUT_PROP),
217             // Needed for Agrona/aeron.io
218             systemPackages("com.sun.media.sound", "sun.net", "sun.nio.ch"),
219         };
220     }
221
222     private static String getNewJFRFile() throws IOException {
223         return File.createTempFile("SingleFeatureTest-Karaf-JavaFlightRecorder", ".jfr").getAbsolutePath();
224     }
225
226     private String getKarafVersion() throws IOException {
227         if (karafVersion == null) {
228             // We use a properties file to retrieve Karaf's version, instead of .versionAsInProject()
229             // This avoids forcing all users to depend on Karaf in their POMs
230             Properties singleFeatureTestProps = new Properties();
231             try (InputStream singleFeatureTestInputStream = Thread.currentThread().getContextClassLoader()
232                     .getResourceAsStream(PROPERTIES_FILENAME)) {
233                 if (singleFeatureTestInputStream == null) {
234                     throw new IOException("Resource not found; expected to be present on current thread classloader: "
235                             + PROPERTIES_FILENAME);
236                 }
237                 singleFeatureTestProps.load(singleFeatureTestInputStream);
238             }
239             karafVersion = singleFeatureTestProps.getProperty(KARAF_DISTRO_VERSION_PROP);
240
241             LOG.info("Retrieved karafVersion {} from properties file {}", karafVersion, PROPERTIES_FILENAME);
242         } else {
243             LOG.info("Retrieved karafVersion {} from system property {}", karafVersion, KARAF_DISTRO_VERSION_PROP);
244         }
245
246         return karafVersion;
247     }
248
249     private String getKarafDistroVersion() throws IOException {
250         if (karafDistroVersion == null) {
251             karafDistroVersion = System.getProperty(KARAF_DISTRO_VERSION_PROP);
252             if (karafDistroVersion == null) {
253                 karafDistroVersion = getKarafVersion();
254             } else {
255                 LOG.info("Retrieved karafDistroVersion {} from system property {}", karafVersion,
256                         KARAF_DISTRO_VERSION_PROP);
257             }
258         }
259
260         return karafDistroVersion;
261     }
262
263     /**
264      * Disables snapshot repositories, which are enabled by default in karaf distribution.
265      *
266      * @return Edit Configuration option which removes external snapshot repositories.
267      */
268     private static Option disableExternalSnapshotRepositories() {
269         return editConfigurationFilePut(ETC_ORG_OPS4J_PAX_URL_MVN_CFG, ORG_OPS4J_PAX_URL_MVN_REPOSITORIES,
270                 EXTERNAL_DEFAULT_REPOSITORIES);
271     }
272
273     protected Option mvnLocalRepoOption() {
274         String mvnRepoLocal = System.getProperty(MAVEN_REPO_LOCAL, "");
275         LOG.info("mvnLocalRepo \"{}\"", mvnRepoLocal);
276         return editConfigurationFilePut(ETC_ORG_OPS4J_PAX_URL_MVN_CFG, ORG_OPS4J_PAX_URL_MVN_LOCAL_REPOSITORY,
277                 mvnRepoLocal);
278     }
279
280     protected Option getKarafDistroOption() throws IOException {
281         String groupId = System.getProperty(KARAF_DISTRO_GROUPID_PROP, KARAF_DISTRO_GROUPID);
282         String artifactId = System.getProperty(KARAF_DISTRO_ARTIFACTID_PROP, KARAF_DISTRO_ARTIFACTID);
283         String type = System.getProperty(KARAF_DISTRO_TYPE_PROP, KARAF_DISTRO_TYPE);
284         LOG.info("Using karaf distro {} {} {} {}", groupId, artifactId, getKarafDistroVersion(), type);
285         return karafDistributionConfiguration()
286                 .frameworkUrl(
287                         maven()
288                                 .groupId(groupId)
289                                 .artifactId(artifactId)
290                                 .type(type)
291                                 .version(getKarafDistroVersion()))
292                 .name("OpenDaylight")
293                 .unpackDirectory(new File("target/pax"))
294                 .useDeployFolder(false);
295     }
296
297     private static URI getRepoUri() throws URISyntaxException {
298         return new URI(getProperty(ORG_OPENDAYLIGHT_FEATURETEST_URI_PROP));
299     }
300
301     private static String getFeatureName() {
302         return getProperty(ORG_OPENDAYLIGHT_FEATURETEST_FEATURENAME_PROP);
303     }
304
305     public String getFeatureVersion() {
306         return getProperty(ORG_OPENDAYLIGHT_FEATURETEST_FEATUREVERSION_PROP);
307     }
308
309     private static String getProperty(final String propName) {
310         String prop = System.getProperty(propName);
311         Assert.assertTrue("Missing property :" + propName, prop != null);
312         return prop;
313     }
314
315     private void checkRepository(final URI repoUri) throws Exception {
316         Repository repo = null;
317         for (Repository r : featuresService.listRepositories()) {
318             if (r.getURI().equals(repoUri)) {
319                 repo = r;
320                 break;
321             }
322         }
323         Assert.assertNotNull("Repository not found: " + repoUri, repo);
324     }
325
326     /**
327      * Sets the repository up.
328      *
329      * @throws Exception if an error occurs.
330      */
331     @Before
332     public void installRepo() throws Exception {
333         final URI repoUri = getRepoUri();
334         LOG.info("Attempting to add repository {}", repoUri);
335         featuresService.addRepository(repoUri);
336         checkRepository(repoUri);
337         LOG.info("Successfully loaded repository {}", repoUri);
338     }
339
340     // Give it 10 minutes max as we've seen feature install hang on jenkins.
341     @Test(timeout = 600000)
342     @SuppressWarnings("checkstyle:IllegalCatch")
343     public void installFeatureCatchAndLog() throws Exception {
344         // TODO remove this when the underlying problem is solved
345         // https://bugs.opendaylight.org/show_bug.cgi?id=7981:
346         // "SFT never fails, Pax Exam (or our wrappers) swallow all exceptions"
347         try {
348             installFeature();
349         } catch (Throwable t) {
350             LOG.error("installFeature() failed", t);
351             // as of 2017.03.20, this re-throw seems to have no effect,
352             // the exception gets lost in space, swallowed somewhere! :(
353             throw t;
354         }
355     }
356
357     public void installFeature() throws Exception {
358         // The BundleContext originally @Inject'd into the field
359         // is, as expected, the PAXEXAM-PROBE.  For some strange reason,
360         // under Karaf 4 (this works under Karaf 3 without this trick),
361         // after the installFeature() & getFeature() & isInstalled()
362         // below are through, that BundleContext has become invalid
363         // already (too soon!), and using it leads to "IllegalStateException:
364         // BundleContext is no longer valid". -- Because we don't actually
365         // need the PAXEXAM-PROBE, just ANY BundleContext, we employ a
366         // little trick, and obtain the OSGi Framework's (Felix or Equinox's)
367         // own BundleContext, which will never become invalid, and use that instead.
368         // This works, but is a work around, and the fact that we have to do this
369         // may be an indication of a larger problem... see also related strange open bugs
370         // which make it seem like at least some other bundles also get uninstalled
371         // way too soon, for some reason:
372         //  * https://bugs.opendaylight.org/show_bug.cgi?id=7924
373         //  * https://bugs.opendaylight.org/show_bug.cgi?id=7923 (?)
374         //  * https://bugs.opendaylight.org/show_bug.cgi?id=7926
375         bundleContext = bundleContext.getBundle(0).getBundleContext();
376
377         LOG.info("Attempting to install feature {} {}", getFeatureName(), getFeatureVersion());
378         featuresService.installFeature(getFeatureName(), getFeatureVersion(),
379                 EnumSet.of(FeaturesService.Option.Verbose));
380         LOG.info("installFeature() completed");
381         Feature feature = featuresService.getFeature(getFeatureName(), getFeatureVersion());
382         LOG.info("getFeature() completed");
383         Assert.assertNotNull(
384                 "Attempt to get feature " + getFeatureName() + " " + getFeatureVersion() + "resulted in null",
385                 feature);
386         boolean isInstalled = featuresService.isInstalled(feature);
387         LOG.info("isInstalled() completed");
388         Assert.assertTrue(
389                 "Failed to install Feature: " + getFeatureName() + " " + getFeatureVersion(), isInstalled);
390         LOG.info("Successfully installed feature {} {}", getFeatureName(), getFeatureVersion());
391
392         if (!Boolean.getBoolean(BUNDLES_DIAG_SKIP_PROP)
393                 && (Boolean.getBoolean(BUNDLES_DIAG_FORCE_PROP)
394                     || !BLACKLISTED_BROKEN_FEATURES.contains(getFeatureName()))) {
395             LOG.info("new TestBundleDiag().checkBundleDiagInfos() STARTING");
396             Integer timeOutInSeconds = Integer.getInteger(BUNDLES_DIAG_TIMEOUT_PROP, 5 * 60);
397             new TestBundleDiag(bundleContext, bundleService).checkBundleDiagInfos(timeOutInSeconds, SECONDS);
398             LOG.info("new TestBundleDiag().checkBundleDiagInfos() ENDED");
399         } else {
400             LOG.warn("SKIPPING TestBundleDiag because system property {} is true or feature is blacklisted: {}",
401                     BUNDLES_DIAG_SKIP_PROP, getFeatureName());
402         }
403     }
404
405     // TODO remove this when all issues linked to parent https://bugs.opendaylight.org/show_bug.cgi?id=7582 are resolved
406     private static final List<String> BLACKLISTED_BROKEN_FEATURES = new ArrayList<>(Arrays.asList(
407             // integration/distribution/features-test due to DOMRpcService
408             // see https://bugs.opendaylight.org/show_bug.cgi?id=7595
409             "odl-integration-all",
410             // controller/features/mdsal/ due to IllegalStateException: ./configuration/initial/akka.conf is missing
411             // see https://bugs.opendaylight.org/show_bug.cgi?id=7583
412             "odl-mdsal-broker-local",
413             "odl-mdsal-clustering-commons",
414             "odl-mdsal-distributed-datastore",
415             "odl-mdsal-remoterpc-connector",
416             // 1/17 in groupbasedpolicy/features due to NOK org.opendaylight.groupbasedpolicy
417             // Caused by: org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException
418             // see https://bugs.opendaylight.org/show_bug.cgi?id=7587
419             "odl-groupbasedpolicy-ne-location-provider",
420             // 1/11 in tsdr/features due to (strange) ClassNotFoundException: odlparent.bundlestest
421             //   .TestBundleDiag (works for all other features; class loading issue in that feature?)
422             // see https://bugs.opendaylight.org/show_bug.cgi?id=7588
423             "odl-hbaseclient",
424             // 1/9 in unimgr/features due missing mdsal, similar to issue to odl-integration-all?
425             // TODO retry after https://bugs.opendaylight.org/show_bug.cgi?id=7595 is fixed
426             "odl-unimgr-netvirt"
427     ));
428 }