blob: 6a9c8aa051f0e8bd40b5cad3d6236de4534c150f [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;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070019import org.apache.karaf.shell.api.action.Argument;
20import org.apache.karaf.shell.api.action.Command;
Ray Milkey0068fd02018-10-11 15:45:39 -070021import org.apache.karaf.shell.api.action.Completion;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070022import org.apache.karaf.shell.api.action.lifecycle.Service;
23import org.apache.karaf.shell.api.action.Option;
Brian O'Connorabafb502014-12-02 22:26:20 -080024import org.onosproject.cli.AbstractShellCommand;
Ray Milkey0068fd02018-10-11 15:45:39 -070025import org.onosproject.cli.app.ApplicationIdWithIntentNameCompleter;
Ray Milkey02479862015-02-17 17:02:19 -080026import org.onosproject.core.ApplicationId;
27import org.onosproject.core.CoreService;
Brian O'Connorabafb502014-12-02 22:26:20 -080028import org.onosproject.net.intent.Intent;
suibin zhangbaa163f2015-08-06 09:22:24 -070029import org.onosproject.net.intent.IntentEvent;
Carolina Fernandez0b1449d2016-12-04 13:30:36 +010030import org.onosproject.net.intent.IntentListener;
31import org.onosproject.net.intent.IntentService;
32import org.onosproject.net.intent.IntentState;
Ray Milkeyf9af43c2015-02-09 16:45:48 -080033import org.onosproject.net.intent.Key;
Brian O'Connora4cab072014-10-03 18:46:39 -070034
Carolina Fernandez0b1449d2016-12-04 13:30:36 +010035import java.io.BufferedReader;
36import java.io.IOException;
37import java.io.InputStreamReader;
Thomas Vachuska4926c1b2014-10-21 00:44:10 -070038import java.math.BigInteger;
suibin zhangbaa163f2015-08-06 09:22:24 -070039import java.util.EnumSet;
Brian O'Connor8016f342015-02-24 17:00:39 -080040import java.util.Objects;
41import java.util.concurrent.CountDownLatch;
42import java.util.concurrent.TimeUnit;
43
Thomas Vachuska33937c42015-07-15 17:40:32 -070044import static com.google.common.base.Strings.isNullOrEmpty;
Brian O'Connor8016f342015-02-24 17:00:39 -080045import static org.onosproject.net.intent.IntentState.FAILED;
46import static org.onosproject.net.intent.IntentState.WITHDRAWN;
Thomas Vachuska4926c1b2014-10-21 00:44:10 -070047
Brian O'Connora4cab072014-10-03 18:46:39 -070048/**
Ray Milkey02479862015-02-17 17:02:19 -080049 * Removes an intent.
Brian O'Connora4cab072014-10-03 18:46:39 -070050 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -070051@Service
tom6db1f0a2014-10-07 09:12:29 -070052@Command(scope = "onos", name = "remove-intent",
Thomas Vachuska33937c42015-07-15 17:40:32 -070053 description = "Removes the specified intent")
tom6db1f0a2014-10-07 09:12:29 -070054public class IntentRemoveCommand extends AbstractShellCommand {
Brian O'Connora4cab072014-10-03 18:46:39 -070055
Ray Milkey02479862015-02-17 17:02:19 -080056 @Argument(index = 0, name = "app",
Thomas Vachuska33937c42015-07-15 17:40:32 -070057 description = "Application ID",
58 required = false, multiValued = false)
Ray Milkey0068fd02018-10-11 15:45:39 -070059 @Completion(ApplicationIdWithIntentNameCompleter.class)
Ray Milkey02479862015-02-17 17:02:19 -080060 String applicationIdString = null;
61
Brian O'Connor422b8752015-06-04 16:16:49 -070062 @Argument(index = 1, name = "key",
Thomas Vachuska33937c42015-07-15 17:40:32 -070063 description = "Intent Key",
64 required = false, multiValued = false)
Ray Milkey0068fd02018-10-11 15:45:39 -070065 @Completion(IntentKeyCompleter.class)
Brian O'Connor422b8752015-06-04 16:16:49 -070066 String keyString = null;
Brian O'Connora4cab072014-10-03 18:46:39 -070067
Brian O'Connor8016f342015-02-24 17:00:39 -080068 @Option(name = "-p", aliases = "--purge",
69 description = "Purge the intent from the store after removal",
70 required = false, multiValued = false)
71 private boolean purgeAfterRemove = false;
72
73 @Option(name = "-s", aliases = "--sync",
74 description = "Waits for the removal before returning",
75 required = false, multiValued = false)
76 private boolean sync = false;
77
suibin zhangbaa163f2015-08-06 09:22:24 -070078 private static final EnumSet<IntentState> CAN_PURGE = EnumSet.of(WITHDRAWN, FAILED);
79
Brian O'Connora4cab072014-10-03 18:46:39 -070080 @Override
Ray Milkeyd84f89b2018-08-17 14:54:17 -070081 protected void doExecute() {
Ray Milkey02479862015-02-17 17:02:19 -080082 IntentService intentService = get(IntentService.class);
Carolina Fernandez0b1449d2016-12-04 13:30:36 +010083 removeIntent(intentService.getIntents(),
84 applicationIdString, keyString,
85 purgeAfterRemove, sync);
86 }
Ray Milkey02479862015-02-17 17:02:19 -080087
Carolina Fernandez0b1449d2016-12-04 13:30:36 +010088 /**
89 * Purges the intents passed as argument.
90 *
91 * @param intents list of intents to purge
92 */
93 private void purgeIntents(Iterable<Intent> intents) {
94 IntentService intentService = get(IntentService.class);
95 this.purgeAfterRemove = true;
96 removeIntentsByAppId(intentService, intents, null);
97 }
98
99 /**
100 * Purges the intents passed as argument after confirmation is provided
101 * for each of them.
102 * If no explicit confirmation is provided, the intent is not purged.
103 *
104 * @param intents list of intents to purge
105 */
106 public void purgeIntentsInteractive(Iterable<Intent> intents) {
107 BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
108 intents.forEach(intent -> {
109 System.out.print(String.format("Id=%s, Key=%s, AppId=%s. Remove? [y/N]: ",
110 intent.id(), intent.key(), intent.appId().name()));
111 String response;
112 try {
113 response = br.readLine();
114 response = response.trim().replace("\n", "");
Jon Halla3fcf672017-03-28 16:53:22 -0700115 if ("y".equals(response)) {
Carolina Fernandez0b1449d2016-12-04 13:30:36 +0100116 this.purgeIntents(ImmutableList.of(intent));
117 }
118 } catch (IOException e) {
119 response = "";
120 }
121 print(response);
122 });
123 }
124
125 /**
126 * Removes the intents passed as argument, assuming these
127 * belong to the application's ID provided (if any) and
128 * contain a key string.
129 *
130 * If an application ID is provided, it will be used to further
131 * filter the intents to be removed.
132 *
133 * @param intents list of intents to remove
134 * @param applicationIdString application ID to filter intents
135 * @param keyString string to filter intents
136 * @param purgeAfterRemove states whether the intents should be also purged
137 * @param sync states whether the cli should wait for the operation to finish
138 * before returning
139 */
140 private void removeIntent(Iterable<Intent> intents,
141 String applicationIdString, String keyString,
142 boolean purgeAfterRemove, boolean sync) {
143 IntentService intentService = get(IntentService.class);
144 CoreService coreService = get(CoreService.class);
145 this.applicationIdString = applicationIdString;
146 this.keyString = keyString;
147 this.purgeAfterRemove = purgeAfterRemove;
148 this.sync = sync;
suibin zhang5e897b72015-08-06 10:02:18 -0700149 if (purgeAfterRemove || sync) {
150 print("Using \"sync\" to remove/purge intents - this may take a while...");
151 print("Check \"summary\" to see remove/purge progress.");
suibin zhangbaa163f2015-08-06 09:22:24 -0700152 }
153
Ray Milkey02479862015-02-17 17:02:19 -0800154 ApplicationId appId = appId();
Thomas Vachuska33937c42015-07-15 17:40:32 -0700155 if (!isNullOrEmpty(applicationIdString)) {
Ray Milkey02479862015-02-17 17:02:19 -0800156 appId = coreService.getAppId(applicationIdString);
157 if (appId == null) {
158 print("Cannot find application Id %s", applicationIdString);
159 return;
160 }
161 }
Brian O'Connora4cab072014-10-03 18:46:39 -0700162
Thomas Vachuska33937c42015-07-15 17:40:32 -0700163 if (isNullOrEmpty(keyString)) {
Carolina Fernandez0b1449d2016-12-04 13:30:36 +0100164 removeIntentsByAppId(intentService, intents, appId);
Brian O'Connor8016f342015-02-24 17:00:39 -0800165
Thomas Vachuska33937c42015-07-15 17:40:32 -0700166 } else {
167 final Key key;
168 if (keyString.startsWith("0x")) {
169 // The intent uses a LongKey
170 keyString = keyString.replaceFirst("0x", "");
171 key = Key.of(new BigInteger(keyString, 16).longValue(), appId);
172 } else {
173 // The intent uses a StringKey
174 key = Key.of(keyString, appId);
175 }
Brian O'Connor8016f342015-02-24 17:00:39 -0800176
Thomas Vachuska33937c42015-07-15 17:40:32 -0700177 Intent intent = intentService.getIntent(key);
178 if (intent != null) {
179 removeIntent(intentService, intent);
180 }
181 }
182 }
183
Carolina Fernandez0b1449d2016-12-04 13:30:36 +0100184 /**
185 * Removes the intents passed as argument.
186 *
187 * If an application ID is provided, it will be used to further
188 * filter the intents to be removed.
189 *
190 * @param intentService IntentService object
191 * @param intents intents to remove
192 * @param appId application ID to filter intents
193 */
194 private void removeIntentsByAppId(IntentService intentService,
195 Iterable<Intent> intents,
196 ApplicationId appId) {
197 for (Intent intent : intents) {
198 if (appId == null || intent.appId().equals(appId)) {
199 removeIntent(intentService, intent);
200 }
201 }
202 }
203
204 /**
205 * Removes the intent passed as argument.
206 *
207 * @param intentService IntentService object
208 * @param intent intent to remove
209 */
Thomas Vachuska33937c42015-07-15 17:40:32 -0700210 private void removeIntent(IntentService intentService, Intent intent) {
211 IntentListener listener = null;
212 Key key = intent.key();
213 final CountDownLatch withdrawLatch, purgeLatch;
214 if (purgeAfterRemove || sync) {
215 // set up latch and listener to track uninstall progress
216 withdrawLatch = new CountDownLatch(1);
217 purgeLatch = purgeAfterRemove ? new CountDownLatch(1) : null;
218 listener = (IntentEvent event) -> {
219 if (Objects.equals(event.subject().key(), key)) {
220 if (event.type() == IntentEvent.Type.WITHDRAWN ||
221 event.type() == IntentEvent.Type.FAILED) {
222 withdrawLatch.countDown();
Ray Milkey74e59132018-01-17 15:24:52 -0800223 } else if (purgeLatch != null && purgeAfterRemove &&
Thomas Vachuska33937c42015-07-15 17:40:32 -0700224 event.type() == IntentEvent.Type.PURGED) {
225 purgeLatch.countDown();
226 }
Brian O'Connor8016f342015-02-24 17:00:39 -0800227 }
Thomas Vachuska33937c42015-07-15 17:40:32 -0700228 };
229 intentService.addListener(listener);
230 } else {
231 purgeLatch = null;
232 withdrawLatch = null;
233 }
234
235 // request the withdraw
236 intentService.withdraw(intent);
237
Ray Milkey74e59132018-01-17 15:24:52 -0800238 if (withdrawLatch != null && (purgeAfterRemove || sync)) {
Thomas Vachuska33937c42015-07-15 17:40:32 -0700239 try { // wait for withdraw event
240 withdrawLatch.await(5, TimeUnit.SECONDS);
241 } catch (InterruptedException e) {
242 print("Timed out waiting for intent {} withdraw", key);
243 }
Ray Milkey74e59132018-01-17 15:24:52 -0800244 if (purgeLatch != null && purgeAfterRemove && CAN_PURGE.contains(intentService.getIntentState(key))) {
Thomas Vachuska33937c42015-07-15 17:40:32 -0700245 intentService.purge(intent);
suibin zhangbaa163f2015-08-06 09:22:24 -0700246 if (sync) { // wait for purge event
Ray Milkey8c6d00e2015-03-13 14:14:34 -0700247 /* TODO
248 Technically, the event comes before map.remove() is called.
249 If we depend on sync and purge working together, we will
250 need to address this.
251 */
suibin zhangbaa163f2015-08-06 09:22:24 -0700252 try {
253 purgeLatch.await(5, TimeUnit.SECONDS);
254 } catch (InterruptedException e) {
255 print("Timed out waiting for intent {} purge", key);
256 }
Brian O'Connor8016f342015-02-24 17:00:39 -0800257 }
258 }
Thomas Vachuska33937c42015-07-15 17:40:32 -0700259 }
Brian O'Connor8016f342015-02-24 17:00:39 -0800260
Thomas Vachuska33937c42015-07-15 17:40:32 -0700261 if (listener != null) {
262 // clean up the listener
263 intentService.removeListener(listener);
Ray Milkey8c6d00e2015-03-13 14:14:34 -0700264 }
Brian O'Connora4cab072014-10-03 18:46:39 -0700265 }
266}