Make mdsal-dom-schema-osgi Karaf-aware 62/88062/11
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 28 Feb 2020 11:58:16 +0000 (12:58 +0100)
committerRobert Varga <nite@hq.sk>
Fri, 28 Feb 2020 14:58:28 +0000 (14:58 +0000)
Karaf's FeaturesService exposes lifecycle hooks which allow us
to understand in that an installation is in progress, so that
we can suppress updates while we bundles are not fully resolved.

JIRA: MDSAL-235
Change-Id: Ie5c5b862a5d2975cd4cb08ca4bb1976f263578d2
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
dom/mdsal-dom-schema-osgi/pom.xml
dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/KarafFeaturesSupport.java [new file with mode: 0644]
dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/KarafYangModuleInfoRegistry.java [new file with mode: 0644]
dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiModelRuntime.java
dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/RegularYangModuleInfoRegistry.java [new file with mode: 0644]
dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/YangModuleInfoRegistry.java
dom/mdsal-dom-schema-osgi/src/test/java/org/opendaylight/mdsal/dom/schema/osgi/impl/OSGiModelRuntimeTest.java

index 6bf2e1aa7cffe2f3fa89e676141fc6c0425289a4..88ce32b75b4bc3166033dec9769e58338ffb8c0e 100644 (file)
             <groupId>org.osgi</groupId>
             <artifactId>osgi.cmpn</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.features</groupId>
