You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by pd...@apache.org on 2013/12/14 08:40:09 UTC

svn commit: r1550897 - in /felix/trunk/dependencymanager/shell: ./ src/main/java/org/apache/felix/dm/shell/ src/test/ src/test/java/ src/test/java/org/ src/test/java/org/apache/ src/test/java/org/apache/felix/ src/test/java/org/apache/felix/dm/ src/tes...

Author: pderop
Date: Sat Dec 14 07:40:09 2013
New Revision: 1550897

URL: http://svn.apache.org/r1550897
Log:
FELIX-4352: Added the wtf enhancement (thanks Jago !). I think that service properties should be taken into account by the getRoot method.

Added:
    felix/trunk/dependencymanager/shell/src/main/java/org/apache/felix/dm/shell/ComponentId.java
    felix/trunk/dependencymanager/shell/src/test/
    felix/trunk/dependencymanager/shell/src/test/java/
    felix/trunk/dependencymanager/shell/src/test/java/org/
    felix/trunk/dependencymanager/shell/src/test/java/org/apache/
    felix/trunk/dependencymanager/shell/src/test/java/org/apache/felix/
    felix/trunk/dependencymanager/shell/src/test/java/org/apache/felix/dm/
    felix/trunk/dependencymanager/shell/src/test/java/org/apache/felix/dm/shell/
    felix/trunk/dependencymanager/shell/src/test/java/org/apache/felix/dm/shell/DMCommandTest.java
Modified:
    felix/trunk/dependencymanager/shell/changelog.txt
    felix/trunk/dependencymanager/shell/pom.xml
    felix/trunk/dependencymanager/shell/src/main/java/org/apache/felix/dm/shell/DMCommand.java

Modified: felix/trunk/dependencymanager/shell/changelog.txt
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/shell/changelog.txt?rev=1550897&r1=1550896&r2=1550897&view=diff
==============================================================================
--- felix/trunk/dependencymanager/shell/changelog.txt (original)
+++ felix/trunk/dependencymanager/shell/changelog.txt Sat Dec 14 07:40:09 2013
@@ -2,7 +2,7 @@ Latest version
 --------------
 
 FELIX-4294 - Dependency Manager Shell improvements
-
+FELIX-4352 - Extend shell command to give better insight in where the problem is
 
 Release 3.0.0
 -------------

Modified: felix/trunk/dependencymanager/shell/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/shell/pom.xml?rev=1550897&r1=1550896&r2=1550897&view=diff
==============================================================================
--- felix/trunk/dependencymanager/shell/pom.xml (original)
+++ felix/trunk/dependencymanager/shell/pom.xml Sat Dec 14 07:40:09 2013
@@ -53,6 +53,12 @@
             <artifactId>org.apache.felix.gogo.runtime</artifactId>
             <version>0.10.0</version>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <resources>

Added: felix/trunk/dependencymanager/shell/src/main/java/org/apache/felix/dm/shell/ComponentId.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/shell/src/main/java/org/apache/felix/dm/shell/ComponentId.java?rev=1550897&view=auto
==============================================================================
--- felix/trunk/dependencymanager/shell/src/main/java/org/apache/felix/dm/shell/ComponentId.java (added)
+++ felix/trunk/dependencymanager/shell/src/main/java/org/apache/felix/dm/shell/ComponentId.java Sat Dec 14 07:40:09 2013
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.shell;
+
+public class ComponentId implements Comparable<ComponentId> {
+    private final String name;
+    private final String type;
+    private final String bundleName;
+    
+    public ComponentId(String name, String type, String bundleName) {
+        super();
+        this.name = name;
+        this.type = type;
+        this.bundleName = bundleName;
+    }
+    
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+    
+    public String getBundleName() {
+        return bundleName;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((bundleName == null) ? 0 : bundleName.hashCode());
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ComponentId other = (ComponentId) obj;
+        if (bundleName == null) {
+            if (other.bundleName != null)
+                return false;
+        }
+        else if (!bundleName.equals(other.bundleName))
+            return false;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        }
+        else if (!name.equals(other.name))
+            return false;
+        if (type == null) {
+            if (other.type != null)
+                return false;
+        }
+        else if (!type.equals(other.type))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "ComponentId [name=" + name + ", type=" + type + ", bundleName=" + bundleName + "]";
+    }
+
+    public int compareTo(ComponentId o) {
+        return name.compareTo(o.name);
+    }
+    
+}

