View Javadoc

1   /*
2    * $Id: DeploymentServiceTestCase.java 22377 2011-07-11 12:41:42Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.module.launcher;
12  
13  import org.mule.MuleCoreExtension;
14  import org.mule.api.MuleContext;
15  import org.mule.api.component.JavaComponent;
16  import org.mule.api.config.MuleProperties;
17  import org.mule.api.construct.FlowConstruct;
18  import org.mule.api.registry.MuleRegistry;
19  import org.mule.construct.SimpleService;
20  import org.mule.module.launcher.application.Application;
21  import org.mule.module.launcher.application.ApplicationWrapper;
22  import org.mule.module.launcher.application.PriviledgedMuleApplication;
23  import org.mule.tck.junit4.AbstractMuleContextTestCase;
24  import org.mule.util.CollectionUtils;
25  import org.mule.util.FileUtils;
26  import org.mule.util.StringUtils;
27  import org.mule.util.concurrent.Latch;
28  
29  import java.io.File;
30  import java.io.IOException;
31  import java.net.URL;
32  import java.net.URLDecoder;
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.concurrent.TimeUnit;
39  
40  import org.apache.commons.io.filefilter.DirectoryFileFilter;
41  import org.apache.commons.io.filefilter.SuffixFileFilter;
42  import org.junit.Test;
43  
44  import static org.junit.Assert.assertArrayEquals;
45  import static org.junit.Assert.assertEquals;
46  import static org.junit.Assert.assertFalse;
47  import static org.junit.Assert.assertNotNull;
48  import static org.junit.Assert.assertNotSame;
49  import static org.junit.Assert.assertNull;
50  import static org.junit.Assert.assertTrue;
51  
52  public class DeploymentServiceTestCase extends AbstractMuleContextTestCase
53  {
54      protected static final int LATCH_TIMEOUT = 10000;
55      protected static final String[] NONE = new String[0];
56  
57      protected File muleHome;
58      protected File appsDir;
59      protected DeploymentService deploymentService;
60      // these latches are re-created during the test, thus need to be declared volatile
61      protected volatile Latch deployLatch;
62      protected volatile Latch installLatch;
63      protected volatile Latch undeployLatch;
64  
65      @Override
66      protected void doSetUp() throws Exception
67      {
68          super.doSetUp();
69          // set up some mule home structure
70          final String tmpDir = System.getProperty("java.io.tmpdir");
71          muleHome = new File(tmpDir, getClass().getSimpleName() + System.currentTimeMillis());
72          appsDir = new File(muleHome, "apps");
73          appsDir.mkdirs();
74          System.setProperty(MuleProperties.MULE_HOME_DIRECTORY_PROPERTY, muleHome.getCanonicalPath());
75  
76          new File(muleHome, "lib/shared/default").mkdirs();
77  
78          deploymentService = new DeploymentService(new HashMap<Class<? extends MuleCoreExtension>, MuleCoreExtension>());
79          deploymentService.setDeployer(new TestDeployer());
80          installLatch = new Latch();
81          deployLatch = new Latch();
82          undeployLatch = new Latch();
83      }
84  
85      @Override
86      protected void doTearDown() throws Exception
87      {
88          // comment out the deletion to analyze results after test is done
89          FileUtils.deleteTree(muleHome);
90          if (deploymentService != null)
91          {
92              deploymentService.stop();
93          }
94          super.doTearDown();
95  
96          // this is a complex classloader setup and we can't reproduce standalone Mule 100%,
97          // so trick the next test method into thinking it's the first run, otherwise
98          // app resets CCL ref to null and breaks the next test
99          Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
100     }
101 
102     @Test
103     public void testPriviledgedApp() throws Exception
104     {
105         final URL url = getClass().getResource("/priviledged-dummy-app.zip");
106         assertNotNull("Test app file not found " + url, url);
107         addAppArchive(url);
108 
109         deploymentService.start();
110 
111         assertTrue("Deployer never invoked", deployLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
112 
113         assertAppsDir(NONE, new String[] {"priviledged-dummy-app"}, true);
114 
115         final Application app = findApp("priviledged-dummy-app", 1);
116         // now that we're sure it's the app we wanted, assert the registry has everything
117         // a 'privileged' app would have had
118         final Object obj = app.getMuleContext().getRegistry().lookupObject(PriviledgedMuleApplication.REGISTRY_KEY_DEPLOYMENT_SERVICE);
119         assertNotNull("Privileged objects have not been registered", obj);
120         assertTrue(((ApplicationWrapper) app).getDelegate() instanceof PriviledgedMuleApplication);
121     }
122 
123     @Test
124     public void testPriviledgedCrossAppAccess() throws Exception
125     {
126         URL url = getClass().getResource("/priviledged-dummy-app.zip");
127         assertNotNull("Test app file not found " + url, url);
128         addAppArchive(url);
129 
130         url = getClass().getResource("/dummy-app.zip");
131         assertNotNull("Test app file not found " + url, url);
132         addAppArchive(url);
133 
134         deploymentService.start();
135 
136         // a basic latch isn't ideal here, as there are 2 apps to deploy
137         assertTrue("Deployer never invoked", deployLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
138 
139         assertAppsDir(NONE, new String[] {"dummy-app", "priviledged-dummy-app"}, true);
140 
141         final Application privApp = findApp("priviledged-dummy-app", 2);
142         final Application dummyApp = findApp("dummy-app", 2);
143         assertTrue(((ApplicationWrapper) privApp).getDelegate() instanceof PriviledgedMuleApplication);
144 
145         final MuleContext muleContext1 = privApp.getMuleContext();
146         System.out.println("muleContext1 = " + muleContext1);
147         assertNotSame(muleContext1, muleContext);
148         assertNotSame(privApp.getDeploymentClassLoader(), dummyApp.getDeploymentClassLoader());
149         final Collection<FlowConstruct> flowConstructs = dummyApp.getMuleContext().getRegistry().lookupObjects(FlowConstruct.class);
150         assertFalse("No FlowConstructs found in the sibling app", flowConstructs.isEmpty());
151         FlowConstruct fc = flowConstructs.iterator().next();
152         assertTrue(fc instanceof SimpleService);
153         SimpleService service = (SimpleService) fc;
154         // note that we don't have this class available to this test directly
155         Class<?> clazz = ((JavaComponent) service.getComponent()).getObjectType();
156         assertEquals("Wrong component implementation class", "org.mule.module.launcher.EchoTest", clazz.getName());
157     }
158 
159     @Test
160     public void testDeployZipOnStartup() throws Exception
161     {
162         final URL url = getClass().getResource("/dummy-app.zip");
163         assertNotNull("Test app file not found " + url, url);
164         addAppArchive(url);
165 
166         deploymentService.start();
167 
168         assertTrue("Deployer never invoked", deployLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
169 
170         assertAppsDir(NONE, new String[] {"dummy-app"}, true);
171 
172         // just assert no privileged entries were put in the registry
173         final Application app = findApp("dummy-app", 1);
174         final MuleRegistry registry = app.getMuleContext().getRegistry();
175         final Object obj = registry.lookupObject(PriviledgedMuleApplication.REGISTRY_KEY_DEPLOYMENT_SERVICE);
176         assertNull(obj);
177         assertFalse(((ApplicationWrapper) app).getDelegate() instanceof PriviledgedMuleApplication);
178 
179         // mule-app.properties from the zip archive must have loaded properly
180         assertEquals("mule-app.properties should have been loaded.", "someValue", registry.get("myCustomProp"));
181     }
182 
183     @Test
184     public void testUpdateAppViaZip() throws Exception
185     {
186         final URL url = getClass().getResource("/dummy-app.zip");
187         assertNotNull("Test app file not found " + url, url);
188         addAppArchive(url);
189 
190         deploymentService.start();
191 
192         assertTrue("Deployer never invoked", deployLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
193         assertAppsDir(NONE, new String[] {"dummy-app"}, true);
194         assertEquals("Application has not been properly registered with Mule", 1, deploymentService.getApplications().size());
195 
196         // set up a new deployment latch (can't reuse the old one)
197         deployLatch = new Latch();
198         addAppArchive(url);
199         assertTrue("Undeploy never invoked", undeployLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
200         assertTrue("Deployer never invoked", deployLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
201         assertEquals("Application has not been properly registered with Mule", 1, deploymentService.getApplications().size());
202         assertAppsDir(NONE, new String[]{"dummy-app"}, true);
203     }
204 
205     @Test
206     public void testBrokenAppArchive() throws Exception
207     {
208         final URL url = getClass().getResource("/broken-app.zip");
209         assertNotNull("Test app file not found " + url, url);
210         addAppArchive(url);
211 
212         deploymentService.start();
213 
214         assertTrue("Install never invoked", installLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
215 
216         // Resets the latch to detect a new attempt to deploy the zip file
217         installLatch = new Latch();
218 
219         // let the file system's write-behind cache commit the delete operation?
220         Thread.sleep(1000);
221 
222         // zip stays intact, no app dir created
223         assertAppsDir(new String[] {"broken-app.zip"}, NONE, true);
224         // don't assert dir contents, we want to check internal deployer state next
225         assertAppsDir(NONE, new String[] {"dummy-app"}, false);
226         assertEquals("No apps should have been registered with Mule.", 0, deploymentService.getApplications().size());
227         final Map<URL, Long> zombieMap = deploymentService.getZombieMap();
228         assertEquals("Wrong number of zombie apps registered.", 1, zombieMap.size());
229         final Map.Entry<URL, Long> zombie = zombieMap.entrySet().iterator().next();
230         assertEquals("Wrong URL tagged as zombie.", "broken-app.zip", new File(zombie.getKey().getFile()).getName());
231         assertTrue("Invalid lastModified value for file URL.", zombie.getValue() != -1);
232 
233         // Checks that the invalid zip was not deployed again
234         assertFalse("Install was invoked again for the broken application file", installLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS));
235     }
236 
237     @Test
238     public void testBrokenAppName() throws Exception
239     {
240         final URL url = getClass().getResource("/app with spaces.zip");
241         assertNotNull("Test app file not found " + url, url);
242         addAppArchive(url);
243 
244         try
245         {
246             deploymentService.start();
247         }
248         catch (DeploymentInitException e)
249         {
250             assertTrue(e.getMessage().contains("may not contain spaces"));
251         }
252 
253         // zip stays intact, no app dir created
254         assertAppsDir(new String[] {"app with spaces.zip"}, NONE, true);
255         final Map<URL, Long> zombieMap = deploymentService.getZombieMap();
256         assertEquals("Wrong number of zombie apps registered.", 1, zombieMap.size());
257         final Map.Entry<URL, Long> zombie = zombieMap.entrySet().iterator().next();
258         assertEquals("Wrong URL tagged as zombie.", "app%20with%20spaces.zip", new File(zombie.getKey().getFile()).getName());
259         assertTrue("Invalid lastModified value for file URL.", zombie.getValue() != -1);
260     }
261 
262     /**
263      * Find a deployed app, performing some basic assertions.
264      */
265     private Application findApp(final String appName, int totalAppsExpected)
266     {
267         // list all apps to validate total count
268         final List<Application> apps = deploymentService.getApplications();
269         assertNotNull(apps);
270         assertEquals(totalAppsExpected, apps.size());
271         final Application app = deploymentService.findApplication(appName);
272         assertNotNull(app);
273         return app;
274     }
275 
276     private void assertAppsDir(String[] expectedZips, String[] expectedApps, boolean performValidation)
277     {
278         final String[] actualZips = appsDir.list(new SuffixFileFilter(".zip"));
279         if (performValidation) {
280             assertArrayEquals("Invalid Mule application archives set", expectedZips, actualZips);
281         }
282         final String[] actualApps = appsDir.list(DirectoryFileFilter.DIRECTORY);
283         if (performValidation) {
284             assertTrue("Invalid Mule exploded applications set",
285                        CollectionUtils.isEqualCollection(Arrays.asList(expectedApps), Arrays.asList(actualApps)));
286         }
287     }
288 
289     /**
290      * Copies a given app archive to the apps folder for deployment.
291      */
292     private void addAppArchive(URL url) throws IOException
293     {
294         // copy is not atomic, copy to a temp file and rename instead (rename is atomic)
295         final String tempFileName = new File(URLDecoder.decode(url.getFile()) + ".part").getName();
296         final File tempFile = new File(appsDir, tempFileName);
297         FileUtils.copyURLToFile(url, tempFile);
298         tempFile.renameTo(new File(StringUtils.removeEnd(tempFile.getAbsolutePath(), ".part")));
299     }
300 
301 
302     private class TestDeployer implements MuleDeployer
303     {
304         MuleDeployer delegate = new DefaultMuleDeployer(deploymentService);
305 
306         @Override
307         public void deploy(Application app)
308         {
309             System.out.println("DeploymentServiceTestCase$TestDeployer.deploy");
310             delegate.deploy(app);
311             deployLatch.release();
312         }
313 
314         @Override
315         public void undeploy(Application app)
316         {
317             System.out.println("DeploymentServiceTestCase$TestDeployer.undeploy");
318             delegate.undeploy(app);
319             undeployLatch.release();
320         }
321 
322         @Override
323         public Application installFromAppDir(String packedMuleAppFileName) throws IOException
324         {
325             installLatch.release();
326             System.out.println("DeploymentServiceTestCase$TestDeployer.installFromAppDir");
327             return delegate.installFromAppDir(packedMuleAppFileName);
328         }
329 
330         @Override
331         public Application installFrom(URL url) throws IOException
332         {
333             installLatch.release();
334             System.out.println("DeploymentServiceTestCase$TestDeployer.installFrom");
335             return delegate.installFrom(url);
336         }
337     }
338 }