blob: 4ead980f72d1db5b9416266953ad32d34697b1ab [file] [log] [blame]
Thomas Vachuska7d693f52014-10-21 19:17:57 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2014-present Open Networking Foundation
Thomas Vachuska7d693f52014-10-21 19:17:57 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
Thomas Vachuska7d693f52014-10-21 19:17:57 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
Thomas Vachuska7d693f52014-10-21 19:17:57 -070015 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.cli.net;
Brian O'Connora4cab072014-10-03 18:46:39 -070017
Carolina Fernandez0b1449d2016-12-04 13:30:36 +010018import com.google.common.collect.ImmutableList;
Brian O'Connora4cab072014-10-03 18:46:39 -070019import org.apache.karaf.shell.commands.Argument;
20import org.apache.karaf.shell.commands.Command;
Brian O'Connor8016f342015-02-24 17:00:39 -080021import org.apache.karaf.shell.commands.Option;
Brian O'Connorabafb502014-12-02 22:26:20 -080022import org.onosproject.cli.AbstractShellCommand;
Ray Milkey02479862015-02-17 17:02:19 -080023import org.onosproject.core.ApplicationId;
24import org.onosproject.core.CoreService;
Brian O'Connorabafb502014-12-02 22:26:20 -080025import org.onosproject.net.intent.Intent;
suibin zhangbaa163f2015-08-06 09:22:24 -070026import org.onosproject.net.intent.IntentEvent;
Carolina Fernandez0b1449d2016-12-04 13:30:36 +010027import org.onosproject.net.intent.IntentListener;
28import org.onosproject.net.intent.IntentService;
29import org.onosproject.net.intent.IntentState;
Ray Milkeyf9af43c2015-02-09 16:45:48 -080030import org.onosproject.net.intent.Key;
Brian O'Connora4cab072014-10-03 18:46:39 -070031
Carolina Fernandez0b1449d2016-12-04 13:30:36 +010032import java.io.BufferedReader;
33import java.io.IOException;
34import java.io.InputStreamReader;
Thomas Vachuska4926c1b2014-10-21 00:44:10 -070035import java.math.BigInteger;
suibin zhangbaa163f2015-08-06 09:22:24 -070036import java.util.EnumSet;
Brian O'Connor8016f342015-02-24 17:00:39 -080037import java.util.Objects;
38import java.util.concurrent.CountDownLatch;
39import java.util.concurrent.TimeUnit;
40
Thomas Vachuska33937c42015-07-15 17:40:32 -070041import static com.google.common.base.Strings.isNullOrEmpty;
Brian O'Connor8016f342015-02-24 17:00:39 -080042import static org.onosproject.net.intent.IntentState.FAILED;
43import static org.onosproject.net.intent.IntentState.WITHDRAWN;
Thomas Vachuska4926c1b2014-10-21 00:44:10 -070044
Brian O'Connora4cab072014-10-03 18:46:39 -070045/**
Ray Milkey02479862015-02-17 17:02:19 -080046 * Removes an intent.
Brian O'Connora4cab072014-10-03 18:46:39 -070047 */
tom6db1f0a2014-10-07 09:12:29 -070048@Command(scope = "onos", name = "remove-intent",
Thomas Vachuska33937c42015-07-15 17:40:32 -070049 description = "Removes the specified intent")
tom6db1f0a2014-10-07 09:12:29 -070050public class IntentRemoveCommand extends AbstractShellCommand {
Brian O'Connora4cab072014-10-03 18:46:39 -070051
Ray Milkey02479862015-02-17 17:02:19 -080052 @Argument(index = 0, name = "app",
Thomas Vachuska33937c42015-07-15 17:40:32 -070053 description = "Application ID",
54 required = false, multiValued = false)
Ray Milkey02479862015-02-17 17:02:19 -080055 String applicationIdString = null;
56
Brian O'Connor422b8752015-06-04 16:16:49 -070057 @Argument(index = 1, name = "key",
Thomas Vachuska33937c42015-07-15 17:40:32 -070058 description = "Intent Key",
59 required = false, multiValued = false)
Brian O'Connor422b8752015-06-04 16:16:49 -070060 String keyString = null;
Brian O'Connora4cab072014-10-03 18:46:39 -070061
Brian O'Connor8016f342015-02-24 17:00:39 -080062 @Option(name = "-p", aliases = "--purge",
63 description = "Purge the intent from the store after removal",
64 required = false, multiValued = false)
65 private boolean purgeAfterRemove = false;
66
67 @Option(name = "-s", aliases = "--sync",
68 description = "Waits for the removal before returning",
69 required = false, multiValued = false)
70 private boolean sync = false;
71
suibin zhangbaa163f2015-08-06 09:22:24 -070072 private static final EnumSet<IntentState> CAN_PURGE = EnumSet.of(WITHDRAWN, FAILED);
73
Brian O'Connora4cab072014-10-03 18:46:39 -070074 @Override
75 protected void execute() {
Ray Milkey02479862015-02-17 17:02:19 -080076 IntentService intentService = get(IntentService.class);
Carolina Fernandez0b1449d2016-12-04 13:30:36 +010077 removeIntent(intentService.getIntents(),
78 applicationIdString, keyString,
79 purgeAfterRemove, sync);
80 }
Ray Milkey02479862015-02-17 17:02:19 -080081
Carolina Fernandez0b1449d2016-12-04 13:30:36 +010082 /**
83 * Purges the intents passed as argument.
84 *
85 * @param intents list of intents to purge
86 */
87 private void purgeIntents(Iterable<Intent> intents) {
88 IntentService intentService = get(IntentService.class);
89 this.purgeAfterRemove = true;
90 removeIntentsByAppId(intentService, intents, null);
91 }
92
93 /**
94 * Purges the intents passed as argument after confirmation is provided
95 * for each of them.
96 * If no explicit confirmation is provided, the intent is not purged.
97 *
98 * @param intents list of intents to purge
99 */
100 public void purgeIntentsInteractive(Iterable<Intent> intents) {
101 BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
102 intents.forEach(intent -> {
103 System.out.print(String.format("Id=%s, Key=%s, AppId=%s. Remove? [y/N]: ",
104 intent.id(), intent.key(), intent.appId().name()));
105 String response;
106 try {
107 response = br.readLine();
108 response = response.trim().replace("\n", "");
Jon Halla3fcf672017-03-28 16:53:22 -0700109 if ("y".equals(response)) {
Carolina Fernandez0b1449d2016-12-04 13:30:36 +0100110 this.purgeIntents(ImmutableList.of(intent));
111 }
112 } catch (IOException e) {
113 response = "";
114 }
115 print(response);
116 });
117 }
118
119 /**
120 * Removes the intents passed as argument, assuming these
121 * belong to the application's ID provided (if any) and
122 * contain a key string.
123 *
124 * If an application ID is provided, it will be used to further
125 * filter the intents to be removed.
126 *
127 * @param intents list of intents to remove
128 * @param applicationIdString application ID to filter intents
129 * @param keyString string to filter intents
130 * @param purgeAfterRemove states whether the intents should be also purged
131 * @param sync states whether the cli should wait for the operation to finish
132 * before returning
133 */
134 private void removeIntent(Iterable<Intent> intents,
135 String applicationIdString, String keyString,
136 boolean purgeAfterRemove, boolean sync) {
137 IntentService intentService = get(IntentService.class);
138 CoreService coreService = get(CoreService.class);
139 this.applicationIdString = applicationIdString;
140 this.keyString = keyString;
141 this.purgeAfterRemove = purgeAfterRemove;
142 this.sync = sync;
suibin zhang5e897b72015-08-06 10:02:18 -0700143 if (purgeAfterRemove || sync) {
144 print("Using \"sync\" to remove/purge intents - this may take a while...");
145 print("Check \"summary\" to see remove/purge progress.");
suibin zhangbaa163f2015-08-06 09:22:24 -0700146 }
147
Ray Milkey02479862015-02-17 17:02:19 -0800148 ApplicationId appId = appId();
Thomas Vachuska33937c42015-07-15 17:40:32 -0700149 if (!isNullOrEmpty(applicationIdString)) {
Ray Milkey02479862015-02-17 17:02:19 -0800150 appId = coreService.getAppId(applicationIdString);
151 if (appId == null) {
152 print("Cannot find application Id %s", applicationIdString);
153 return;
154 }
155 }
Brian O'Connora4cab072014-10-03 18:46:39 -0700156
Thomas Vachuska33937c42015-07-15 17:40:32 -0700157 if (isNullOrEmpty(keyString)) {
Carolina Fernandez0b1449d2016-12-04 13:30:36 +0100158 removeIntentsByAppId(intentService, intents, appId);
Brian O'Connor8016f342015-02-24 17:00:39 -0800159
Thomas Vachuska33937c42015-07-15 17:40:32 -0700160 } else {
161 final Key key;
162 if (keyString.startsWith("0x")) {
163 // The intent uses a LongKey
164 keyString = keyString.replaceFirst("0x", "");
165 key = Key.of(new BigInteger(keyString, 16).longValue(), appId);
166 } else {
167 // The intent uses a StringKey
168 key = Key.of(keyString, appId);
169 }
Brian O'Connor8016f342015-02-24 17:00:39 -0800170
Thomas Vachuska33937c42015-07-15 17:40:32 -0700171 Intent intent = intentService.getIntent(key);
172 if (intent != null) {
173 removeIntent(intentService, intent);
174 }
175 }
176 }
177
Carolina Fernandez0b1449d2016-12-04 13:30:36 +0100178 /**
179 * Removes the intents passed as argument.
180 *
181 * If an application ID is provided, it will be used to further
182 * filter the intents to be removed.
183 *
184 * @param intentService IntentService object
185 * @param intents intents to remove
186 * @param appId application ID to filter intents
187 */
188 private void removeIntentsByAppId(IntentService intentService,
189 Iterable<Intent> intents,
190 ApplicationId appId) {
191 for (Intent intent : intents) {
192 if (appId == null || intent.appId().equals(appId)) {
193 removeIntent(intentService, intent);
194 }
195 }
196 }
197
198 /**
199 * Removes the intent passed as argument.
200 *
201 * @param intentService IntentService object
202 * @param intent intent to remove
203 */
Thomas Vachuska33937c42015-07-15 17:40:32 -0700204 private void removeIntent(IntentService intentService, Intent intent) {
205 IntentListener listener = null;
206 Key key = intent.key();
207 final CountDownLatch withdrawLatch, purgeLatch;
208 if (purgeAfterRemove || sync) {
209 // set up latch and listener to track uninstall progress
210 withdrawLatch = new CountDownLatch(1);
211 purgeLatch = purgeAfterRemove ? new CountDownLatch(1) : null;
212 listener = (IntentEvent event) -> {
213 if (Objects.equals(event.subject().key(), key)) {
214 if (event.type() == IntentEvent.Type.WITHDRAWN ||
215 event.type() == IntentEvent.Type.FAILED) {
216 withdrawLatch.countDown();
217 } else if (purgeAfterRemove &&
218 event.type() == IntentEvent.Type.PURGED) {
219 purgeLatch.countDown();
220 }
Brian O'Connor8016f342015-02-24 17:00:39 -0800221 }
Thomas Vachuska33937c42015-07-15 17:40:32 -0700222 };
223 intentService.addListener(listener);
224 } else {
225 purgeLatch = null;
226 withdrawLatch = null;
227 }
228
229 // request the withdraw
230 intentService.withdraw(intent);
231
232 if (purgeAfterRemove || sync) {
233 try { // wait for withdraw event
234 withdrawLatch.await(5, TimeUnit.SECONDS);
235 } catch (InterruptedException e) {
236 print("Timed out waiting for intent {} withdraw", key);
237 }
suibin zhangbaa163f2015-08-06 09:22:24 -0700238 if (purgeAfterRemove && CAN_PURGE.contains(intentService.getIntentState(key))) {
Thomas Vachuska33937c42015-07-15 17:40:32 -0700239 intentService.purge(intent);
suibin zhangbaa163f2015-08-06 09:22:24 -0700240 if (sync) { // wait for purge event
Ray Milkey8c6d00e2015-03-13 14:14:34 -0700241 /* TODO
242 Technically, the event comes before map.remove() is called.
243 If we depend on sync and purge working together, we will
244 need to address this.
245 */
suibin zhangbaa163f2015-08-06 09:22:24 -0700246 try {
247 purgeLatch.await(5, TimeUnit.SECONDS);
248 } catch (InterruptedException e) {
249 print("Timed out waiting for intent {} purge", key);
250 }
Brian O'Connor8016f342015-02-24 17:00:39 -0800251 }
252 }
Thomas Vachuska33937c42015-07-15 17:40:32 -0700253 }
Brian O'Connor8016f342015-02-24 17:00:39 -0800254
Thomas Vachuska33937c42015-07-15 17:40:32 -0700255 if (listener != null) {
256 // clean up the listener
257 intentService.removeListener(listener);
Ray Milkey8c6d00e2015-03-13 14:14:34 -0700258 }
Brian O'Connora4cab072014-10-03 18:46:39 -0700259 }
260}