Modified: felix/trunk/dependencymanager/shell/src/main/java/org/apache/felix/dm/shell/DMCommand.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/shell/src/main/java/org/apache/felix/dm/shell/DMCommand.java?rev=1550897&r1=1550896&r2=1550897&view=diff
==============================================================================
--- felix/trunk/dependencymanager/shell/src/main/java/org/apache/felix/dm/shell/DMCommand.java (original)
+++ felix/trunk/dependencymanager/shell/src/main/java/org/apache/felix/dm/shell/DMCommand.java Sat Dec 14 07:40:09 2013
@@ -25,7 +25,9 @@ import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.StringTokenizer;
+import java.util.TreeSet;
 
 import org.apache.felix.dm.Component;
 import org.apache.felix.dm.ComponentDeclaration;
@@ -59,6 +61,16 @@ public class DMCommand {
     private static final DependencyManagerSorter SORTER = new DependencyManagerSorter();
 
     /**
+     * Constant used by the wtf command, when listing missing services.
+     */
+    private static final String SERVICE = "service";
+    
+    /**
+     * Constant used by the wtf command, when listing missing configurations.
+     */
+    private static final String CONFIGURATION = "configuration";
+
+    /**
      * Name of a specific gogo shell variable, which may be used to configure "compact" mode.
      * Example: g! dependencymanager.compact=true
      */
@@ -124,6 +136,10 @@ public class DMCommand {
             @Parameter(names = {"notavail", "na"}, presentValue = "true", absentValue = "false") 
             boolean notavail,
 
+            @Descriptor("Detects where are the root failures") 
+            @Parameter(names = {"wtf"}, presentValue = "true", absentValue = "false") 
+            boolean wtf,
+
             @Descriptor("Displays components statistics") 
             @Parameter(names = {"stats", "st"}, presentValue = "true", absentValue = "false") 
             boolean stats,
@@ -180,6 +196,11 @@ public class DMCommand {
             while (tok.hasMoreTokens()) {
                 bids.add(tok.nextToken());
             }
+            
+            if (wtf) {
+        	wtf();
+        	return;
+            }
 
             // lookup all dependency manager service components
             List<DependencyManager> managers = DependencyManager.getDependencyManagers();
@@ -472,6 +493,172 @@ public class DMCommand {
         return output.toString();
     }
 
+    protected void wtf() {
+        List<ComponentDeclaration> downComponents = getDependenciesThatAreDown();
+        if (downComponents.isEmpty()) {
+            System.out.println("No missing dependencies found.");
+        }
+        else {
+            System.out.println(downComponents.size() + " missing dependencies found.");
+            System.out.println("-------------------------------------");
+        }
+        listResolvedBundles();
+        listInstalledBundles();
+        Set<ComponentId> downComponentsRoot = getTheRootCouses(downComponents);
+        listAllMissingConfigurations(downComponentsRoot);
+        listAllMissingServices(downComponents, downComponentsRoot);
+    }
+
+    private Set<ComponentId> getTheRootCouses(List<ComponentDeclaration> downComponents) {
+        Set<ComponentId> downComponentsRoot = new TreeSet<ComponentId>();
+        for (ComponentDeclaration c : downComponents) {
+            ComponentId root = getRoot(downComponents, c);
+            if (root != null) {
+                downComponentsRoot.add(root);
+            }
+        }
+        return downComponentsRoot;
+    }
+
+    @SuppressWarnings("unchecked")
+    private List<ComponentDeclaration> getDependenciesThatAreDown() {
+        List<DependencyManager> dependencyManagers = DependencyManager.getDependencyManagers();
+        List<ComponentDeclaration> downComponents = new ArrayList<ComponentDeclaration>();
+        for (DependencyManager dm : dependencyManagers) {
+            List<ComponentDeclaration> components = dm.getComponents();
+            // first create a list of all down components
+            for (ComponentDeclaration c : components) {
+                if (c.getState() == ComponentDeclaration.STATE_UNREGISTERED) {
+                    downComponents.add(c);
+                }
+            }
+        }
+        return downComponents;
+    }
+
+    private void listResolvedBundles() {
+        boolean areResolved = false;
+        for (Bundle b : m_context.getBundles()) {
+            if (b.getState() == Bundle.RESOLVED && !isFragment(b)) {
+                areResolved = true;
+                break;
+            }
+        }
+        if (areResolved) {
+            System.out.println("Please note that the following bundles are in the RESOLVED state:");
+            for (Bundle b : m_context.getBundles()) {
+                if (b.getState() == Bundle.RESOLVED && !isFragment(b)) {
+                    System.out.println(" * [" + b.getBundleId() + "] " + b.getSymbolicName());
+                }
+            }
+        }
+    }
+    
+    private void listInstalledBundles() {
+        boolean areResolved = false;
+        for (Bundle b : m_context.getBundles()) {
+            if (b.getState() == Bundle.INSTALLED) {
+                areResolved = true;
+                break;
+            }
+        }
+        if (areResolved) {
+            System.out.println("Please note that the following bundles are in the INSTALLED state:");
+            for (Bundle b : m_context.getBundles()) {
+                if (b.getState() == Bundle.INSTALLED) {
+                    System.out.println(" * [" + b.getBundleId() + "] " + b.getSymbolicName());
+                }
+            }
+        }
+    }
+
+    private boolean isFragment(Bundle b) {
+        @SuppressWarnings("unchecked")
+        Dictionary<String, String> headers = b.getHeaders();
+        return headers.get("Fragment-Host") != null;
+    }
+
+    private void listAllMissingConfigurations(Set<ComponentId> downComponentsRoot) {
+        if (hasMissingType(downComponentsRoot, CONFIGURATION)) {
+            System.out.println("The following configuration(s) are missing: ");
+            for (ComponentId s : downComponentsRoot) {
+                if (CONFIGURATION.equals(s.getType())) {
+                    System.out.println(" * " + s.getName() + " for bundle " + s.getBundleName());
+                }
+            }
+        }
+    }
+
+    private void listAllMissingServices(List<ComponentDeclaration> downComponents, Set<ComponentId> downComponentsRoot) {
+        if (hasMissingType(downComponentsRoot, SERVICE)) {
+            System.out.println("The following service(s) are missing: ");
+            for (ComponentId s : downComponentsRoot) {
+                if (SERVICE.equals(s.getType())) {
+                    System.out.print(" * " + s.getName());
+                    ComponentDeclaration component = getComponentDeclaration(s.getName(), downComponents);
+                    if (component == null) {
+                        System.out.println(" is not found in the service registry");
+                    } else {
+                        ComponentDependencyDeclaration[] componentDependencies = component.getComponentDependencies();
+                        System.out.println(" and needs:");
+                        for (ComponentDependencyDeclaration cdd : componentDependencies) {
+                            if (cdd.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_REQUIRED) {
+                                System.out.println(cdd.getName());
+                            }
+                        }
+                        System.out.println(" to work");
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean hasMissingType(Set<ComponentId> downComponentsRoot, String type) {
+        for (ComponentId s : downComponentsRoot) {
+            if (type.equals(s.getType())) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    private ComponentId getRoot(List<ComponentDeclaration> downComponents, ComponentDeclaration c) {
+        ComponentDependencyDeclaration[] componentDependencies = c.getComponentDependencies();
+        int downDeps = 0;
+        for (ComponentDependencyDeclaration cdd : componentDependencies) {
+            if (cdd.getState() == ComponentDependencyDeclaration.STATE_UNAVAILABLE_REQUIRED) {
+                downDeps++;
+                ComponentDeclaration component = getComponentDeclaration(cdd.getName(), downComponents);
+                if (component == null) {
+                    String contextName = null;
+                    if (CONFIGURATION.equals(cdd.getType())) {
+                        contextName = c.getBundleContext().getBundle().getSymbolicName();
+                    }
+                    return new ComponentId(cdd.getName(), cdd.getType(), contextName);
+                }
+                return getRoot(downComponents, component);
+            }
+        }
+        if (downDeps > 0) {
+            return new ComponentId(c.getName(), SERVICE, c.getBundleContext().getBundle().getSymbolicName());
+        }
+        return null;
+    }
+    
+    private ComponentDeclaration getComponentDeclaration(String name, List<ComponentDeclaration> list) {
+        for (ComponentDeclaration c : list) {
+            String serviceName = c.getName();
+            int cuttOff = serviceName.indexOf("(");
+            if (cuttOff != -1) {
+                serviceName = serviceName.substring(0, cuttOff);
+            }
+            if (name.equals(serviceName)) {
+                return c;
+            }
+        }
+        return null;
+    }
+    
     public static class DependencyManagerSorter implements Comparator<DependencyManager> {
         public int compare(DependencyManager dm1, DependencyManager dm2) {
             long id1 = dm1.getBundleContext().getBundle().getBundleId();

Added: felix/trunk/dependencymanager/shell/src/test/java/org/apache/felix/dm/shell/DMCommandTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/dependencymanager/shell/src/test/java/org/apache/felix/dm/shell/DMCommandTest.java?rev=1550897&view=auto
==============================================================================
--- felix/trunk/dependencymanager/shell/src/test/java/org/apache/felix/dm/shell/DMCommandTest.java (added)
+++ felix/trunk/dependencymanager/shell/src/test/java/org/apache/felix/dm/shell/DMCommandTest.java Sat Dec 14 07:40:09 2013
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.shell;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Properties;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+public class DMCommandTest {
+    
+    /** Setup a ByteArrayOutputStream to capture the system out printlines */
+    private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+    private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
+    
+    private DependencyManager dm;
+    @Spy @InjectMocks private DMCommand dme;
+    @Mock private BundleContext m_bundleContext;
+
+    @SuppressWarnings("unchecked")
+    @Before
+    public void setUp() {
+        m_bundleContext = mock(BundleContext.class);
+        System.setOut(new PrintStream(outContent));
+        System.setErr(new PrintStream(errContent));
+        dm = new DependencyManager(m_bundleContext);
+        dme = new DMCommand(m_bundleContext);
+        DependencyManager.getDependencyManagers().add(dm);
+    }
+
+    @After
+    public void cleanUp() {
+        System.setOut(null);
+        System.setErr(null);
+        DependencyManager.getDependencyManagers().remove(dm);
+    }
+
+    @Test
+    public void testWithoutAnyDependcyManagersShouldNotCrash() {
+        setupEmptyBundles();
+        
+        dme.wtf();
+        assertEquals("No missing dependencies found.\n", outContent.toString());
+    }
+
+    @Test
+    public void testASingleComponentShouldNotRegisterAsFailure() {
+        setupEmptyBundles();
+        
+        dm.add(dm.createComponent()
+            .setImplementation(Object.class)
+            .setInterface(Object.class.getName(), null)
+            );
+        dme.wtf();
+        assertEquals("No missing dependencies found.\n", outContent.toString());
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testComponentThatDependsOnAOtheComponentShouldRegisterAsFailure() {
+        setupEmptyBundles();
+        DependencyManager dm = new DependencyManager(m_bundleContext);
+        DependencyManager.getDependencyManagers().add(dm);
+        
+        Component component = dm.createComponent()
+            .setImplementation(Object.class)
+            .setInterface(Object.class.getName(), null)
+            .add(dm.createServiceDependency().setService(Math.class).setRequired(true));
+        dm.add(component);
+        
+        dme.wtf();
+        String output = outContent.toString();
+        assertTrue(output.contains("1 missing"));
+        assertTrue(output.contains("java.lang.Math"));
+        
+        // remove the mess
+        dm.remove(component);
+    }
+    
+    @Test
+    public void testCanFindRootFailure() {
+        setupEmptyBundles();
+        
+        Component component1 = dm.createComponent()
+            .setImplementation(Object.class)
+            .setInterface(Object.class.getName(), null)
+            .add(dm.createServiceDependency().setService(Math.class).setRequired(true));
+        dm.add(component1);
+        
+        Component component2 = dm.createComponent()
+            .setImplementation(Math.class)
+            .setInterface(Math.class.getName(), null)
+            .add(dm.createServiceDependency().setService(String.class).setRequired(true));
+        dm.add(component2);
+        
+        dme.wtf();
+        String output = outContent.toString();
+        assertTrue(output.contains("2 missing"));
+        assertTrue(output.contains("java.lang.String"));
+        
+        // remove the mess
+        dm.remove(component1);
+        dm.remove(component2);
+    }
+    
+    @Test
+    public void testInstalledBundleListing() {
+        Bundle bundle1 = mock(Bundle.class);
+        when(bundle1.getState()).thenReturn(Bundle.INSTALLED);
+        when(bundle1.getSymbolicName()).thenReturn("BadBundle");
+        
+        setupBundles(bundle1);
+        
+        dme.wtf();
+        String output = outContent.toString();
+        assertTrue(output.contains("following bundles are in the INSTALLED"));
+        assertTrue(output.contains("[0] BadBundle"));
+        // Will print null if it gets bundle 2, that should not happen
+        assertFalse(output.contains("null"));
+    }
+    
+    @Test
+    public void testResolvedBundleListing() {
+        Bundle bundle1 = mock(Bundle.class);
+        when(bundle1.getState()).thenReturn(Bundle.RESOLVED);
+        when(bundle1.getSymbolicName()).thenReturn("BadBundle");
+        Properties headers = new Properties();
+        when(bundle1.getHeaders()).thenReturn(headers);
+        
+        setupBundles(bundle1);
+        
+        dme.wtf();
+        String output = outContent.toString();
+        assertTrue(output.contains("following bundles are in the RESOLVED"));
+        assertTrue(output.contains("[0] BadBundle"));
+        assertFalse(output.contains("null"));
+    }
+    
+    @Test
+    public void testResolvedBundleListingButNoFragements() {
+        Bundle bundle1 = mock(Bundle.class);
+        when(bundle1.getState()).thenReturn(Bundle.RESOLVED);
+        when(bundle1.getSymbolicName()).thenReturn("BadBundle");
+        Properties headers = new Properties();
+        headers.put("Fragment-Host", "some value");
+        when(bundle1.getHeaders()).thenReturn(headers);
+        setupBundles(bundle1);
+        
+        dme.wtf();
+        String output = outContent.toString();
+        assertFalse(output.contains("following bundles are in the RESOLVED"));
+        // Will print null if it gets bundle 2, that should not happen
+        assertFalse(output.contains("null"));
+    }
+
+    private void setupBundles( Bundle bundle1) {
+        Bundle bundle2 = mock(Bundle.class);
+        when(bundle2.getState()).thenReturn(Bundle.ACTIVE);
+        
+        when(m_bundleContext.getBundles()).thenReturn(new Bundle[] { bundle1, bundle2});
+    }
+    
+    /** Sets up the bundle context without any bundles */
+    private void setupEmptyBundles() {
+        when(m_bundleContext.getBundles()).thenReturn(new Bundle[] {});
+    }
+
+}