Carmelo Cascone
Committed by Gerrit Code Review

Simplified Bmv2 device context service and context handling in demo apps

Change-Id: I2a13ed673902d0616732d43c841f50b1ad38cd4c
......@@ -56,9 +56,11 @@ import org.onosproject.net.topology.TopologyVertex;
import org.slf4j.Logger;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
......@@ -135,10 +137,13 @@ public abstract class AbstractUpgradableFabricApp {
private Set<DeviceId> spineSwitches;
private Map<DeviceId, List<FlowRule>> deviceFlowRules;
private Map<DeviceId, Boolean> rulesInstalled;
private Map<DeviceId, Boolean> contextFlags;
private Map<DeviceId, Boolean> ruleFlags;
private ConcurrentMap<DeviceId, Boolean> deployLocks = Maps.newConcurrentMap();
/**
* Creates a new Bmv2 Fabric Component.
* Creates a new BMv2 fabric app.
*
* @param appName app name
* @param configurationName a common name for the P4 program / BMv2 configuration used by this app
......@@ -212,7 +217,8 @@ public abstract class AbstractUpgradableFabricApp {
leafSwitches = Sets.newHashSet();
spineSwitches = Sets.newHashSet();
deviceFlowRules = Maps.newConcurrentMap();
rulesInstalled = Maps.newConcurrentMap();
ruleFlags = Maps.newConcurrentMap();
contextFlags = Maps.newConcurrentMap();
}
// Start flow rules generator...
......@@ -266,41 +272,19 @@ public abstract class AbstractUpgradableFabricApp {
private void deployRoutine() {
if (otherAppFound && otherApp.appActive) {
log.info("Starting update routine...");
updateRoutine();
log.info("Deactivating other app...");
appService.deactivate(otherApp.appId);
} else {
Stream.concat(leafSwitches.stream(), spineSwitches.stream())
.map(deviceService::getDevice)
.forEach(device -> spawnTask(() -> deployDevice(device)));
}
}
private void updateRoutine() {
Stream.concat(leafSwitches.stream(), spineSwitches.stream())
.forEach(did -> spawnTask(() -> {
cleanUpDevice(did);
try {
Thread.sleep(CLEANUP_SLEEP);
} catch (InterruptedException e) {
log.warn("Cleanup sleep interrupted!");
Thread.interrupted();
}
deployDevice(deviceService.getDevice(did));
}));
}
private void cleanUpDevice(DeviceId deviceId) {
List<FlowRule> flowRulesToRemove = Lists.newArrayList();
flowRuleService.getFlowEntries(deviceId).forEach(fe -> {
if (fe.appId() == otherApp.appId.id()) {
flowRulesToRemove.add(fe);
}
});
if (flowRulesToRemove.size() > 0) {
log.info("Cleaning {} old flow rules from {}...", flowRulesToRemove.size(), deviceId);
removeFlowRules(flowRulesToRemove);
}
Stream.concat(leafSwitches.stream(), spineSwitches.stream())
.map(deviceService::getDevice)
.forEach(device -> spawnTask(() -> deployDevice(device)));
}
/**
......@@ -309,36 +293,35 @@ public abstract class AbstractUpgradableFabricApp {
* @param device a device
*/
public void deployDevice(Device device) {
// Serialize executions per device ID using a concurrent map.
rulesInstalled.compute(device.id(), (did, deployed) -> {
Bmv2DeviceContext deviceContext = bmv2ContextService.getContext(device.id());
if (deviceContext == null) {
log.error("Unable to get context for device {}", device.id());
return deployed;
} else if (!deviceContext.equals(bmv2Context)) {
log.info("Swapping configuration to {} on device {}...", configurationName, device.id());
bmv2ContextService.triggerConfigurationSwap(device.id(), bmv2Context);
return deployed;
}
List<FlowRule> rules = deviceFlowRules.get(device.id());
if (initDevice(device.id())) {
if (deployed == null && rules != null && rules.size() > 0) {
log.info("Installing rules for {}...", did);
DeviceId deviceId = device.id();
// Synchronize executions over the same device.
deployLocks.putIfAbsent(deviceId, new Boolean(true));
synchronized (deployLocks.get(deviceId)) {
// Set context if not already done.
if (!contextFlags.getOrDefault(deviceId, false)) {
log.info("Setting context to {} for {}...", configurationName, deviceId);
bmv2ContextService.setContext(deviceId, bmv2Context);
contextFlags.put(device.id(), true);
}
// Initialize device.
if (!initDevice(deviceId)) {
log.warn("Failed to initialize device {}", deviceId);
}
// Install rules.
if (!ruleFlags.getOrDefault(deviceId, false)) {
List<FlowRule> rules = deviceFlowRules.getOrDefault(deviceId, Collections.emptyList());
if (rules.size() > 0) {
log.info("Installing rules for {}...", deviceId);
installFlowRules(rules);
return true;
ruleFlags.put(deviceId, true);
}
} else {
log.warn("Filed to initialize device {}", device.id());
if (deployed != null && rules != null && rules.size() > 0) {
log.info("Removing rules for {}...", did);
removeFlowRules(rules);
return null;
}
}
return deployed;
});
}
private void spawnTask(Runnable task) {
......
......@@ -164,7 +164,7 @@ public interface Bmv2DeviceAgent {
* @param jsonString a string value
* @throws Bmv2RuntimeException if any error occurs
*/
void loadNewJsonConfig(String jsonString) throws Bmv2RuntimeException;
void uploadNewJsonConfig(String jsonString) throws Bmv2RuntimeException;
/**
* Triggers a configuration swap on the device.
......
......@@ -25,12 +25,8 @@ import org.onosproject.net.DeviceId;
*/
public interface Bmv2DeviceContextService {
// TODO: handle the potential configuration states (e.g. RUNNING, SWAP_REQUESTED, etc.)
/**
* Returns the context of a given device. The context returned is the last one for which a configuration swap was
* triggered, hence there's no guarantees that the device is enforcing the returned context's configuration at the
* time of the call.
* Returns the context of the given device, null if no context has been previously set.
*
* @param deviceId a device ID
* @return a BMv2 device context
......@@ -38,12 +34,12 @@ public interface Bmv2DeviceContextService {
Bmv2DeviceContext getContext(DeviceId deviceId);
/**
* Triggers a configuration swap on a given device.
* Sets the context for the given device.
*
* @param deviceId a device ID
* @param context a BMv2 device context
*/
void triggerConfigurationSwap(DeviceId deviceId, Bmv2DeviceContext context);
void setContext(DeviceId deviceId, Bmv2DeviceContext context);
/**
* Binds the given interpreter with the given class loader so that other ONOS instances in the cluster can properly
......@@ -55,14 +51,9 @@ public interface Bmv2DeviceContextService {
void registerInterpreterClassLoader(Class<? extends Bmv2Interpreter> interpreterClass, ClassLoader loader);
/**
* Notifies this service that a given device has been updated, meaning a potential context change.
* It returns true if the device configuration is the same as the last for which a swap was triggered, false
* otherwise. In the last case, the service will asynchronously trigger a swap to the last
* configuration stored by this service. If no swap has already been triggered then a default configuration will be
* applied.
* Returns a default context.
*
* @param deviceId a device ID
* @return a boolean value
* @return a BMv2 device context
*/
boolean notifyDeviceChange(DeviceId deviceId);
Bmv2DeviceContext defaultContext();
}
......
......@@ -397,7 +397,7 @@ public final class Bmv2DeviceThriftClient implements Bmv2DeviceAgent {
}
@Override
public void loadNewJsonConfig(String jsonString) throws Bmv2RuntimeException {
public void uploadNewJsonConfig(String jsonString) throws Bmv2RuntimeException {
log.debug("Loading new JSON config on device... > deviceId={}, jsonStringLength={}",
deviceId, jsonString.length());
......
......@@ -185,9 +185,11 @@ public class Bmv2DeviceProvider extends AbstractDeviceProvider {
(!Objects.equals(thisDescription, lastDescription) ||
!Objects.equals(thisDescription.annotations(), lastDescription.annotations()));
if (descriptionChanged || !deviceService.isAvailable(did)) {
// Device description changed or device not available in the core.
if (contextService.notifyDeviceChange(did)) {
// Configuration is the expected one, we can proceed notifying the core.
if (contextService.getContext(did) == null) {
// Device is a first timer.
log.info("Setting DEFAULT context for {}", did);
contextService.setContext(did, contextService.defaultContext());
} else {
resetDeviceState(did);
initPortCounters(did);
providerService.deviceConnected(did, thisDescription);
......@@ -295,7 +297,7 @@ public class Bmv2DeviceProvider extends AbstractDeviceProvider {
if (cfg != null) {
try {
cfg.getDevicesInfo().stream().forEach(info -> {
// TODO: require also bmv2 internal device id from net-cfg (now is default 0)
// FIXME: require also bmv2 internal device id from net-cfg (now is default 0)
Bmv2Device bmv2Device = new Bmv2Device(info.ip().toString(), info.port(), 0);
triggerProbe(bmv2Device.asDeviceId());
});
......