SingleFeatureTest now fails if diag CLI command would have failed 01/48901/33
authorMichael Vorburger <vorburger@redhat.com>
Thu, 1 Dec 2016 16:35:37 +0000 (17:35 +0100)
committerStephen Kitt <skitt@redhat.com>
Mon, 16 Jan 2017 08:32:58 +0000 (08:32 +0000)
This will help us detect a whole range of issues, notably any
blueprint related problems, much earlier in our development cycle than
we currently are.

The log contains details re. OSGi missing and available services.

NB: This change includes a (surprisingly long!) "black list" of features
known to be broken right now.  These ideally should all be fixed, and
the black list removed.

IMPL: The new bundles-test was introduced here because I ran into Pax
Exam related ClassNotFoundException, which I could not to resolve any
other way.  Basically, the problem is that the "probe" bundle which Pax
Exam creates on the fly includes the SingleFeatureTest class, and not
classes it references.  In other tests like IT this works, possibly
because Pax Exam somehow finds them in src/test/java; but not from
features-test.  (The currently existing utility classes next to
SingleFeatureTest in features-test probably only work because those run
outside the probe?)

This also did not help, because it's never called, because the
ClassNotFoundException happened earlier, on loading of the class
already:

import org.osgi.framework.Constants;

@ProbeBuilder
public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) {
    LOG.info("probeConfiguration()");
    probe.setHeader(Constants.DYNAMICIMPORT_PACKAGE, "*");
    probe.setHeader(Constants.EXPORT_PACKAGE,
"org.opendaylight.odlparent.featuretest");
    probe.setHeader(IMPORT_PACKAGE,
"org.opendaylight.odlparent.featuretest");
    return probe;
}

Change-Id: I328e503703770aab539fdf1f0a7804ba676596ac
Signed-off-by: Michael Vorburger <vorburger@redhat.com>
15 files changed:
bundles-test/pom.xml [new file with mode: 0644]
bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/BundleDiagInfos.java [new file with mode: 0644]
bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/BundleServiceSummaryMatcher.java [new file with mode: 0644]
bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/ServiceReferenceUtil.java [new file with mode: 0644]
bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/SystemState.java [new file with mode: 0644]
bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/TestBundleDiag.java [new file with mode: 0644]
bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/package-info.java [new file with mode: 0644]
bundles-test/src/test/java/org/opendaylight/odlparent/bundlestest/ServiceReferenceUtilTest.java [new file with mode: 0644]
feature-repo-parent/pom.xml
features-parent/pom.xml
features-test/pom.xml
features-test/src/main/java/org/opendaylight/odlparent/featuretest/PerFeatureRunner.java
features-test/src/main/java/org/opendaylight/odlparent/featuretest/SingleFeatureTest.java
odlparent/pom.xml
pom.xml

