Thomas Vachuska
Committed by Gerrit Code Review

Added ability to upload apps as both app.xml or app.zip.

Added a number of app.xml files for built-in apps.
Added ability to install & activate in one command.

Change-Id: I3fa5fa487ef76d9fe3da4d6dce8045d538cba423
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2015 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<app name="org.onosproject.app.config" origin="ON.Lab" version="1.1.0"
18 + features="onos-app-config">
19 + <description>ONOS network configuration application</description>
20 +</app>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2015 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<app name="org.onosproject.app.fwd" origin="ON.Lab" version="1.1.0"
18 + features="onos-app-fwd">
19 + <description>ONOS Reactive forwarding application using flow subsystem</description>
20 +</app>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2015 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<app name="org.onosproject.app.ifwd" origin="ON.Lab" version="1.1.0"
18 + features="onos-app-ifwd">
19 + <description>ONOS Reactive forwarding application using intent subsystem (experimental)</description>
20 +</app>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2015 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<app name="org.onosproject.app.metrics.intent" origin="ON.Lab" version="1.1.0"
18 + features="onos-app-metrics-intent">
19 + <description>ONOS intent metrics test application</description>
20 +</app>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2015 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<app name="org.onosproject.app.metrics.topology" origin="ON.Lab" version="1.1.0"
18 + features="onos-app-metrics-topology">
19 + <description>ONOS topology metrics test application</description>
20 +</app>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2015 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<app name="org.onosproject.app.optical" origin="ON.Lab" version="1.1.0"
18 + features="onos-app-sdnip">
19 + <description>ONOS Packet/Optical use-case application</description>
20 +</app>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2015 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<app name="org.onosproject.app.proxyarp" origin="ON.Lab" version="1.1.0"
18 + features="onos-app-proxyarp">
19 + <description>ONOS proxy ARP application</description>
20 +</app>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2015 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<app name="org.onosproject.app.sdnip" origin="ON.Lab" version="1.1.0"
18 + features="onos-app-sdnip">
19 + <description>ONOS SDN/IP use-case application</description>
20 +</app>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2015 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<app name="org.onosproject.app.tvue" origin="ON.Lab" version="1.1.0"
18 + features="onos-app-tvue">
19 + <description>Early prototype GUI (deprecated)</description>
20 +</app>
...@@ -29,7 +29,9 @@ public interface ApplicationAdminService extends ApplicationService { ...@@ -29,7 +29,9 @@ public interface ApplicationAdminService extends ApplicationService {
29 29
30 /** 30 /**
31 * Installs the application contained in the specified application archive 31 * Installs the application contained in the specified application archive
32 - * input stream. 32 + * input stream. This can be either a ZIP stream containing a compressed
33 + * application archive or a plain XML stream containing just the
34 + * {@code app.xml} application descriptor file.
33 * 35 *
34 * @param appDescStream application descriptor input stream 36 * @param appDescStream application descriptor input stream
35 * @return installed application descriptor 37 * @return installed application descriptor
......
...@@ -40,6 +40,7 @@ import java.io.FileNotFoundException; ...@@ -40,6 +40,7 @@ import java.io.FileNotFoundException;
40 import java.io.IOException; 40 import java.io.IOException;
41 import java.io.InputStream; 41 import java.io.InputStream;
42 import java.net.URI; 42 import java.net.URI;
43 +import java.nio.charset.Charset;
43 import java.nio.file.NoSuchFileException; 44 import java.nio.file.NoSuchFileException;
44 import java.util.List; 45 import java.util.List;
45 import java.util.Set; 46 import java.util.Set;
...@@ -57,6 +58,13 @@ import static com.google.common.io.Files.write; ...@@ -57,6 +58,13 @@ import static com.google.common.io.Files.write;
57 public class ApplicationArchive 58 public class ApplicationArchive
58 extends AbstractStore<ApplicationEvent, ApplicationStoreDelegate> { 59 extends AbstractStore<ApplicationEvent, ApplicationStoreDelegate> {
59 60
61 + // Magic strings to search for at the beginning of the archive stream
62 + private static final String XML_MAGIC = "<?xml ";
63 +
64 + // Magic strings to search for and how deep to search it into the archive stream
65 + private static final String APP_MAGIC = "<app ";
66 + private static final int APP_MAGIC_DEPTH = 1024;
67 +
60 private static final String NAME = "[@name]"; 68 private static final String NAME = "[@name]";
61 private static final String ORIGIN = "[@origin]"; 69 private static final String ORIGIN = "[@origin]";
62 private static final String VERSION = "[@version]"; 70 private static final String VERSION = "[@version]";
...@@ -144,13 +152,21 @@ public class ApplicationArchive ...@@ -144,13 +152,21 @@ public class ApplicationArchive
144 try (InputStream ais = stream) { 152 try (InputStream ais = stream) {
145 byte[] cache = toByteArray(ais); 153 byte[] cache = toByteArray(ais);
146 InputStream bis = new ByteArrayInputStream(cache); 154 InputStream bis = new ByteArrayInputStream(cache);
147 - ApplicationDescription desc = parseAppDescription(bis);
148 - bis.reset();
149 155
150 - expandApplication(bis, desc); 156 + boolean plainXml = isPlainXml(cache);
157 + ApplicationDescription desc = plainXml ?
158 + parsePlainAppDescription(bis) : parseZippedAppDescription(bis);
159 +
160 + if (plainXml) {
161 + expandPlainApplication(cache, desc);
162 + } else {
151 bis.reset(); 163 bis.reset();
164 + expandZippedApplication(bis, desc);
152 165
166 + bis.reset();
153 saveApplication(bis, desc); 167 saveApplication(bis, desc);
168 + }
169 +
154 installArtifacts(desc); 170 installArtifacts(desc);
155 return desc; 171 return desc;
156 } catch (IOException e) { 172 } catch (IOException e) {
...@@ -158,28 +174,45 @@ public class ApplicationArchive ...@@ -158,28 +174,45 @@ public class ApplicationArchive
158 } 174 }
159 } 175 }
160 176
177 + // Indicates whether the stream encoded in the given bytes is plain XML.
178 + private boolean isPlainXml(byte[] bytes) {
179 + return substring(bytes, XML_MAGIC.length()).equals(XML_MAGIC) ||
180 + substring(bytes, APP_MAGIC_DEPTH).contains(APP_MAGIC);
181 + }
182 +
183 + // Returns the substring of maximum possible length from the specified bytes.
184 + private String substring(byte[] bytes, int length) {
185 + return new String(bytes, 0, Math.min(bytes.length, length), Charset.forName("UTF-8"));
186 + }
187 +
161 /** 188 /**
162 * Purges the application archive directory. 189 * Purges the application archive directory.
163 * 190 *
164 * @param appName application name 191 * @param appName application name
165 */ 192 */
166 public void purgeApplication(String appName) { 193 public void purgeApplication(String appName) {
194 + File appDir = new File(appsDir, appName);
167 try { 195 try {
168 - Tools.removeDirectory(new File(appsDir, appName)); 196 + Tools.removeDirectory(appDir);
169 } catch (IOException e) { 197 } catch (IOException e) {
170 throw new ApplicationException("Unable to purge application " + appName, e); 198 throw new ApplicationException("Unable to purge application " + appName, e);
171 } 199 }
200 + if (appDir.exists()) {
201 + throw new ApplicationException("Unable to purge application " + appName);
202 + }
172 } 203 }
173 204
174 /** 205 /**
175 - * Returns application archive stream for the specified application. 206 + * Returns application archive stream for the specified application. This
207 + * will be either the application ZIP file or the application XML file.
176 * 208 *
177 * @param appName application name 209 * @param appName application name
178 * @return application archive stream 210 * @return application archive stream
179 */ 211 */
180 public InputStream getApplicationInputStream(String appName) { 212 public InputStream getApplicationInputStream(String appName) {
181 try { 213 try {
182 - return new FileInputStream(appFile(appName, appName + ".zip")); 214 + File appFile = appFile(appName, appName + ".zip");
215 + return new FileInputStream(appFile.exists() ? appFile : appFile(appName, APP_XML));
183 } catch (FileNotFoundException e) { 216 } catch (FileNotFoundException e) {
184 throw new ApplicationException("Application " + appName + " not found"); 217 throw new ApplicationException("Application " + appName + " not found");
185 } 218 }
...@@ -187,26 +220,32 @@ public class ApplicationArchive ...@@ -187,26 +220,32 @@ public class ApplicationArchive
187 220
188 // Scans the specified ZIP stream for app.xml entry and parses it producing 221 // Scans the specified ZIP stream for app.xml entry and parses it producing
189 // an application descriptor. 222 // an application descriptor.
190 - private ApplicationDescription parseAppDescription(InputStream stream) 223 + private ApplicationDescription parseZippedAppDescription(InputStream stream)
191 throws IOException { 224 throws IOException {
192 try (ZipInputStream zis = new ZipInputStream(stream)) { 225 try (ZipInputStream zis = new ZipInputStream(stream)) {
193 ZipEntry entry; 226 ZipEntry entry;
194 while ((entry = zis.getNextEntry()) != null) { 227 while ((entry = zis.getNextEntry()) != null) {
195 if (entry.getName().equals(APP_XML)) { 228 if (entry.getName().equals(APP_XML)) {
196 byte[] data = ByteStreams.toByteArray(zis); 229 byte[] data = ByteStreams.toByteArray(zis);
230 + return parsePlainAppDescription(new ByteArrayInputStream(data));
231 + }
232 + zis.closeEntry();
233 + }
234 + }
235 + throw new IOException("Unable to locate " + APP_XML);
236 + }
237 +
238 + // Scans the specified XML stream and parses it producing an application descriptor.
239 + private ApplicationDescription parsePlainAppDescription(InputStream stream)
240 + throws IOException {
197 XMLConfiguration cfg = new XMLConfiguration(); 241 XMLConfiguration cfg = new XMLConfiguration();
198 try { 242 try {
199 - cfg.load(new ByteArrayInputStream(data)); 243 + cfg.load(stream);
200 return loadAppDescription(cfg); 244 return loadAppDescription(cfg);
201 } catch (ConfigurationException e) { 245 } catch (ConfigurationException e) {
202 throw new IOException("Unable to parse " + APP_XML, e); 246 throw new IOException("Unable to parse " + APP_XML, e);
203 } 247 }
204 } 248 }
205 - zis.closeEntry();
206 - }
207 - }
208 - throw new IOException("Unable to locate " + APP_XML);
209 - }
210 249
211 private ApplicationDescription loadAppDescription(XMLConfiguration cfg) { 250 private ApplicationDescription loadAppDescription(XMLConfiguration cfg) {
212 cfg.setAttributeSplittingDisabled(true); 251 cfg.setAttributeSplittingDisabled(true);
...@@ -225,7 +264,7 @@ public class ApplicationArchive ...@@ -225,7 +264,7 @@ public class ApplicationArchive
225 } 264 }
226 265
227 // Expands the specified ZIP stream into app-specific directory. 266 // Expands the specified ZIP stream into app-specific directory.
228 - private void expandApplication(InputStream stream, ApplicationDescription desc) 267 + private void expandZippedApplication(InputStream stream, ApplicationDescription desc)
229 throws IOException { 268 throws IOException {
230 ZipInputStream zis = new ZipInputStream(stream); 269 ZipInputStream zis = new ZipInputStream(stream);
231 ZipEntry entry; 270 ZipEntry entry;
...@@ -234,7 +273,6 @@ public class ApplicationArchive ...@@ -234,7 +273,6 @@ public class ApplicationArchive
234 if (!entry.isDirectory()) { 273 if (!entry.isDirectory()) {
235 byte[] data = ByteStreams.toByteArray(zis); 274 byte[] data = ByteStreams.toByteArray(zis);
236 zis.closeEntry(); 275 zis.closeEntry();
237 -
238 File file = new File(appDir, entry.getName()); 276 File file = new File(appDir, entry.getName());
239 createParentDirs(file); 277 createParentDirs(file);
240 write(data, file); 278 write(data, file);
...@@ -243,6 +281,15 @@ public class ApplicationArchive ...@@ -243,6 +281,15 @@ public class ApplicationArchive
243 zis.close(); 281 zis.close();
244 } 282 }
245 283
284 + // Saves the specified XML stream into app-specific directory.
285 + private void expandPlainApplication(byte[] stream, ApplicationDescription desc)
286 + throws IOException {
287 + File file = appFile(desc.name(), APP_XML);
288 + createParentDirs(file);
289 + write(stream, file);
290 + }
291 +
292 +
246 // Saves the specified ZIP stream into a file under app-specific directory. 293 // Saves the specified ZIP stream into a file under app-specific directory.
247 private void saveApplication(InputStream stream, ApplicationDescription desc) 294 private void saveApplication(InputStream stream, ApplicationDescription desc)
248 throws IOException { 295 throws IOException {
......
...@@ -30,8 +30,7 @@ import java.io.InputStream; ...@@ -30,8 +30,7 @@ import java.io.InputStream;
30 import java.util.Random; 30 import java.util.Random;
31 import java.util.Set; 31 import java.util.Set;
32 32
33 -import static org.junit.Assert.assertArrayEquals; 33 +import static org.junit.Assert.*;
34 -import static org.junit.Assert.assertEquals;
35 import static org.onosproject.app.DefaultApplicationDescriptionTest.*; 34 import static org.onosproject.app.DefaultApplicationDescriptionTest.*;
36 35
37 public class ApplicationArchiveTest { 36 public class ApplicationArchiveTest {
...@@ -64,43 +63,69 @@ public class ApplicationArchiveTest { ...@@ -64,43 +63,69 @@ public class ApplicationArchiveTest {
64 } 63 }
65 64
66 @Test 65 @Test
67 - public void saveApp() throws IOException { 66 + public void saveZippedApp() throws IOException {
68 InputStream stream = getClass().getResourceAsStream("app.zip"); 67 InputStream stream = getClass().getResourceAsStream("app.zip");
69 ApplicationDescription app = aar.saveApplication(stream); 68 ApplicationDescription app = aar.saveApplication(stream);
70 validate(app); 69 validate(app);
71 } 70 }
72 71
73 @Test 72 @Test
73 + public void savePlainApp() throws IOException {
74 + InputStream stream = getClass().getResourceAsStream("app.xml");
75 + ApplicationDescription app = aar.saveApplication(stream);
76 + validate(app);
77 + }
78 +
79 + @Test
74 public void loadApp() throws IOException { 80 public void loadApp() throws IOException {
75 - saveApp(); 81 + saveZippedApp();
76 ApplicationDescription app = aar.getApplicationDescription(APP_NAME); 82 ApplicationDescription app = aar.getApplicationDescription(APP_NAME);
77 validate(app); 83 validate(app);
78 } 84 }
79 85
80 @Test 86 @Test
81 public void getAppNames() throws IOException { 87 public void getAppNames() throws IOException {
82 - saveApp(); 88 + saveZippedApp();
83 Set<String> names = aar.getApplicationNames(); 89 Set<String> names = aar.getApplicationNames();
84 assertEquals("incorrect names", ImmutableSet.of(APP_NAME), names); 90 assertEquals("incorrect names", ImmutableSet.of(APP_NAME), names);
85 } 91 }
86 92
87 @Test 93 @Test
88 public void purgeApp() throws IOException { 94 public void purgeApp() throws IOException {
89 - saveApp(); 95 + saveZippedApp();
90 aar.purgeApplication(APP_NAME); 96 aar.purgeApplication(APP_NAME);
91 assertEquals("incorrect names", ImmutableSet.<String>of(), 97 assertEquals("incorrect names", ImmutableSet.<String>of(),
92 aar.getApplicationNames()); 98 aar.getApplicationNames());
93 } 99 }
94 100
95 @Test 101 @Test
96 - public void getAppStream() throws IOException { 102 + public void getAppZipStream() throws IOException {
97 - saveApp(); 103 + saveZippedApp();
98 InputStream stream = aar.getApplicationInputStream(APP_NAME); 104 InputStream stream = aar.getApplicationInputStream(APP_NAME);
99 byte[] orig = ByteStreams.toByteArray(getClass().getResourceAsStream("app.zip")); 105 byte[] orig = ByteStreams.toByteArray(getClass().getResourceAsStream("app.zip"));
100 byte[] loaded = ByteStreams.toByteArray(stream); 106 byte[] loaded = ByteStreams.toByteArray(stream);
101 assertArrayEquals("incorrect stream", orig, loaded); 107 assertArrayEquals("incorrect stream", orig, loaded);
102 } 108 }
103 109
110 + @Test
111 + public void getAppXmlStream() throws IOException {
112 + savePlainApp();
113 + InputStream stream = aar.getApplicationInputStream(APP_NAME);
114 + byte[] orig = ByteStreams.toByteArray(getClass().getResourceAsStream("app.xml"));
115 + byte[] loaded = ByteStreams.toByteArray(stream);
116 + assertArrayEquals("incorrect stream", orig, loaded);
117 + }
118 +
119 + @Test
120 + public void active() throws IOException {
121 + savePlainApp();
122 + assertFalse("should not be active", aar.isActive(APP_NAME));
123 + aar.setActive(APP_NAME);
124 + assertTrue("should not be active", aar.isActive(APP_NAME));
125 + aar.clearActive(APP_NAME);
126 + assertFalse("should not be active", aar.isActive(APP_NAME));
127 + }
128 +
104 @Test(expected = ApplicationException.class) 129 @Test(expected = ApplicationException.class)
105 public void getBadAppDesc() throws IOException { 130 public void getBadAppDesc() throws IOException {
106 aar.getApplicationDescription("org.foo.BAD"); 131 aar.getApplicationDescription("org.foo.BAD");
...@@ -111,4 +136,14 @@ public class ApplicationArchiveTest { ...@@ -111,4 +136,14 @@ public class ApplicationArchiveTest {
111 aar.getApplicationInputStream("org.foo.BAD"); 136 aar.getApplicationInputStream("org.foo.BAD");
112 } 137 }
113 138
139 + @Test(expected = ApplicationException.class)
140 + public void setBadActive() throws IOException {
141 + aar.setActive("org.foo.BAD");
142 + }
143 +
144 + @Test(expected = ApplicationException.class)
145 + public void purgeBadApp() throws IOException {
146 + aar.purgeApplication("org.foo.BAD");
147 + }
148 +
114 } 149 }
...\ No newline at end of file ...\ No newline at end of file
......
1 +<?xml version="1.0" encoding="UTF-8"?>
1 <!-- 2 <!--
2 ~ Copyright 2015 Open Networking Laboratory 3 ~ Copyright 2015 Open Networking Laboratory
3 ~ 4 ~
......
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
16 --> 16 -->
17 <features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" 17 <features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
18 name="onos-@FEATURE-VERSION"> 18 name="onos-@FEATURE-VERSION">
19 - <repository>mvn:org.onosproject/onos-features/@ONOS-VERSION/xml/features</repository> 19 + <repository>mvn:org.onosproject/onos-features/@ONOS-VERSION/xml/features
20 + </repository>
20 21
21 <feature name="onos-thirdparty-base" version="@FEATURE-VERSION" 22 <feature name="onos-thirdparty-base" version="@FEATURE-VERSION"
22 description="ONOS 3rd party dependencies"> 23 description="ONOS 3rd party dependencies">
......
...@@ -14,7 +14,11 @@ export curl="curl -sS" ...@@ -14,7 +14,11 @@ export curl="curl -sS"
14 case $cmd in 14 case $cmd in
15 list) $curl -X GET $URL;; 15 list) $curl -X GET $URL;;
16 install) $curl -X POST $HDR $URL --data-binary @$app;; 16 install) $curl -X POST $HDR $URL --data-binary @$app;;
17 + install!) $curl -X POST $HDR $URL?activate=true --data-binary @$app;;
17 uninstall) $curl -X DELETE $URL/$app;; 18 uninstall) $curl -X DELETE $URL/$app;;
18 activate) $curl -X POST $URL/$app/active;; 19 activate) $curl -X POST $URL/$app/active;;
19 deactivate) $curl -X DELETE $URL/$app/active;; 20 deactivate) $curl -X DELETE $URL/$app/active;;
21 + *) echo "usage: onos-app {install|install!} <app-file>" >&2
22 + echo " onos-app {activate|deactivate|uninstall} <app-name>" >&2
23 + exit 1;;
20 esac 24 esac
......
...@@ -182,7 +182,11 @@ public abstract class Tools { ...@@ -182,7 +182,11 @@ public abstract class Tools {
182 * @throws java.io.IOException if unable to remove contents 182 * @throws java.io.IOException if unable to remove contents
183 */ 183 */
184 public static void removeDirectory(String path) throws IOException { 184 public static void removeDirectory(String path) throws IOException {
185 - walkFileTree(Paths.get(path), new DirectoryDeleter()); 185 + DirectoryDeleter visitor = new DirectoryDeleter();
186 + walkFileTree(Paths.get(path), visitor);
187 + if (visitor.exception != null) {
188 + throw visitor.exception;
189 + }
186 } 190 }
187 191
188 /** 192 /**
...@@ -194,11 +198,18 @@ public abstract class Tools { ...@@ -194,11 +198,18 @@ public abstract class Tools {
194 * @throws java.io.IOException if unable to remove contents 198 * @throws java.io.IOException if unable to remove contents
195 */ 199 */
196 public static void removeDirectory(File dir) throws IOException { 200 public static void removeDirectory(File dir) throws IOException {
197 - walkFileTree(Paths.get(dir.getAbsolutePath()), new DirectoryDeleter()); 201 + DirectoryDeleter visitor = new DirectoryDeleter();
202 + walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
203 + if (visitor.exception != null) {
204 + throw visitor.exception;
205 + }
198 } 206 }
199 207
200 - 208 + // Auxiliary path visitor for recursive directory structure removal.
201 private static class DirectoryDeleter extends SimpleFileVisitor<Path> { 209 private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
210 +
211 + private IOException exception;
212 +
202 @Override 213 @Override
203 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) 214 public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
204 throws IOException { 215 throws IOException {
...@@ -218,9 +229,8 @@ public abstract class Tools { ...@@ -218,9 +229,8 @@ public abstract class Tools {
218 @Override 229 @Override
219 public FileVisitResult visitFileFailed(Path file, IOException ioe) 230 public FileVisitResult visitFileFailed(Path file, IOException ioe)
220 throws IOException { 231 throws IOException {
221 - log.warn("Unable to delete file {}", file); 232 + this.exception = ioe;
222 - log.warn("Boom", ioe); 233 + return FileVisitResult.TERMINATE;
223 - return FileVisitResult.CONTINUE;
224 } 234 }
225 } 235 }
226 236
...@@ -253,8 +263,8 @@ public abstract class Tools { ...@@ -253,8 +263,8 @@ public abstract class Tools {
253 dst.getAbsolutePath())); 263 dst.getAbsolutePath()));
254 } 264 }
255 265
256 - 266 + // Auxiliary path visitor for recursive directory structure copying.
257 - public static class DirectoryCopier extends SimpleFileVisitor<Path> { 267 + private static class DirectoryCopier extends SimpleFileVisitor<Path> {
258 private Path src; 268 private Path src;
259 private Path dst; 269 private Path dst;
260 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING; 270 private StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
......
...@@ -21,11 +21,13 @@ import org.onosproject.core.ApplicationId; ...@@ -21,11 +21,13 @@ import org.onosproject.core.ApplicationId;
21 21
22 import javax.ws.rs.Consumes; 22 import javax.ws.rs.Consumes;
23 import javax.ws.rs.DELETE; 23 import javax.ws.rs.DELETE;
24 +import javax.ws.rs.DefaultValue;
24 import javax.ws.rs.GET; 25 import javax.ws.rs.GET;
25 import javax.ws.rs.POST; 26 import javax.ws.rs.POST;
26 import javax.ws.rs.Path; 27 import javax.ws.rs.Path;
27 import javax.ws.rs.PathParam; 28 import javax.ws.rs.PathParam;
28 import javax.ws.rs.Produces; 29 import javax.ws.rs.Produces;
30 +import javax.ws.rs.QueryParam;
29 import javax.ws.rs.core.MediaType; 31 import javax.ws.rs.core.MediaType;
30 import javax.ws.rs.core.Response; 32 import javax.ws.rs.core.Response;
31 import java.io.InputStream; 33 import java.io.InputStream;
...@@ -55,9 +57,14 @@ public class ApplicationsWebResource extends AbstractWebResource { ...@@ -55,9 +57,14 @@ public class ApplicationsWebResource extends AbstractWebResource {
55 @POST 57 @POST
56 @Consumes(MediaType.APPLICATION_OCTET_STREAM) 58 @Consumes(MediaType.APPLICATION_OCTET_STREAM)
57 @Produces(MediaType.APPLICATION_JSON) 59 @Produces(MediaType.APPLICATION_JSON)
58 - public Response installApplication(InputStream stream) { 60 + public Response installApplication(@QueryParam("activate")
61 + @DefaultValue("false") boolean activate,
62 + InputStream stream) {
59 ApplicationAdminService service = get(ApplicationAdminService.class); 63 ApplicationAdminService service = get(ApplicationAdminService.class);
60 Application app = service.install(stream); 64 Application app = service.install(stream);
65 + if (activate) {
66 + service.activate(app.id());
67 + }
61 return ok(codec(Application.class).encode(app, this)).build(); 68 return ok(codec(Application.class).encode(app, this)).build();
62 } 69 }
63 70
......