2 * Copyright (c) 2024 PANTHEON.tech s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.odlparent.features.test.plugin;
10 import static org.apache.karaf.bundle.core.BundleState.Installed;
11 import static org.apache.karaf.bundle.core.BundleState.Waiting;
15 import java.util.Arrays;
16 import java.util.EnumSet;
17 import java.util.HashMap;
19 import java.util.stream.Collectors;
20 import javax.inject.Inject;
21 import org.apache.karaf.bundle.core.BundleService;
22 import org.apache.karaf.bundle.core.BundleState;
23 import org.apache.karaf.features.FeaturesService;
24 import org.junit.Test;
25 import org.osgi.framework.Bundle;
26 import org.osgi.framework.BundleContext;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * The feature test probe artifact.
34 * The class is being packaged and deployed to karaf environment on {@link PaxExamExecution#execute()} invocation.
35 * All dependencies which are absent on target environment expected to be packaged using same
36 * {@link org.ops4j.pax.exam.ProbeBuilder}. Input parameters are passed through system properties. in order to be
37 * delivered properly all affected properties require explicit declaration using associated Pax options -- see
38 * {@link PaxOptionUtils#probePropertiesOptions()}.
41 * Pax Exam module references:
43 * <li>Probe bundle deployment handling is served by pax-exam-extender-service</li>
44 * <li>Service instances lookup and injection into probe instance is served by pax-exam-inject</li>
45 * <li>Test method invocation is served by pax-exam-invoker-junit, uses JUnitCore v.4, which requires @Test
46 * annotation for method to be eligible for invocation</li>
49 public final class TestProbe {
51 static final String FEATURE_FILE_URI_PROP = "feature.test.file.uri";
52 static final String BUNDLE_CHECK_SKIP = "feature.test.bundle.check.skip";
53 static final String BUNDLE_CHECK_TIMEOUT_SECONDS = "feature.test.bundle.check.timeout.seconds";
54 static final String BUNDLE_CHECK_INTERVAL_SECONDS = "feature.test.bundle.check.interval.seconds";
55 static final String DEFAULT_TIMEOUT = "300";
56 static final String DEFAULT_INTERVAL = "1";
58 static final String[] ALL_PROPERTY_KEYS =
59 {FEATURE_FILE_URI_PROP, BUNDLE_CHECK_SKIP, BUNDLE_CHECK_TIMEOUT_SECONDS, BUNDLE_CHECK_INTERVAL_SECONDS};
61 private static final Logger LOG = LoggerFactory.getLogger(TestProbe.class);
62 private static final Map<Integer, String> OSGI_STATES = Map.of(
63 Bundle.INSTALLED, "Installed", Bundle.RESOLVED, "Resolved",
64 Bundle.STARTING, "Starting", Bundle.ACTIVE, "Active",
65 Bundle.STOPPING, "Stopping", Bundle.UNINSTALLED, "Uninstalled");
66 private static final Map<String, BundleState> ELIGIBLE_STATES = Map.of(
67 "slf4j.log4j12", Installed,
68 "org.apache.karaf.scr.management", Waiting);
70 private final Map<Long, CheckResult> bundleCheckResults = new HashMap<>();
73 private BundleContext bundleContext;
76 private FeaturesService featuresService;
79 private BundleService bundleService;
82 * Performs the project feature installation on karaf environment with subsequent state check of deployed bundles.
84 * @throws Exception on probe failure
87 @SuppressWarnings("IllegalCatch")
88 public void testFeature() throws Exception {
93 } catch (Exception e) {
94 LOG.error("Exception executing feature test", e);
99 private void validateServices() {
100 if (bundleContext == null) {
101 throw new IllegalStateException("bundleContext is not initialized");
103 // replace the probe's initial context which expires too fast
104 bundleContext = bundleContext.getBundle(0).getBundleContext();
106 if (featuresService == null) {
107 throw new IllegalStateException("featureService is not initialized");
109 if (bundleService == null) {
110 throw new IllegalStateException("bundleService is not initialized");
114 private void installFeatures() throws Exception {
115 final var featureUri = URI.create(System.getProperty(FEATURE_FILE_URI_PROP));
116 if (!new File(featureUri).exists()) {
117 throw new IllegalStateException("Feature file with URI " + featureUri + " does not exist");
120 // install repository the feature definition can be read from
121 featuresService.addRepository(featureUri);
122 LOG.info("Feature repository with URI: {} initialized", featureUri);
125 for (var feature : featuresService.getRepository(featureUri).getFeatures()) {
126 final var name = feature.getName();
127 final var version = feature.getVersion();
128 LOG.info("Installing feature: {}, {}", name, version);
129 featuresService.installFeature(name, version, EnumSet.of(FeaturesService.Option.Verbose));
130 LOG.info("Feature is installed: {}, isInstalled()={}, getState()={}",
131 name, featuresService.isInstalled(feature), featuresService.getState(feature.getId()));
135 private void checkBundleStates() throws InterruptedException {
136 if ("true".equals(System.getProperty(BUNDLE_CHECK_SKIP))) {
139 final int timeout = Integer.parseInt(System.getProperty(BUNDLE_CHECK_TIMEOUT_SECONDS, DEFAULT_TIMEOUT));
140 final int interval = Integer.parseInt(System.getProperty(BUNDLE_CHECK_INTERVAL_SECONDS, DEFAULT_INTERVAL));
141 LOG.info("Checking bundle states. Interval = {} second(s). Timeout = {} second(s).", interval, timeout);
143 final var maxTimestamp = System.currentTimeMillis() + timeout * 1000L;
144 CheckResult result = CheckResult.IN_PROGRESS;
145 while (System.currentTimeMillis() < maxTimestamp) {
146 Arrays.stream(bundleContext.getBundles()).forEach(this::captureBundleState);
147 result = aggregatedCheckResults();
148 if (result != CheckResult.IN_PROGRESS) {
151 Thread.sleep(interval * 1000L);
153 LOG.info("Bundle state check completed with result {}", result);
154 if (result == CheckResult.IN_PROGRESS) {
155 logNokBundleDetails();
156 throw new IllegalStateException("Bundles states check timeout");
158 if (result != CheckResult.SUCCESS) {
159 logNokBundleDetails();
160 throw new IllegalStateException("Bundle states check failure");
164 private void captureBundleState(final Bundle bundle) {
165 if (bundle != null) {
166 final var info = bundleService.getInfo(bundle);
167 final var checkResult = checkResultOf(info.getSymbolicName(), info.getState());
168 if (checkResult != bundleCheckResults.get(bundle.getBundleId())) {
169 LOG.info("Bundle {} -> State: {} ({})", info.getSymbolicName(), info.getState(), checkResult);
170 bundleCheckResults.put(bundle.getBundleId(), checkResult);
175 private CheckResult aggregatedCheckResults() {
176 final var resultStats = bundleCheckResults.entrySet().stream()
177 .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.counting()));
178 LOG.info("Bundle states check results: total={}, byResult={}", bundleCheckResults.size(), resultStats);
180 if (resultStats.getOrDefault(CheckResult.FAILURE, 0L) > 0) {
181 return CheckResult.FAILURE;
183 if (resultStats.getOrDefault(CheckResult.STOPPING, 0L) > 0) {
184 return CheckResult.STOPPING;
186 return resultStats.getOrDefault(CheckResult.IN_PROGRESS, 0L) == 0
187 ? CheckResult.SUCCESS : CheckResult.IN_PROGRESS;
190 private void logNokBundleDetails() {
191 final var nokBundles = bundleCheckResults.entrySet().stream()
192 .filter(entry -> CheckResult.SUCCESS != entry.getValue())
193 .map(Map.Entry::getKey).collect(Collectors.toSet());
195 for (var bundle : bundleContext.getBundles()) {
196 if (nokBundles.contains(bundle.getBundleId())) {
197 final var info = bundleService.getInfo(bundle);
198 final var diag = bundleService.getDiag(bundle);
199 final var diagText = diag.isEmpty() ? "" : ", diag: " + diag;
200 final var osgiState = OSGI_STATES.getOrDefault(bundle.getState(), "Unknown");
201 LOG.warn("NOK Bundle {}:{} -> OSGi state: {}, Karaf bundle state: {}{}",
202 info.getSymbolicName(), info.getVersion(), osgiState, info.getState(), diagText);
207 static CheckResult checkResultOf(final String bundleName, final BundleState state) {
208 if (bundleName != null && state == ELIGIBLE_STATES.get(bundleName)) {
209 return CheckResult.SUCCESS;
211 if (state == BundleState.Stopping) {
212 return CheckResult.STOPPING;
214 if (state == BundleState.Failure) {
215 return CheckResult.FAILURE;
217 if (state == BundleState.Resolved || state == BundleState.Active) {
218 return CheckResult.SUCCESS;
220 return CheckResult.IN_PROGRESS;
224 SUCCESS, FAILURE, STOPPING, IN_PROGRESS;