diff --git a/bundles-test/pom.xml b/bundles-test/pom.xml
new file mode 100644 (file)
index 0000000..d4c3059
--- /dev/null
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+ Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ and is available at http://www.eclipse.org/legal/epl-v10.html
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.opendaylight.odlparent</groupId>
+    <artifactId>bundle-parent</artifactId>
+    <version>1.8.0-SNAPSHOT</version>
+    <relativePath>../bundle-parent</relativePath>
+  </parent>
+
+  <artifactId>bundles-test</artifactId>
+  <packaging>bundle</packaging>
+  <name>ODL :: odlparent :: ${project.artifactId}</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.karaf.bundle</groupId>
+      <artifactId>org.apache.karaf.bundle.core</artifactId>
+      <version>${karaf.version}</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.hamcrest</groupId>
+      <artifactId>hamcrest-library</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.hamcrest</groupId>
+      <artifactId>hamcrest-core</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.awaitility</groupId>
+      <artifactId>awaitility</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <configuration>
+          <propertyExpansion>checkstyle.violationSeverity=error</propertyExpansion>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <configuration>
+          <failOnError>true</failOnError>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <!--
+    Maven Site Configuration
+
+    The following configuration is necessary for maven-site-plugin to
+    correctly identify the correct deployment path for OpenDaylight Maven
+    sites.
+  -->
+  <url>${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/</url>
+
+  <distributionManagement>
+    <site>
+      <id>opendaylight-site</id>
+      <url>${nexus.site.url}/${project.artifactId}/</url>
+    </site>
+  </distributionManagement>
+
+</project>
diff --git a/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/BundleDiagInfos.java b/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/BundleDiagInfos.java
new file mode 100644 (file)
index 0000000..547e149
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.odlparent.bundlestest;
+
+import static org.apache.karaf.bundle.core.BundleState.Active;
+import static org.apache.karaf.bundle.core.BundleState.Installed;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.karaf.bundle.core.BundleInfo;
+import org.apache.karaf.bundle.core.BundleService;
+import org.apache.karaf.bundle.core.BundleState;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/**
+ * System readiness diagnostic summary information.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class BundleDiagInfos {
+
+    private final List<String> okBundleStateInfoTexts;
+    private final List<String> nokBundleStateInfoTexts;
+    private final List<String> whitelistedBundleStateInfoTexts;
+    private final Map<BundleState, Integer> bundleStatesCounters;
+
+    private static final Map<String, BundleState> WHITELISTED_BUNDLES = ImmutableMap.of(
+            "slf4j.log4j12", Installed );
+
+    public static BundleDiagInfos forContext(BundleContext bundleContext, BundleService bundleService) {
+        List<String> okBundleStateInfoTexts = new ArrayList<>();
+        List<String> nokBundleStateInfoTexts = new ArrayList<>();
+        List<String> whitelistedBundleStateInfoTexts = new ArrayList<>();
+        Map<BundleState, Integer> bundleStatesCounters = new EnumMap<>(BundleState.class);
+        for (BundleState bundleState : BundleState.values()) {
+            bundleStatesCounters.put(bundleState, 0);
+        }
+
+        for (Bundle bundle : bundleContext.getBundles()) {
+            String bundleSymbolicName = bundle.getSymbolicName();
+            BundleInfo karafBundleInfo = bundleService.getInfo(bundle);
+            BundleState karafBundleState = karafBundleInfo.getState();
+
+            String bundleStateDiagText = "OSGi state = " + bundleStatetoText(bundle.getState())
+                + ", Karaf bundleState = " + karafBundleState;
+            String diagText = bundleService.getDiag(bundle);
+            if (!Strings.isNullOrEmpty(diagText)) {
+                bundleStateDiagText += ", due to: " + diagText;
+            }
+
+            if (WHITELISTED_BUNDLES.get(bundleSymbolicName) != null) {
+                if (WHITELISTED_BUNDLES.get(bundleSymbolicName).equals(karafBundleState)) {
+                    String msg = "WHITELISTED " + bundleSymbolicName + ": " + bundleStateDiagText;
+                    whitelistedBundleStateInfoTexts.add(msg);
+                    continue;
+                }
+            }
+
+            bundleStatesCounters.compute(karafBundleState, (key, counter) -> counter + 1);
+
+            // BundleState comparison as in Karaf's "diag" command,
+            // see https://github.com/apache/karaf/blob/master/bundle/core/src/main/java/org/apache/karaf/bundle/command/Diag.java
+            // but we intentionally, got a little further than Karaf's "diag" command,
+            // and instead of only checking some states, we check what's really Active,
+            // but accept that some remain just Resolved:
+            if (karafBundleState != Active && !(karafBundleState == BundleState.Resolved)) {
+                String msg = "NOK " + bundleSymbolicName + ": " + bundleStateDiagText;
+                nokBundleStateInfoTexts.add(msg);
+            } else {
+                String msg = "OK " + bundleSymbolicName + ": " + bundleStateDiagText;
+                okBundleStateInfoTexts.add(msg);
+            }
+        }
+
+        return new BundleDiagInfos(okBundleStateInfoTexts, nokBundleStateInfoTexts,
+                whitelistedBundleStateInfoTexts, bundleStatesCounters);
+    }
+
+    private static String bundleStatetoText(int state) {
+        switch (state) {
+            case Bundle.INSTALLED:
+                return "Installed";
+            case Bundle.RESOLVED:
+                return "Resolved";
+            case Bundle.STARTING:
+                return "Starting";
+            case Bundle.ACTIVE:
+                return "Active";
+            case Bundle.STOPPING:
+                return "Stopping";
+            case Bundle.UNINSTALLED:
+                return "Uninstalled";
+            default:
+                return state + "???";
+        }
+    }
+
+    private BundleDiagInfos(List<String> okBundleStateInfoTexts, List<String> nokBundleStateInfoTexts,
+            List<String> whitelistedBundleStateInfoTexts, Map<BundleState, Integer> bundleStatesCounters) {
+        this.okBundleStateInfoTexts = ImmutableList.copyOf(okBundleStateInfoTexts);
+        this.nokBundleStateInfoTexts = ImmutableList.copyOf(nokBundleStateInfoTexts);
+        this.whitelistedBundleStateInfoTexts = ImmutableList.copyOf(whitelistedBundleStateInfoTexts);
+        this.bundleStatesCounters = ImmutableMap.copyOf(bundleStatesCounters);
+    }
+
+    public SystemState getSystemState() {
+        if (bundleStatesCounters.get(BundleState.Failure) > 0) {
+            return SystemState.Failure;
+        } else if (bundleStatesCounters.get(BundleState.Stopping) > 0) {
+            return SystemState.Stopping;
+        } else if (bundleStatesCounters.get(BundleState.Installed) == 0
+                // No, just Resolved is OK, so do not: && bundleStatesCounters.get(BundleState.Resolved) == 0
+                && bundleStatesCounters.get(BundleState.Unknown) == 0
+                && bundleStatesCounters.get(BundleState.GracePeriod) == 0
+                && bundleStatesCounters.get(BundleState.Waiting) == 0
+                && bundleStatesCounters.get(BundleState.Starting) == 0
+                // BundleState.Active *should* be ~= total # of bundles (minus Resolved, and whitelisted installed)
+                && bundleStatesCounters.get(BundleState.Stopping) == 0
+                && bundleStatesCounters.get(BundleState.Failure) == 0) {
+            return SystemState.Active;
+        } else {
+            return SystemState.Booting;
+        }
+    }
+
+    public String getFullDiagnosticText() {
+        StringBuilder sb = new StringBuilder(getSummaryText());
+        int failureNumber = 1;
+        for (String nokBundleStateInfoText : getNokBundleStateInfoTexts()) {
+            sb.append('\n');
+            sb.append(failureNumber++);
+            sb.append(". ");
+            sb.append(nokBundleStateInfoText);
+        }
+        return sb.toString();
+    }
+
+    public String getSummaryText() {
+        return "diag: " + getSystemState() + " " + bundleStatesCounters.toString();
+    }
+
+    public List<String> getNokBundleStateInfoTexts() {
+        return ImmutableList.copyOf(nokBundleStateInfoTexts);
+    }
+
+    public List<String> getOkBundleStateInfoTexts() {
+        return ImmutableList.copyOf(okBundleStateInfoTexts);
+    }
+
+    public List<String> getWhitelistedBundleStateInfoTexts() {
+        return ImmutableList.copyOf(whitelistedBundleStateInfoTexts);
+    }
+
+    @Override
+    public String toString() {
+        return getFullDiagnosticText();
+    }
+}
diff --git a/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/BundleServiceSummaryMatcher.java b/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/BundleServiceSummaryMatcher.java
new file mode 100644 (file)
index 0000000..aede1ac
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.odlparent.bundlestest;
+
+import static org.opendaylight.odlparent.bundlestest.SystemState.Active;
+import static org.opendaylight.odlparent.bundlestest.SystemState.Failure;
+import static org.opendaylight.odlparent.bundlestest.SystemState.Stopping;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+
+/**
+ * Hamcrest Matcher for {@link BundleDiagInfos}.
+ *
+ * @author Michael Vorburger.ch
+ */
+class BundleServiceSummaryMatcher extends BaseMatcher<BundleDiagInfos> {
+
+    @Override
+    public boolean matches(Object item) {
+        SystemState systemState = ((BundleDiagInfos) item).getSystemState();
+        return systemState.equals(Active) || systemState.equals(Stopping) || systemState.equals(Failure);
+    }
+
+    @Override
+    public void describeTo(Description description) {
+        description.appendText("system either ready with all bundles Active, "
+                + "or Stopping or Failure (but not still booting in GracePeriod, Waiting, Starting, Unknown;"
+                + "but just Resolved and some exceptional Installed is OK)");
+    }
+}
diff --git a/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/ServiceReferenceUtil.java b/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/ServiceReferenceUtil.java
new file mode 100644 (file)
index 0000000..542e39a
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.odlparent.bundlestest;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Utilities for OSGi's {@link ServiceReference}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class ServiceReferenceUtil {
+
+    public Map<String, Object> getProperties(ServiceReference<?> serviceRef) {
+        String[] propertyKeys = serviceRef.getPropertyKeys();
+        Map<String, Object> properties = new HashMap<>(propertyKeys.length);
+        for (String propertyKey : propertyKeys) {
+            Object propertyValue = serviceRef.getProperty(propertyKey);
+            if (propertyValue.getClass().isArray()) {
+                propertyValue = Arrays.asList((Object[]) propertyValue);
+            }
+            properties.put(propertyKey, propertyValue);
+        }
+        return properties;
+    }
+
+    public List<String> getUsingBundleSymbolicNames(ServiceReference<?> serviceRef) {
+        if (serviceRef.getUsingBundles() == null) {
+            return Collections.emptyList();
+        } else {
+            return Arrays.asList(serviceRef.getUsingBundles()).stream()
+                .map(bundle -> bundle.getSymbolicName()).collect(Collectors.toList());
+        }
+    }
+
+}
diff --git a/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/SystemState.java b/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/SystemState.java
new file mode 100644 (file)
index 0000000..4f67778
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.odlparent.bundlestest;
+
+import org.apache.karaf.bundle.core.BundleState;
+
+/**
+ * Karaf OSGi System State.
+ *
+ * @see BundleState
+ *
+ * @author Michael Vorburger.ch
+ */
+public enum SystemState {
+
+    Booting,
+
+    Active,
+
+    Stopping,
+
+    Failure
+
+}
diff --git a/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/TestBundleDiag.java b/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/TestBundleDiag.java
new file mode 100644 (file)
index 0000000..1c1c92c
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.odlparent.bundlestest;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.junit.Assert.fail;
+
+import org.apache.karaf.bundle.core.BundleService;
+import org.awaitility.Awaitility;
+import org.awaitility.core.ConditionTimeoutException;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility to verify bundle diagnostic state from OSGi integration tests.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class TestBundleDiag {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TestBundleDiag.class);
+
+    private final BundleContext bundleContext;
+    private final BundleService bundleService;
+
+    public TestBundleDiag(BundleContext bundleContext, BundleService bundleService) {
+        this.bundleContext = bundleContext;
+        this.bundleService = bundleService;
+    }
+
+    /**
+     * Does the equivalent of the "diag" CLI command, and fails the test if anything incl. bundle wiring is NOK.
+     *
+     * <p>The implementation is based on Karaf's BundleService, and not the BundleStateService,
+     * because each Karaf supported DI system (such as Blueprint and Declarative Services, see String constants
+     * in BundleStateService), will have a separate BundleStateService.  The BundleService however will
+     * contain the combined status of all BundleStateServices.
+     *
+     * @author Michael Vorburger, based on guidance from Christian Schneider
+     */
+    public void checkBundleDiagInfos() {
+        try {
+            Awaitility.await("checkBundleDiagInfos")
+                .pollDelay(0, MILLISECONDS)
+                .pollInterval(250, MILLISECONDS)
+                .atMost(5, MINUTES)
+                    .conditionEvaluationListener(
+                        condition -> LOG.info("{} (elapsed time {}ms, remaining time {}ms)",
+                            ((BundleDiagInfos) condition.getValue()).getSummaryText(),
+                            condition.getElapsedTimeInMS(), condition.getRemainingTimeInMS()))
+                    .until(() -> getBundleDiagInfos(), new BundleServiceSummaryMatcher());
+
+            // If we're here then either BundleServiceSummaryMatcher quit because of Active, Failure or Stopping..
+            BundleDiagInfos bundleInfos = getBundleDiagInfos();
+            SystemState systemState = bundleInfos.getSystemState();
+            if (systemState.equals(SystemState.Failure) || systemState.equals(SystemState.Stopping)) {
+                LOG.error("diag failure; BundleService reports bundle(s) which failed or are already stopping"
+                        + " (details in following INFO and ERROR log messages...)");
+                logBundleDiagInfos(bundleInfos);
+                fail(bundleInfos.getFullDiagnosticText());
+
+            } else {
+                // Inform the developer of the green SystemState.Active
+                LOG.info(bundleInfos.getFullDiagnosticText());
+            }
+
+        } catch (ConditionTimeoutException e) {
+            // If this happens then it got stuck waiting in SystemState.Booting,
+            // typically due to bundles still in BundleState GracePeriod or Waiting
+            LOG.error("diag failure; BundleService reports bundle(s) which are still not active"
+                    + " (details in following INFO and ERROR log messages...)");
+            BundleDiagInfos bundleInfos = getBundleDiagInfos();
+            logBundleDiagInfos(bundleInfos);
+            throw e; // fail the test!
+        }
+    }
+
+    private void logBundleDiagInfos(BundleDiagInfos bundleInfos) {
+        try {
+            logOSGiServices();
+        } catch (IllegalStateException e) {
+            LOG.warn("logOSGiServices() failed (never mind); too late during shutdown already?", e);
+        }
+        for (String okBundleStateInfo : bundleInfos.getOkBundleStateInfoTexts()) {
+            LOG.info(okBundleStateInfo);
+        }
+        for (String whitelistedBundleStateInfo : bundleInfos.getWhitelistedBundleStateInfoTexts()) {
+            LOG.warn(whitelistedBundleStateInfo);
+        }
+        for (String nokBundleStateInfo : bundleInfos.getNokBundleStateInfoTexts()) {
+            LOG.error(nokBundleStateInfo);
+        }
+    }
+
+    private BundleDiagInfos getBundleDiagInfos() {
+        return BundleDiagInfos.forContext(bundleContext, bundleService);
+    }
+
+    private void logOSGiServices() {
+        ServiceReferenceUtil util = new ServiceReferenceUtil();
+        LOG.info("Now going to log all known services, to help diagnose root cause of "
+                + "diag failure BundleService reported bundle(s) which are not active");
+        try {
+            for (ServiceReference<?> serviceRef : bundleContext.getAllServiceReferences(null, null)) {
+                LOG.info("{} defines OSGi Service {} used by {}", serviceRef.getBundle().getSymbolicName(),
+                        util.getProperties(serviceRef), util.getUsingBundleSymbolicNames(serviceRef));
+            }
+        } catch (InvalidSyntaxException e) {
+            LOG.error("logOSGiServices() failed due to InvalidSyntaxException", e);
+        }
+    }
+
+}
diff --git a/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/package-info.java b/bundles-test/src/main/java/org/opendaylight/odlparent/bundlestest/package-info.java
new file mode 100644 (file)
index 0000000..db3ec9e
--- /dev/null
@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.opendaylight.odlparent.bundlestest;
diff --git a/bundles-test/src/test/java/org/opendaylight/odlparent/bundlestest/ServiceReferenceUtilTest.java b/bundles-test/src/test/java/org/opendaylight/odlparent/bundlestest/ServiceReferenceUtilTest.java
new file mode 100644 (file)
index 0000000..2d32084
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.odlparent.bundlestest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Unit test for ServiceReferenceUtil.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class ServiceReferenceUtilTest {
+
+    @Test
+    public void testGetUsingBundleSymbolicNames() {
+        assertThat(new ServiceReferenceUtil().getUsingBundleSymbolicNames(getServiceReference())).isEmpty();
+    }
+
+    @Test
+    public void testGetProperties() {
+        assertThat(new ServiceReferenceUtil().getProperties(getServiceReference())).containsExactly(
+                "property1", "value1",
+                "property2", Arrays.asList(new String[] { "value2.1", "value2.2" }));
+    }
+
+    private ServiceReference<?> getServiceReference() {
+        return new ServiceReference<Object>() {
+
+            @Override
+            public Object getProperty(String key) {
+                if ("property1".equals(key)) {
+                    return "value1";
+                } else if ("property2".equals(key)) {
+                    return new String[] { "value2.1", "value2.2" };
+                } else {
+                    return null;
+                }
+            }
+
+            @Override
+            public String[] getPropertyKeys() {
+                return new String[] { "property1", "property2"};
+            }
+
+            @Override
+            public Bundle getBundle() {
+                return null;
+            }
+
+            @Override
+            public Bundle[] getUsingBundles() {
+                return null;
+            }
+
+            @Override
+            public boolean isAssignableTo(Bundle bundle, String className) {
+                return false;
+            }
+
+            @Override
+            public int compareTo(Object reference) {
+                return 0;
+            }
+        };
+    }
+
+}
index 55194f281ce37a0ed75201c25abf6b43ca3c60c4..86eff2e15d3a256801a8efd7d76277fb68e53742 100644 (file)
 
     <build>
         <plugins>