+            <artifactId>org.apache.karaf.features.core</artifactId>
+            <scope>provided</scope>
+            <!-- Needed to generate optional OSGi import -->
+            <optional>true</optional>
+        </dependency>
 
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
diff --git a/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/KarafFeaturesSupport.java b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/KarafFeaturesSupport.java
new file mode 100644 (file)
index 0000000..de88bf2
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.mdsal.dom.schema.osgi.impl;
+
+import org.apache.karaf.features.FeaturesService;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Optional support for Karaf's FeaturesService. This class centralizes wrapping based on bundle resolution state. If
+ * FeaturesService interface is not resolved, this class ends up reusing RegularYangModuleInfoRegistry. If the interface
+ * is resolved, we use it to locate the appropriate service whenever we are asked to activate.
+ */
+@NonNullByDefault
+final class KarafFeaturesSupport {
+    @FunctionalInterface
+    private interface WrapperFunction {
+        YangModuleInfoRegistry wrap(BundleContext ctx, RegularYangModuleInfoRegistry delegate);
+    }
+
+    private static final class NoopWrapperFunction implements WrapperFunction {
+        @Override
+        public YangModuleInfoRegistry wrap(final BundleContext ctx, final RegularYangModuleInfoRegistry delegate) {
+            return delegate;
+        }
+    }
+
+    private static final class KarafWrapperFunction implements WrapperFunction {
+        // Forces FeaturesService to be resolved
+        private static final Class<FeaturesService> FEATURES_SERVICE = FeaturesService.class;
+
+        @Override
+        public YangModuleInfoRegistry wrap(final BundleContext ctx, final RegularYangModuleInfoRegistry delegate) {
+            final ServiceReference<FeaturesService> ref = ctx.getServiceReference(FEATURES_SERVICE);
+            if (ref != null) {
+                final FeaturesService features = ctx.getService(ref);
+                if (features != null) {
+                    LOG.debug("Integrating with Karaf's FeaturesService");
+                    return KarafYangModuleInfoRegistry.create(features, delegate);
+                }
+            }
+
+            return delegate;
+        }
+    }
+
+    private static final Logger LOG = LoggerFactory.getLogger(KarafFeaturesSupport.class);
+    private static final WrapperFunction WRAPPER = staticInit();
+
+    private KarafFeaturesSupport() {
+        // Hidden on purpose
+    }
+
+    static YangModuleInfoRegistry wrap(final BundleContext ctx, final RegularYangModuleInfoRegistry regular) {
+        return WRAPPER.wrap(ctx, regular);
+    }
+
+    private static WrapperFunction staticInit() {
+        try {
+            final WrapperFunction karaf = new KarafWrapperFunction();
+            LOG.info("Will attempt to integrate with Karaf FeaturesService");
+            return karaf;
+        } catch (NoClassDefFoundError e) {
+            LOG.trace("Failed to initialize Karaf support", e);
+            LOG.info("Karaf FeaturesService integration disabled");
+            return new NoopWrapperFunction();
+        }
+    }
+}
diff --git a/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/KarafYangModuleInfoRegistry.java b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/KarafYangModuleInfoRegistry.java
new file mode 100644 (file)
index 0000000..3dfeb25
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.mdsal.dom.schema.osgi.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import org.apache.karaf.features.DeploymentEvent;
+import org.apache.karaf.features.DeploymentListener;
+import org.apache.karaf.features.FeaturesService;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.ObjectRegistration;
+import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class KarafYangModuleInfoRegistry extends YangModuleInfoRegistry implements DeploymentListener {
+    private static final Logger LOG = LoggerFactory.getLogger(KarafYangModuleInfoRegistry.class);
+
+    private final RegularYangModuleInfoRegistry delegate;
+
+    private boolean scannerEnabled = false;
+    private boolean updatesEnabled = false;
+
+    private KarafYangModuleInfoRegistry(final RegularYangModuleInfoRegistry delegate) {
+        this.delegate = requireNonNull(delegate);
+    }
+
+    static @NonNull KarafYangModuleInfoRegistry create(final FeaturesService features,
+            final RegularYangModuleInfoRegistry delegate) {
+        final KarafYangModuleInfoRegistry ret = new KarafYangModuleInfoRegistry(delegate);
+        features.registerListener(ret);
+        return ret;
+    }
+
+    @Override
+    public synchronized void deploymentEvent(final DeploymentEvent event) {
+        LOG.debug("Features service indicates {}", event);
+        switch (event) {
+            case DEPLOYMENT_STARTED:
+            case BUNDLES_INSTALLED:
+                updatesEnabled = false;
+                LOG.debug("BindingRuntimeContext updates disabled");
+                break;
+            case BUNDLES_RESOLVED:
+            case DEPLOYMENT_FINISHED:
+            default:
+                updatesEnabled = true;
+                LOG.debug("BindingRuntimeContext updates enabled");
+                if (scannerEnabled) {
+                    delegate.enableScannerAndUpdate();
+                }
+        }
+    }
+
+    @Override
+    synchronized void scannerUpdate() {
+        if (updatesEnabled) {
+            delegate.scannerUpdate();
+        }
+    }
+
+    @Override
+    synchronized void enableScannerAndUpdate() {
+        scannerEnabled = true;
+        if (updatesEnabled) {
+            delegate.enableScannerAndUpdate();
+
+        }
+    }
+
+    @Override
+    List<ObjectRegistration<YangModuleInfo>> registerInfos(final List<YangModuleInfo> infos) {
+        return delegate.registerInfos(infos);
+    }
+
+    @Override
+    synchronized void close() {
+        delegate.close();
+    }
+
+    @Override
+    synchronized void scannerShutdown() {
+        scannerEnabled = false;
+        delegate.scannerShutdown();
+    }
+}
index 3bea8c02572f4a19096664a753b569575a6b6a6c..c7041d77eb01c96481e9f427defba6d04a063621 100644 (file)
@@ -32,7 +32,7 @@ public final class OSGiModelRuntime {
     @Activate
     void activate(final BundleContext ctx) {
         LOG.info("Model Runtime starting");
-        moduleRegistry = new YangModuleInfoRegistry(contextFactory, parserFactory);
+        moduleRegistry = YangModuleInfoRegistry.create(ctx, contextFactory, parserFactory);
         bundleTracker = new YangModuleInfoScanner(ctx, moduleRegistry);
         bundleTracker.open();
         moduleRegistry.enableScannerAndUpdate();
diff --git a/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/RegularYangModuleInfoRegistry.java b/dom/mdsal-dom-schema-osgi/src/main/java/org/opendaylight/mdsal/dom/schema/osgi/impl/RegularYangModuleInfoRegistry.java
new file mode 100644 (file)
index 0000000..aff0e1f
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2017, 2020 PANTHEON.tech, s.r.o. 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.mdsal.dom.schema.osgi.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+import java.util.NoSuchElementException;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.Holding;
+import org.opendaylight.binding.runtime.api.ModuleInfoSnapshot;
+import org.opendaylight.binding.runtime.spi.ModuleInfoSnapshotBuilder;
+import org.opendaylight.yangtools.concepts.ObjectRegistration;
+import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
+import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory;
+import org.osgi.service.component.ComponentFactory;
+import org.osgi.service.component.ComponentInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Update SchemaContext service in Service Registry each time new YangModuleInfo is added or removed.
+ */
+final class RegularYangModuleInfoRegistry extends YangModuleInfoRegistry {
+    private static final Logger LOG = LoggerFactory.getLogger(RegularYangModuleInfoRegistry.class);
+
+    private final ComponentFactory contextFactory;
+    private final ModuleInfoSnapshotBuilder moduleInfoRegistry;
+
+    @GuardedBy("this")
+    private ComponentInstance currentInstance;
+    @GuardedBy("this")
+    private ModuleInfoSnapshot currentSnapshot;
+    @GuardedBy("this")
+    private int generation;
+
+    private volatile boolean ignoreScanner = true;
+
+    RegularYangModuleInfoRegistry(final ComponentFactory contextFactory, final YangParserFactory factory) {
+        this.contextFactory = requireNonNull(contextFactory);
+        moduleInfoRegistry = new ModuleInfoSnapshotBuilder("binding-dom-codec", factory);
+    }
+
+    // Invocation from scanner, we may want to ignore this in order to not process partial updates
+    @Override
+    void scannerUpdate() {
+        if (!ignoreScanner) {
+            synchronized (this) {
+                updateService();
+            }
+        }
+    }
+
+    @Override
+    synchronized void scannerShutdown() {
+        ignoreScanner = true;
+    }
+
+    @Override
+    synchronized void enableScannerAndUpdate() {
+        ignoreScanner = false;
+        updateService();
+    }
+
+    @Override
+    synchronized void close() {
+        ignoreScanner = true;
+        if (currentInstance != null) {
+            currentInstance.dispose();
+            currentInstance = null;
+        }
+    }
+
+    @Override
+    List<ObjectRegistration<YangModuleInfo>> registerInfos(final List<YangModuleInfo> infos) {
+        return moduleInfoRegistry.registerModuleInfos(infos);
+    }
+
+    @Holding("this")
+    private void updateService() {
+        final ModuleInfoSnapshot newSnapshot;
+        try {
+            newSnapshot = moduleInfoRegistry.build();
+        } catch (NoSuchElementException e) {
+            LOG.debug("No snapshot available", e);
+            return;
+        }
+        if (newSnapshot.equals(currentSnapshot)) {
+            LOG.debug("No update to snapshot");
+            return;
+        }
+
+
+        final ComponentInstance newInstance = contextFactory.newInstance(
+            OSGiEffectiveModelImpl.props(nextGeneration(), newSnapshot));
+        if (currentInstance != null) {
+            currentInstance.dispose();
+        }
+        currentInstance = newInstance;
+        currentSnapshot = newSnapshot;
+    }
+
+    @Holding("this")
+    private long nextGeneration() {
+        return generation == -1 ? -1 : ++generation;
+    }
+}
index de1b4ad91490b2b1e9f72730a614c24b50a33043..f3d37c0422424836a1da4f6f515c0b977f74b05f 100644 (file)
  */
 package org.opendaylight.mdsal.dom.schema.osgi.impl;
 
-import static java.util.Objects.requireNonNull;
-
 import java.util.List;
-import java.util.NoSuchElementException;
-import org.checkerframework.checker.lock.qual.GuardedBy;
-import org.checkerframework.checker.lock.qual.Holding;
-import org.opendaylight.binding.runtime.api.ModuleInfoSnapshot;
-import org.opendaylight.binding.runtime.spi.ModuleInfoSnapshotBuilder;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.concepts.ObjectRegistration;
 import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
 import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory;
+import org.osgi.framework.BundleContext;
 import org.osgi.service.component.ComponentFactory;
-import org.osgi.service.component.ComponentInstance;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Update SchemaContext service in Service Registry each time new YangModuleInfo is added or removed.
  */
-final class YangModuleInfoRegistry {
-    private static final Logger LOG = LoggerFactory.getLogger(YangModuleInfoRegistry.class);
-
-    private final ComponentFactory contextFactory;
-    private final ModuleInfoSnapshotBuilder moduleInfoRegistry;
-
-    @GuardedBy("this")
-    private ComponentInstance currentInstance;
-    @GuardedBy("this")
-    private ModuleInfoSnapshot currentSnapshot;
-    @GuardedBy("this")
-    private int generation;
-
-    private volatile boolean ignoreScanner = true;
-
-    YangModuleInfoRegistry(final ComponentFactory contextFactory, final YangParserFactory factory) {
-        this.contextFactory = requireNonNull(contextFactory);
-        moduleInfoRegistry = new ModuleInfoSnapshotBuilder("binding-dom-codec", factory);
+abstract class YangModuleInfoRegistry {
+    static @NonNull YangModuleInfoRegistry create(final BundleContext ctx, final ComponentFactory contextFactory,
+            final YangParserFactory factory) {
+        return KarafFeaturesSupport.wrap(ctx, new RegularYangModuleInfoRegistry(contextFactory, factory));
     }
 
     // Invocation from scanner, we may want to ignore this in order to not process partial updates
-    void scannerUpdate() {
-        if (!ignoreScanner) {
-            synchronized (this) {
-                updateService();
-            }
-        }
-    }
-
-    synchronized void scannerShutdown() {
-        ignoreScanner = true;
-    }
+    abstract void scannerUpdate();
 
-    synchronized void enableScannerAndUpdate() {
-        ignoreScanner = false;
-        updateService();
-    }
-
-    synchronized void close() {
-        ignoreScanner = true;
-        if (currentInstance != null) {
-            currentInstance.dispose();
-            currentInstance = null;
-        }
-    }
-
-    List<ObjectRegistration<YangModuleInfo>> registerInfos(final List<YangModuleInfo> infos) {
-        return moduleInfoRegistry.registerModuleInfos(infos);
-    }
-
-    @Holding("this")
-    private void updateService() {
-        final ModuleInfoSnapshot newSnapshot;
-        try {
-            newSnapshot = moduleInfoRegistry.build();
-        } catch (NoSuchElementException e) {
-            LOG.debug("No snapshot available", e);
-            return;
-        }
-        if (newSnapshot.equals(currentSnapshot)) {
-            LOG.debug("No update to snapshot");
-            return;
-        }
+    abstract void scannerShutdown();
 
+    abstract void enableScannerAndUpdate();
 
-        final ComponentInstance newInstance = contextFactory.newInstance(
-            OSGiEffectiveModelImpl.props(nextGeneration(), newSnapshot));
-        if (currentInstance != null) {
-            currentInstance.dispose();
-        }
-        currentInstance = newInstance;
-        currentSnapshot = newSnapshot;
-    }
+    abstract void close();
 
-    @Holding("this")
-    private long nextGeneration() {
-        return generation == -1 ? -1 : ++generation;
-    }
+    abstract List<ObjectRegistration<YangModuleInfo>> registerInfos(List<YangModuleInfo> infos);
 }
index da2f5435aab2c8eaeac7540164b68db7e30d117c..96986a409e08b8fd675769923c63a93554950704 100644 (file)
@@ -12,6 +12,7 @@ import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import org.apache.karaf.features.FeaturesService;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -39,6 +40,7 @@ public class OSGiModelRuntimeTest {
         target = new OSGiModelRuntime();
         target.parserFactory = parserFactory;
         target.contextFactory = contextFactory;
+        doReturn(null).when(bundleContext).getServiceReference(FeaturesService.class);
     }
 
     @After