blob: 6a9c8aa051f0e8bd40b5cad3d6236de4534c150f [file] [log] [blame]
/*
* Copyright 2014-present Open Networking Foundation
*
* Licensed 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.onosproject.cli.net;
import com.google.common.collect.ImmutableList;
import org.apache.karaf.shell.api.action.Argument;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.Completion;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.apache.karaf.shell.api.action.Option;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.cli.app.ApplicationIdWithIntentNameCompleter;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentEvent;
import org.onosproject.net.intent.IntentListener;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.intent.IntentState;
import org.onosproject.net.intent.Key;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.util.EnumSet;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onosproject.net.intent.IntentState.FAILED;
import static org.onosproject.net.intent.IntentState.WITHDRAWN;
/**
* Removes an intent.
*/
@Service
@Command(scope = "onos", name = "remove-intent",
description = "Removes the specified intent")
public class IntentRemoveCommand extends AbstractShellCommand {
@Argument(index = 0, name = "app",
description = "Application ID",
required = false, multiValued = false)
@Completion(ApplicationIdWithIntentNameCompleter.class)
String applicationIdString = null;
@Argument(index = 1, name = "key",
description = "Intent Key",
required = false, multiValued = false)
@Completion(IntentKeyCompleter.class)
String keyString = null;
@Option(name = "-p", aliases = "--purge",
description = "Purge the intent from the store after removal",
required = false, multiValued = false)
private boolean purgeAfterRemove = false;
@Option(name = "-s", aliases = "--sync",
description = "Waits for the removal before returning",
required = false, multiValued = false)
private boolean sync = false;
private static final EnumSet<IntentState> CAN_PURGE = EnumSet.of(WITHDRAWN, FAILED);
@Override
protected void doExecute() {
IntentService intentService = get(IntentService.class);
removeIntent(intentService.getIntents(),
applicationIdString, keyString,
purgeAfterRemove, sync);
}
/**
* Purges the intents passed as argument.
*
* @param intents list of intents to purge
*/
private void purgeIntents(Iterable<Intent> intents) {
IntentService intentService = get(IntentService.class);
this.purgeAfterRemove = true;
removeIntentsByAppId(intentService, intents, null);
}
/**
* Purges the intents passed as argument after confirmation is provided
* for each of them.
* If no explicit confirmation is provided, the intent is not purged.
*
* @param intents list of intents to purge
*/
public void purgeIntentsInteractive(Iterable<Intent> intents) {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
intents.forEach(intent -> {
System.out.print(String.format("Id=%s, Key=%s, AppId=%s. Remove? [y/N]: ",
intent.id(), intent.key(), intent.appId().name()));
String response;
try {
response = br.readLine();
response = response.trim().replace("\n", "");
if ("y".equals(response)) {
this.purgeIntents(ImmutableList.of(intent));
}
} catch (IOException e) {
response = "";
}
print(response);
});
}
/**
* Removes the intents passed as argument, assuming these
* belong to the application's ID provided (if any) and
* contain a key string.
*
* If an application ID is provided, it will be used to further
* filter the intents to be removed.
*
* @param intents list of intents to remove
* @param applicationIdString application ID to filter intents
* @param keyString string to filter intents
* @param purgeAfterRemove states whether the intents should be also purged
* @param sync states whether the cli should wait for the operation to finish
* before returning
*/
private void removeIntent(Iterable<Intent> intents,
String applicationIdString, String keyString,
boolean purgeAfterRemove, boolean sync) {
IntentService intentService = get(IntentService.class);
CoreService coreService = get(CoreService.class);
this.applicationIdString = applicationIdString;
this.keyString = keyString;
this.purgeAfterRemove = purgeAfterRemove;
this.sync = sync;
if (purgeAfterRemove || sync) {
print("Using \"sync\" to remove/purge intents - this may take a while...");
print("Check \"summary\" to see remove/purge progress.");
}
ApplicationId appId = appId();
if (!isNullOrEmpty(applicationIdString)) {
appId = coreService.getAppId(applicationIdString);
if (appId == null) {
print("Cannot find application Id %s", applicationIdString);
return;
}
}
if (isNullOrEmpty(keyString)) {
removeIntentsByAppId(intentService, intents, appId);
} else {
final Key key;
if (keyString.startsWith("0x")) {
// The intent uses a LongKey
keyString = keyString.replaceFirst("0x", "");
key = Key.of(new BigInteger(keyString, 16).longValue(), appId);
} else {
// The intent uses a StringKey
key = Key.of(keyString, appId);
}
Intent intent = intentService.getIntent(key);
if (intent != null) {
removeIntent(intentService, intent);
}
}
}
/**
* Removes the intents passed as argument.
*
* If an application ID is provided, it will be used to further
* filter the intents to be removed.
*
* @param intentService IntentService object
* @param intents intents to remove
* @param appId application ID to filter intents
*/
private void removeIntentsByAppId(IntentService intentService,
Iterable<Intent> intents,
ApplicationId appId) {
for (Intent intent : intents) {
if (appId == null || intent.appId().equals(appId)) {
removeIntent(intentService, intent);
}
}
}
/**
* Removes the intent passed as argument.
*
* @param intentService IntentService object
* @param intent intent to remove
*/
private void removeIntent(IntentService intentService, Intent intent) {
IntentListener listener = null;
Key key = intent.key();
final CountDownLatch withdrawLatch, purgeLatch;
if (purgeAfterRemove || sync) {
// set up latch and listener to track uninstall progress
withdrawLatch = new CountDownLatch(1);
purgeLatch = purgeAfterRemove ? new CountDownLatch(1) : null;
listener = (IntentEvent event) -> {
if (Objects.equals(event.subject().key(), key)) {
if (event.type() == IntentEvent.Type.WITHDRAWN ||
event.type() == IntentEvent.Type.FAILED) {
withdrawLatch.countDown();
} else if (purgeLatch != null && purgeAfterRemove &&
event.type() == IntentEvent.Type.PURGED) {
purgeLatch.countDown();
}
}
};
intentService.addListener(listener);
} else {
purgeLatch = null;
withdrawLatch = null;
}
// request the withdraw
intentService.withdraw(intent);
if (withdrawLatch != null && (purgeAfterRemove || sync)) {
try { // wait for withdraw event
withdrawLatch.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
print("Timed out waiting for intent {} withdraw", key);
}
if (purgeLatch != null && purgeAfterRemove && CAN_PURGE.contains(intentService.getIntentState(key))) {
intentService.purge(intent);
if (sync) { // wait for purge event
/* TODO
Technically, the event comes before map.remove() is called.
If we depend on sync and purge working together, we will
need to address this.
*/
try {
purgeLatch.await(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
print("Timed out waiting for intent {} purge", key);
}
}
}
}
if (listener != null) {
// clean up the listener
intentService.removeListener(listener);
}
}
}