+            <plugin>
+                <!-- This generates the META-INF/maven/dependencies.properties file
+                     which is required by the versionAsInProject() used in SingleFeatureTest -->
+                <groupId>org.apache.servicemix.tooling</groupId>
+                <artifactId>depends-maven-plugin</artifactId>
+            </plugin>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <version>${maven.surefire.version}</version>
                 <configuration>
                     <skip>${skip.karaf.featureTest}</skip>
                     <dependenciesToScan>
@@ -82,7 +87,6 @@
             <plugin>
                 <groupId>org.apache.karaf.tooling</groupId>
                 <artifactId>karaf-maven-plugin</artifactId>
-                <version>3.0.8</version>
                 <extensions>true</extensions>
                 <configuration>
                     <aggregateFeatures>true</aggregateFeatures>
         </site>
     </distributionManagement>
 
-</project>
\ No newline at end of file
+</project>
index 7b223c930a17c7b81eaf5490b1b9f5a65c5e0e6e..91af1708f9a06418001c61f6ba108ef79f31478f 100644 (file)
         </pluginManagement>
 
         <plugins>
+            <plugin>
+                <!-- This generates the META-INF/maven/dependencies.properties file
+                        which is required by the versionAsInProject() used in SingleFeatureTest -->
+                <groupId>org.apache.servicemix.tooling</groupId>
+                <artifactId>depends-maven-plugin</artifactId>
+            </plugin>
             <plugin>
                 <artifactId>maven-dependency-plugin</artifactId>
             </plugin>
index 2ff586ba7b858975a8c4ad2f7b777406b81dd863..0d6653835b984c82e78274f894455b7bd6429464 100644 (file)
@@ -97,7 +97,6 @@
         <dependency>
             <groupId>javax.inject</groupId>
             <artifactId>javax.inject</artifactId>
-            <version>1</version>
             <scope>compile</scope>
         </dependency>
         <dependency>
             <artifactId>org.osgi.core</artifactId>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>bundles-test</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
 
     <build>
index cebd7ee9fb26536d702b46ca56d55543e44769dc..f14a401bb17c3d18245f25a991a8e982e4bdbce2 100644 (file)
@@ -28,7 +28,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class PerFeatureRunner extends Runner implements Filterable, Sortable {
+
     private static final Logger LOG = LoggerFactory.getLogger(PerFeatureRunner.class);
+
     private final String featureVersion;
     private final String featureName;
     private final PaxExam delegate;
index 6c26bb45152f023e3d6e454ba6c42634fc19a902..d46677a40d369557251c70c369705158d08a1c25 100644 (file)
@@ -12,7 +12,9 @@ import static org.opendaylight.odlparent.featuretest.Constants.ORG_OPENDAYLIGHT_
 import static org.opendaylight.odlparent.featuretest.Constants.ORG_OPENDAYLIGHT_FEATURETEST_FEATUREVERSION_PROP;
 import static org.opendaylight.odlparent.featuretest.Constants.ORG_OPENDAYLIGHT_FEATURETEST_URI_PROP;
 import static org.ops4j.pax.exam.CoreOptions.maven;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
 import static org.ops4j.pax.exam.CoreOptions.when;
+import static org.ops4j.pax.exam.CoreOptions.wrappedBundle;
 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureConsole;
 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut;
 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.features;
@@ -20,6 +22,7 @@ import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDist
 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;
 import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;
 
+import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -30,23 +33,25 @@ import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Properties;
-
 import javax.inject.Inject;
-
+import org.apache.karaf.bundle.core.BundleService;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeaturesService;
 import org.apache.karaf.features.Repository;
 import org.apache.karaf.features.internal.model.Features;
 import org.apache.karaf.features.internal.model.JaxbUtil;
+import org.eclipse.jdt.annotation.NonNull;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.opendaylight.odlparent.bundlestest.TestBundleDiag;
 import org.ops4j.pax.exam.Configuration;
 import org.ops4j.pax.exam.CoreOptions;
 import org.ops4j.pax.exam.Option;
 import org.ops4j.pax.exam.karaf.options.LogLevelOption.LogLevel;
 import org.ops4j.pax.exam.options.extra.VMOption;
+import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -58,8 +63,10 @@ public class SingleFeatureTest {
     private static final String ORG_OPS4J_PAX_URL_MVN_REPOSITORIES = "org.ops4j.pax.url.mvn.repositories";
     private static final String ETC_ORG_OPS4J_PAX_URL_MVN_CFG = "etc/org.ops4j.pax.url.mvn.cfg";
     private static final String ETC_ORG_OPS4J_PAX_LOGGING_CFG = "etc/org.ops4j.pax.logging.cfg";
+
     private static final String KEEP_UNPACK_DIRECTORY_PROP = "karaf.keep.unpack";
     private static final String PROFILE_PROP = "karaf.featureTest.profile";
+    private static final String BUNDLES_DIAG_SKIP_PROP = "sft.diag.skip";
 
     private static final String LOG4J_LOGGER_ORG_OPENDAYLIGHT_YANGTOOLS_FEATURETEST =
             "log4j.logger.org.opendaylight.odlparent.featuretest";
@@ -103,10 +110,15 @@ public class SingleFeatureTest {
             + "http://repository.springsource.com/maven/bundles/external@id=spring.ebr.external, "
             + "http://zodiac.springsource.com/maven/bundles/release@id=gemini ";
 
+    @Inject @NonNull
+    private BundleContext bundleContext;
 
-    @Inject
+    @Inject @NonNull
     private FeaturesService featuresService;
 
+    @Inject @NonNull
+    private BundleService bundleService; // NOT BundleStateService, see checkBundleStatesDiag()
+
     private String karafVersion;
     private String karafDistroVersion;
 
@@ -140,6 +152,9 @@ public class SingleFeatureTest {
             logLevel(LogLevel.WARN),
             mvnLocalRepoOption(),
             standardKarafFeatures(),
+            wrappedBundle(maven("org.awaitility", "awaitility").versionAsInProject()), // req. by bundles-test
+            mavenBundle(maven("com.google.guava", "guava").versionAsInProject()),      // req. by bundles-test
+            mavenBundle(maven("org.opendaylight.odlparent", "bundles-test").versionAsInProject()),
             editConfigurationFilePut(ORG_OPS4J_PAX_LOGGING_CFG, LOG4J_LOGGER_ORG_OPENDAYLIGHT_YANGTOOLS_FEATURETEST,
                     LogLevel.INFO.name()),
             editConfigurationFilePut(ETC_ORG_OPS4J_PAX_LOGGING_CFG, "log4j.rootLogger", "INFO, stdout, osgi:*"),
@@ -322,5 +337,49 @@ public class SingleFeatureTest {
         Assert.assertTrue("Failed to install Feature: " + getFeatureName() + " " + getFeatureVersion(),
                 featuresService.isInstalled(feature));
         LOG.info("Successfull installed feature {} {}", getFeatureName(), getFeatureVersion());
+
+        if (!Boolean.getBoolean(BUNDLES_DIAG_SKIP_PROP)
+                && !BLACKLISTED_BROKEN_FEATURES.contains(getFeatureName())) {
+            new TestBundleDiag(bundleContext, bundleService).checkBundleDiagInfos();
+        } else {
+            LOG.warn("SKIPPING TestBundleDiag because system property {} is true or feature is blacklisted: {}",
+                    BUNDLES_DIAG_SKIP_PROP, getFeatureName());
+        }
     }
+
+    // TODO Figure out why each of these fails the TestBundleDiag, fix it, and remove this.. ;)
+    private static final List<String> BLACKLISTED_BROKEN_FEATURES = ImmutableList.of(
+             // integration/distribution/features-test due to (unclear)
+            "odl-integration-all",
+            // controller/features/mdsal/ due to IllegalStateException: ./configuration/initial/akka.conf is missing
+            "odl-mdsal-broker-local",
+            "odl-mdsal-clustering-commons",
+            "odl-mdsal-distributed-datastore",
+            "odl-mdsal-remoterpc-connector",
+            // aaa/features/authn due to Cassandra expected to be up on
+            "odl-aaa-authn-cassandra-cluster",
+            // 3/18 in bgpcep/features/bgp/ due to NoSuchFileException: etc/....
+            "odl-bgpcep-bgp-rib-impl",
+            "odl-bgpcep-bgp-topology",
+            "odl-bgpcep-bgp-cli",
+            // 1/1 in bgpcep/features/bmp due to NoSuchFileException: etc/opendaylight/bgp
+            "odl-bgpcep-bmp",
+            // 4/8 in lispflowmapping/features due to.. unclear, similar issue to odl-integration-all?
+            "odl-lispflowmapping-mappingservice",
+            "odl-lispflowmapping-mappingservice-shell",
+            "odl-lispflowmapping-neutron",
+            "odl-lispflowmapping-ui",
+            // 1/17 in lispflowmapping/features due to NOK org.opendaylight.groupbasedpolicy
+            // Caused by: org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException
+            "odl-groupbasedpolicy-ne-location-provider",
+            // 1/11 in tsdr/features due to (strange) ClassNotFoundException: odlparent.bundlestest
+            //   .TestBundleDiag (works for all other features; class loading issue in that feature?)
+            "odl-hbaseclient",
+            // 1/9 in unimgr/features due missing mdsal, similar to issue to odl-integration-all?
+            "odl-unimgr-netvirt",
+            // 1/19 in bgpcep/features/bgp due to Missing dependencies:
+            // (objectClass=org.opendaylight.protocol.bgp.rib.spi.state.BGPStateConsumer)
+            // NOK org.opendaylight.bgpcep.bgp-openconfig-state
+            "odl-bgpcep-bgp-openconfig-state"
+    );
 }
index 08202fdb116e73b5645989c715d2e13b59a02f37..8524adccda44965a382ec6b737e7e850b5be58ef 100644 (file)
                     <ignore/>
                   </action>
                 </pluginExecution>
+
+                <pluginExecution>
+                  <pluginExecutionFilter>
+                    <groupId>org.ops4j.pax.exam</groupId>
+                    <artifactId>maven-paxexam-plugin</artifactId>
+                    <versionRange>[1.2.4,)</versionRange>
+                    <goals>
+                      <goal>generate-depends-file</goal>
+                    </goals>
+                  </pluginExecutionFilter>
+                  <action>
+                    <ignore/>
+                  </action>
+                </pluginExecution>
+
               </pluginExecutions>
             </lifecycleMappingMetadata>
           </configuration>
diff --git a/pom.xml b/pom.xml
index f0fa12f0c3ae2c49e91b8a82081d47b9d164d8e8..a8ee24306874a751db80d2317ca66c6aabbb44d5 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -31,6 +31,7 @@
         <module>checkstyle</module>
         <module>findbugs</module>
         <module>license</module>
+        <module>bundles-test</module>
         <module>features-test</module>
 
         <!-- Karaf integration -->