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; ...@@ -56,9 +56,11 @@ import org.onosproject.net.topology.TopologyVertex;
56 import org.slf4j.Logger; 56 import org.slf4j.Logger;
57 57
58 import java.util.Collection; 58 import java.util.Collection;
59 +import java.util.Collections;
59 import java.util.List; 60 import java.util.List;
60 import java.util.Map; 61 import java.util.Map;
61 import java.util.Set; 62 import java.util.Set;
63 +import java.util.concurrent.ConcurrentMap;
62 import java.util.concurrent.ExecutorService; 64 import java.util.concurrent.ExecutorService;
63 import java.util.concurrent.Executors; 65 import java.util.concurrent.Executors;
64 import java.util.concurrent.TimeUnit; 66 import java.util.concurrent.TimeUnit;
...@@ -135,10 +137,13 @@ public abstract class AbstractUpgradableFabricApp { ...@@ -135,10 +137,13 @@ public abstract class AbstractUpgradableFabricApp {
135 private Set<DeviceId> spineSwitches; 137 private Set<DeviceId> spineSwitches;
136 138
137 private Map<DeviceId, List<FlowRule>> deviceFlowRules; 139 private Map<DeviceId, List<FlowRule>> deviceFlowRules;
138 - private Map<DeviceId, Boolean> rulesInstalled; 140 + private Map<DeviceId, Boolean> contextFlags;
141 + private Map<DeviceId, Boolean> ruleFlags;
142 +
143 + private ConcurrentMap<DeviceId, Boolean> deployLocks = Maps.newConcurrentMap();
139 144
140 /** 145 /**
141 - * Creates a new Bmv2 Fabric Component. 146 + * Creates a new BMv2 fabric app.
142 * 147 *
143 * @param appName app name 148 * @param appName app name
144 * @param configurationName a common name for the P4 program / BMv2 configuration used by this app 149 * @param configurationName a common name for the P4 program / BMv2 configuration used by this app
...@@ -212,7 +217,8 @@ public abstract class AbstractUpgradableFabricApp { ...@@ -212,7 +217,8 @@ public abstract class AbstractUpgradableFabricApp {
212 leafSwitches = Sets.newHashSet(); 217 leafSwitches = Sets.newHashSet();
213 spineSwitches = Sets.newHashSet(); 218 spineSwitches = Sets.newHashSet();
214 deviceFlowRules = Maps.newConcurrentMap(); 219 deviceFlowRules = Maps.newConcurrentMap();
215 - rulesInstalled = Maps.newConcurrentMap(); 220 + ruleFlags = Maps.newConcurrentMap();
221 + contextFlags = Maps.newConcurrentMap();
216 } 222 }
217 223
218 // Start flow rules generator... 224 // Start flow rules generator...
...@@ -266,41 +272,19 @@ public abstract class AbstractUpgradableFabricApp { ...@@ -266,41 +272,19 @@ public abstract class AbstractUpgradableFabricApp {
266 272
267 private void deployRoutine() { 273 private void deployRoutine() {
268 if (otherAppFound && otherApp.appActive) { 274 if (otherAppFound && otherApp.appActive) {
269 - log.info("Starting update routine..."); 275 + log.info("Deactivating other app...");
270 - updateRoutine();
271 appService.deactivate(otherApp.appId); 276 appService.deactivate(otherApp.appId);
272 - } else {
273 - Stream.concat(leafSwitches.stream(), spineSwitches.stream())
274 - .map(deviceService::getDevice)
275 - .forEach(device -> spawnTask(() -> deployDevice(device)));
276 - }
277 - }
278 -
279 - private void updateRoutine() {
280 - Stream.concat(leafSwitches.stream(), spineSwitches.stream())
281 - .forEach(did -> spawnTask(() -> {
282 - cleanUpDevice(did);
283 try { 277 try {
284 Thread.sleep(CLEANUP_SLEEP); 278 Thread.sleep(CLEANUP_SLEEP);
285 } catch (InterruptedException e) { 279 } catch (InterruptedException e) {
286 log.warn("Cleanup sleep interrupted!"); 280 log.warn("Cleanup sleep interrupted!");
287 Thread.interrupted(); 281 Thread.interrupted();
288 } 282 }
289 - deployDevice(deviceService.getDevice(did));
290 - }));
291 } 283 }
292 284
293 - private void cleanUpDevice(DeviceId deviceId) { 285 + Stream.concat(leafSwitches.stream(), spineSwitches.stream())
294 - List<FlowRule> flowRulesToRemove = Lists.newArrayList(); 286 + .map(deviceService::getDevice)
295 - flowRuleService.getFlowEntries(deviceId).forEach(fe -> { 287 + .forEach(device -> spawnTask(() -> deployDevice(device)));
296 - if (fe.appId() == otherApp.appId.id()) {
297 - flowRulesToRemove.add(fe);
298 - }
299 - });
300 - if (flowRulesToRemove.size() > 0) {
301 - log.info("Cleaning {} old flow rules from {}...", flowRulesToRemove.size(), deviceId);
302 - removeFlowRules(flowRulesToRemove);
303 - }
304 } 288 }
305 289
306 /** 290 /**
...@@ -309,36 +293,35 @@ public abstract class AbstractUpgradableFabricApp { ...@@ -309,36 +293,35 @@ public abstract class AbstractUpgradableFabricApp {
309 * @param device a device 293 * @param device a device
310 */ 294 */
311 public void deployDevice(Device device) { 295 public void deployDevice(Device device) {
312 - // Serialize executions per device ID using a concurrent map. 296 +
313 - rulesInstalled.compute(device.id(), (did, deployed) -> { 297 + DeviceId deviceId = device.id();
314 - Bmv2DeviceContext deviceContext = bmv2ContextService.getContext(device.id()); 298 +
315 - if (deviceContext == null) { 299 + // Synchronize executions over the same device.
316 - log.error("Unable to get context for device {}", device.id()); 300 + deployLocks.putIfAbsent(deviceId, new Boolean(true));
317 - return deployed; 301 + synchronized (deployLocks.get(deviceId)) {
318 - } else if (!deviceContext.equals(bmv2Context)) { 302 +
319 - log.info("Swapping configuration to {} on device {}...", configurationName, device.id()); 303 + // Set context if not already done.
320 - bmv2ContextService.triggerConfigurationSwap(device.id(), bmv2Context); 304 + if (!contextFlags.getOrDefault(deviceId, false)) {
321 - return deployed; 305 + log.info("Setting context to {} for {}...", configurationName, deviceId);
322 - } 306 + bmv2ContextService.setContext(deviceId, bmv2Context);
323 - 307 + contextFlags.put(device.id(), true);
324 - List<FlowRule> rules = deviceFlowRules.get(device.id()); 308 + }
325 - if (initDevice(device.id())) { 309 +
326 - if (deployed == null && rules != null && rules.size() > 0) { 310 + // Initialize device.
327 - log.info("Installing rules for {}...", did); 311 + if (!initDevice(deviceId)) {
312 + log.warn("Failed to initialize device {}", deviceId);
313 + }
314 +
315 + // Install rules.
316 + if (!ruleFlags.getOrDefault(deviceId, false)) {
317 + List<FlowRule> rules = deviceFlowRules.getOrDefault(deviceId, Collections.emptyList());
318 + if (rules.size() > 0) {
319 + log.info("Installing rules for {}...", deviceId);
328 installFlowRules(rules); 320 installFlowRules(rules);
329 - return true; 321 + ruleFlags.put(deviceId, true);
330 } 322 }
331 - } else {
332 - log.warn("Filed to initialize device {}", device.id());
333 - if (deployed != null && rules != null && rules.size() > 0) {
334 - log.info("Removing rules for {}...", did);
335 - removeFlowRules(rules);
336 - return null;
337 } 323 }
338 } 324 }
339 -
340 - return deployed;
341 - });
342 } 325 }
343 326
344 private void spawnTask(Runnable task) { 327 private void spawnTask(Runnable task) {
......
...@@ -164,7 +164,7 @@ public interface Bmv2DeviceAgent { ...@@ -164,7 +164,7 @@ public interface Bmv2DeviceAgent {
164 * @param jsonString a string value 164 * @param jsonString a string value
165 * @throws Bmv2RuntimeException if any error occurs 165 * @throws Bmv2RuntimeException if any error occurs
166 */ 166 */
167 - void loadNewJsonConfig(String jsonString) throws Bmv2RuntimeException; 167 + void uploadNewJsonConfig(String jsonString) throws Bmv2RuntimeException;
168 168
169 /** 169 /**
170 * Triggers a configuration swap on the device. 170 * Triggers a configuration swap on the device.
......
...@@ -25,12 +25,8 @@ import org.onosproject.net.DeviceId; ...@@ -25,12 +25,8 @@ import org.onosproject.net.DeviceId;
25 */ 25 */
26 public interface Bmv2DeviceContextService { 26 public interface Bmv2DeviceContextService {
27 27
28 - // TODO: handle the potential configuration states (e.g. RUNNING, SWAP_REQUESTED, etc.)
29 -
30 /** 28 /**
31 - * Returns the context of a given device. The context returned is the last one for which a configuration swap was 29 + * Returns the context of the given device, null if no context has been previously set.
32 - * triggered, hence there's no guarantees that the device is enforcing the returned context's configuration at the
33 - * time of the call.
34 * 30 *
35 * @param deviceId a device ID 31 * @param deviceId a device ID
36 * @return a BMv2 device context 32 * @return a BMv2 device context
...@@ -38,12 +34,12 @@ public interface Bmv2DeviceContextService { ...@@ -38,12 +34,12 @@ public interface Bmv2DeviceContextService {
38 Bmv2DeviceContext getContext(DeviceId deviceId); 34 Bmv2DeviceContext getContext(DeviceId deviceId);
39 35
40 /** 36 /**
41 - * Triggers a configuration swap on a given device. 37 + * Sets the context for the given device.
42 * 38 *
43 * @param deviceId a device ID 39 * @param deviceId a device ID
44 * @param context a BMv2 device context 40 * @param context a BMv2 device context
45 */ 41 */
46 - void triggerConfigurationSwap(DeviceId deviceId, Bmv2DeviceContext context); 42 + void setContext(DeviceId deviceId, Bmv2DeviceContext context);
47 43
48 /** 44 /**
49 * Binds the given interpreter with the given class loader so that other ONOS instances in the cluster can properly 45 * 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 { ...@@ -55,14 +51,9 @@ public interface Bmv2DeviceContextService {
55 void registerInterpreterClassLoader(Class<? extends Bmv2Interpreter> interpreterClass, ClassLoader loader); 51 void registerInterpreterClassLoader(Class<? extends Bmv2Interpreter> interpreterClass, ClassLoader loader);
56 52
57 /** 53 /**
58 - * Notifies this service that a given device has been updated, meaning a potential context change. 54 + * Returns a default context.
59 - * It returns true if the device configuration is the same as the last for which a swap was triggered, false
60 - * otherwise. In the last case, the service will asynchronously trigger a swap to the last
61 - * configuration stored by this service. If no swap has already been triggered then a default configuration will be
62 - * applied.
63 * 55 *
64 - * @param deviceId a device ID 56 + * @return a BMv2 device context
65 - * @return a boolean value
66 */ 57 */
67 - boolean notifyDeviceChange(DeviceId deviceId); 58 + Bmv2DeviceContext defaultContext();
68 } 59 }
......
...@@ -29,7 +29,7 @@ import org.apache.felix.scr.annotations.Reference; ...@@ -29,7 +29,7 @@ import org.apache.felix.scr.annotations.Reference;
29 import org.apache.felix.scr.annotations.ReferenceCardinality; 29 import org.apache.felix.scr.annotations.ReferenceCardinality;
30 import org.apache.felix.scr.annotations.Service; 30 import org.apache.felix.scr.annotations.Service;
31 import org.onlab.util.KryoNamespace; 31 import org.onlab.util.KryoNamespace;
32 -import org.onlab.util.SharedExecutors; 32 +import org.onlab.util.SharedScheduledExecutors;
33 import org.onosproject.bmv2.api.context.Bmv2Configuration; 33 import org.onosproject.bmv2.api.context.Bmv2Configuration;
34 import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration; 34 import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
35 import org.onosproject.bmv2.api.context.Bmv2DeviceContext; 35 import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
...@@ -38,11 +38,17 @@ import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent; ...@@ -38,11 +38,17 @@ import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
38 import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException; 38 import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
39 import org.onosproject.bmv2.api.service.Bmv2Controller; 39 import org.onosproject.bmv2.api.service.Bmv2Controller;
40 import org.onosproject.bmv2.api.service.Bmv2DeviceContextService; 40 import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
41 +import org.onosproject.mastership.MastershipService;
41 import org.onosproject.net.DeviceId; 42 import org.onosproject.net.DeviceId;
43 +import org.onosproject.net.device.DeviceService;
42 import org.onosproject.store.serializers.KryoNamespaces; 44 import org.onosproject.store.serializers.KryoNamespaces;
43 import org.onosproject.store.service.ConsistentMap; 45 import org.onosproject.store.service.ConsistentMap;
46 +import org.onosproject.store.service.ConsistentMapException;
47 +import org.onosproject.store.service.MapEvent;
48 +import org.onosproject.store.service.MapEventListener;
44 import org.onosproject.store.service.Serializer; 49 import org.onosproject.store.service.Serializer;
45 import org.onosproject.store.service.StorageService; 50 import org.onosproject.store.service.StorageService;
51 +import org.onosproject.store.service.Versioned;
46 import org.slf4j.Logger; 52 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory; 53 import org.slf4j.LoggerFactory;
48 54
...@@ -50,30 +56,43 @@ import java.io.BufferedReader; ...@@ -50,30 +56,43 @@ import java.io.BufferedReader;
50 import java.io.IOException; 56 import java.io.IOException;
51 import java.io.InputStreamReader; 57 import java.io.InputStreamReader;
52 import java.util.Map; 58 import java.util.Map;
53 -import java.util.concurrent.ExecutorService; 59 +import java.util.concurrent.ConcurrentMap;
60 +import java.util.concurrent.ScheduledExecutorService;
61 +import java.util.concurrent.ScheduledFuture;
62 +import java.util.concurrent.TimeUnit;
54 63
55 import static com.google.common.base.Preconditions.checkNotNull; 64 import static com.google.common.base.Preconditions.checkNotNull;
65 +import static org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration.parse;
66 +import static org.onosproject.store.service.MapEvent.Type.INSERT;
67 +import static org.onosproject.store.service.MapEvent.Type.UPDATE;
56 68
57 @Component(immediate = true) 69 @Component(immediate = true)
58 @Service 70 @Service
59 public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService { 71 public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService {
60 72
61 private static final String JSON_DEFAULT_CONFIG_PATH = "/default.json"; 73 private static final String JSON_DEFAULT_CONFIG_PATH = "/default.json";
74 + private static final long CHECK_INTERVAL = 5_000; // milliseconds
62 75
63 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 76 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
64 private StorageService storageService; 77 private StorageService storageService;
65 78
66 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 79 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
80 + private DeviceService deviceService;
81 +
82 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
83 + private MastershipService mastershipService;
84 +
85 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
67 private Bmv2Controller controller; 86 private Bmv2Controller controller;
68 87
69 - private final ExecutorService executorService = SharedExecutors.getPoolThreadExecutor(); 88 + private final ScheduledExecutorService scheduledExecutor = SharedScheduledExecutors.getPoolThreadExecutor();
89 + private final MapEventListener<DeviceId, Bmv2DeviceContext> contextListener = new ContextMapEventListener();
90 + private final ConcurrentMap<DeviceId, Boolean> deviceLocks = Maps.newConcurrentMap();
70 91
71 private ConsistentMap<DeviceId, Bmv2DeviceContext> contexts; 92 private ConsistentMap<DeviceId, Bmv2DeviceContext> contexts;
72 - private Map<DeviceId, Bmv2DeviceContext> contextsMap;
73 -
74 private Map<String, ClassLoader> interpreterClassLoaders; 93 private Map<String, ClassLoader> interpreterClassLoaders;
75 -
76 private Bmv2DeviceContext defaultContext; 94 private Bmv2DeviceContext defaultContext;
95 + private ScheduledFuture<?> configChecker = null;
77 96
78 private final Logger log = LoggerFactory.getLogger(getClass()); 97 private final Logger log = LoggerFactory.getLogger(getClass());
79 98
...@@ -88,39 +107,55 @@ public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService { ...@@ -88,39 +107,55 @@ public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService {
88 .withSerializer(Serializer.using(kryo)) 107 .withSerializer(Serializer.using(kryo))
89 .withName("onos-bmv2-contexts") 108 .withName("onos-bmv2-contexts")
90 .build(); 109 .build();
91 - contextsMap = contexts.asJavaMap();
92 -
93 - interpreterClassLoaders = Maps.newConcurrentMap();
94 110
95 Bmv2Configuration defaultConfiguration = loadDefaultConfiguration(); 111 Bmv2Configuration defaultConfiguration = loadDefaultConfiguration();
96 Bmv2Interpreter defaultInterpreter = new Bmv2DefaultInterpreterImpl(); 112 Bmv2Interpreter defaultInterpreter = new Bmv2DefaultInterpreterImpl();
97 defaultContext = new Bmv2DeviceContext(defaultConfiguration, defaultInterpreter); 113 defaultContext = new Bmv2DeviceContext(defaultConfiguration, defaultInterpreter);
98 114
99 - interpreterClassLoaders.put(defaultInterpreter.getClass().getName(), this.getClass().getClassLoader()); 115 + interpreterClassLoaders = Maps.newConcurrentMap();
116 + registerInterpreterClassLoader(defaultInterpreter.getClass(), this.getClass().getClassLoader());
117 +
118 + contexts.addListener(contextListener);
119 +
120 + if (configChecker != null && configChecker.isCancelled()) {
121 + configChecker.cancel(false);
122 + }
123 + configChecker = scheduledExecutor.scheduleAtFixedRate(this::checkDevices, 0, CHECK_INTERVAL,
124 + TimeUnit.MILLISECONDS);
100 125
101 log.info("Started"); 126 log.info("Started");
102 } 127 }
103 128
104 @Deactivate 129 @Deactivate
105 public void deactivate() { 130 public void deactivate() {
131 + contexts.removeListener(contextListener);
132 + if (configChecker != null) {
133 + configChecker.cancel(false);
134 + }
106 log.info("Stopped"); 135 log.info("Stopped");
107 } 136 }
108 137
109 @Override 138 @Override
110 public Bmv2DeviceContext getContext(DeviceId deviceId) { 139 public Bmv2DeviceContext getContext(DeviceId deviceId) {
111 checkNotNull(deviceId, "device id cannot be null"); 140 checkNotNull(deviceId, "device id cannot be null");
112 - return contextsMap.get(deviceId); 141 + Versioned<Bmv2DeviceContext> versionedContext = contexts.get(deviceId);
142 + return (versionedContext == null) ? null : versionedContext.value();
113 } 143 }
114 144
115 @Override 145 @Override
116 - public void triggerConfigurationSwap(DeviceId deviceId, Bmv2DeviceContext context) { 146 + public void setContext(DeviceId deviceId, Bmv2DeviceContext context) {
117 checkNotNull(deviceId, "device id cannot be null"); 147 checkNotNull(deviceId, "device id cannot be null");
118 checkNotNull(context, "context cannot be null"); 148 checkNotNull(context, "context cannot be null");
119 if (!interpreterClassLoaders.containsKey(context.interpreter().getClass().getName())) { 149 if (!interpreterClassLoaders.containsKey(context.interpreter().getClass().getName())) {
120 - log.error("Unable to trigger configuration swap, missing class loader for context interpreter. " + 150 + log.error("Unable to set context, missing class loader for interpreter '{}'. " +
121 - "Please register it with registerInterpreterClassLoader()"); 151 + "Please register it with registerInterpreterClassLoader()",
152 + context.interpreter().getClass().getName());
122 } else { 153 } else {
123 - executorService.execute(() -> executeConfigurationSwap(deviceId, context)); 154 + try {
155 + contexts.put(deviceId, context);
156 + } catch (ConsistentMapException.ConcurrentModification e) {
157 + log.error("Detected concurrent modification on context map");
158 + }
124 } 159 }
125 } 160 }
126 161
...@@ -129,86 +164,83 @@ public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService { ...@@ -129,86 +164,83 @@ public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService {
129 interpreterClassLoaders.put(interpreterClass.getName(), loader); 164 interpreterClassLoaders.put(interpreterClass.getName(), loader);
130 } 165 }
131 166
132 - private void executeConfigurationSwap(DeviceId deviceId, Bmv2DeviceContext context) { 167 + @Override
133 - contexts.compute(deviceId, (key, existingValue) -> { 168 + public Bmv2DeviceContext defaultContext() {
134 - if (context.equals(existingValue)) { 169 + return defaultContext;
135 - log.info("Dropping swap request as one has already been triggered for the given context.");
136 - return existingValue;
137 } 170 }
171 +
172 + private void configCheck(DeviceId deviceId) {
173 + // Synchronize executions over the same deviceId.
174 + deviceLocks.putIfAbsent(deviceId, new Boolean(true));
175 + synchronized (deviceLocks.get(deviceId)) {
176 +
177 + Bmv2DeviceContext storedContext = getContext(deviceId);
178 + if (storedContext == null) {
179 + return;
180 + }
181 +
182 + log.trace("Executing configuration check on {}...", deviceId);
183 +
184 + try {
185 + // FIXME: JSON dump is heavy, can we use the JSON MD5 to check the running configuration?
186 + String jsonString = controller.getAgent(deviceId).dumpJsonConfig();
187 + Bmv2Configuration deviceConfiguration = parse(Json.parse(jsonString).asObject());
188 +
189 + if (!storedContext.configuration().equals(deviceConfiguration)) {
190 + log.info("Triggering configuration swap on {}...", deviceId);
138 try { 191 try {
139 Bmv2DeviceAgent agent = controller.getAgent(deviceId); 192 Bmv2DeviceAgent agent = controller.getAgent(deviceId);
140 - String jsonString = context.configuration().json().toString(); 193 + String newJsonString = storedContext.configuration().json().toString();
141 - agent.loadNewJsonConfig(jsonString); 194 + agent.uploadNewJsonConfig(newJsonString);
142 agent.swapJsonConfig(); 195 agent.swapJsonConfig();
143 - return context;
144 } catch (Bmv2RuntimeException e) { 196 } catch (Bmv2RuntimeException e) {
145 log.error("Unable to swap configuration on {}: {}", deviceId, e.explain()); 197 log.error("Unable to swap configuration on {}: {}", deviceId, e.explain());
146 - return existingValue;
147 } 198 }
148 - });
149 } 199 }
150 - 200 + } catch (Bmv2RuntimeException e) {
151 - @Override 201 + log.warn("Unable to dump JSON configuration from {}: {}", deviceId, e.explain());
152 - public boolean notifyDeviceChange(DeviceId deviceId) {
153 - checkNotNull(deviceId, "device id cannot be null");
154 -
155 - Bmv2DeviceContext storedContext = getContext(deviceId);
156 -
157 - if (storedContext == null) {
158 - log.info("No context previously stored for {}, swapping to DEFAULT_CONTEXT.", deviceId);
159 - triggerConfigurationSwap(deviceId, defaultContext);
160 - // Device can be accepted.
161 - return false;
162 - } else {
163 - Bmv2Configuration deviceConfiguration = loadDeviceConfiguration(deviceId);
164 - if (deviceConfiguration == null) {
165 - log.warn("Unable to load configuration from device {}", deviceId);
166 - return false;
167 - }
168 - if (storedContext.configuration().equals(deviceConfiguration)) {
169 - return true;
170 - } else {
171 - log.info("Device context is different from the stored one, triggering configuration swap for {}...",
172 - deviceId);
173 - triggerConfigurationSwap(deviceId, storedContext);
174 - return false;
175 } 202 }
176 } 203 }
177 } 204 }
178 205
179 - /** 206 + private void triggerConfigCheck(DeviceId deviceId) {
180 - * Load and parse a BMv2 JSON configuration from the given device. 207 + if (mastershipService.isLocalMaster(deviceId)) {
181 - * 208 + scheduledExecutor.schedule(() -> configCheck(deviceId), 0, TimeUnit.SECONDS);
182 - * @param deviceId a device id
183 - * @return a BMv2 configuration
184 - */
185 - private Bmv2Configuration loadDeviceConfiguration(DeviceId deviceId) {
186 - try {
187 - String jsonString = controller.getAgent(deviceId).dumpJsonConfig();
188 - return Bmv2DefaultConfiguration.parse(Json.parse(jsonString).asObject());
189 - } catch (Bmv2RuntimeException e) {
190 - log.warn("Unable to load JSON configuration from {}: {}", deviceId, e.explain());
191 - return null;
192 } 209 }
193 } 210 }
194 211
195 - /** 212 + private void checkDevices() {
196 - * Loads default configuration from file. 213 + deviceService.getAvailableDevices().forEach(device -> {
197 - * 214 + triggerConfigCheck(device.id());
198 - * @return a BMv2 configuration 215 + });
199 - */ 216 + }
217 +
200 protected static Bmv2DefaultConfiguration loadDefaultConfiguration() { 218 protected static Bmv2DefaultConfiguration loadDefaultConfiguration() {
201 try { 219 try {
202 JsonObject json = Json.parse(new BufferedReader(new InputStreamReader( 220 JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
203 Bmv2DeviceContextServiceImpl.class.getResourceAsStream(JSON_DEFAULT_CONFIG_PATH)))).asObject(); 221 Bmv2DeviceContextServiceImpl.class.getResourceAsStream(JSON_DEFAULT_CONFIG_PATH)))).asObject();
204 - return Bmv2DefaultConfiguration.parse(json); 222 + return parse(json);
205 } catch (IOException e) { 223 } catch (IOException e) {
206 throw new RuntimeException("Unable to load default configuration", e); 224 throw new RuntimeException("Unable to load default configuration", e);
207 } 225 }
208 } 226 }
209 227
210 /** 228 /**
211 - * Internal BMv2 context serializer. 229 + * Listener of context changes that immediately triggers config checks (to swap the config if necessary).
230 + */
231 + private class ContextMapEventListener implements MapEventListener<DeviceId, Bmv2DeviceContext> {
232 + @Override
233 + public void event(MapEvent<DeviceId, Bmv2DeviceContext> event) {
234 + DeviceId deviceId = event.key();
235 + if (event.type().equals(INSERT) || event.type().equals(UPDATE)) {
236 + log.trace("Context {} for {}", event.type().name(), deviceId);
237 + triggerConfigCheck(deviceId);
238 + }
239 + }
240 + }
241 +
242 + /**
243 + * Context serializer.
212 */ 244 */
213 private class BmvDeviceContextSerializer extends com.esotericsoftware.kryo.Serializer<Bmv2DeviceContext> { 245 private class BmvDeviceContextSerializer extends com.esotericsoftware.kryo.Serializer<Bmv2DeviceContext> {
214 246
...@@ -222,7 +254,7 @@ public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService { ...@@ -222,7 +254,7 @@ public class Bmv2DeviceContextServiceImpl implements Bmv2DeviceContextService {
222 public Bmv2DeviceContext read(Kryo kryo, Input input, Class<Bmv2DeviceContext> type) { 254 public Bmv2DeviceContext read(Kryo kryo, Input input, Class<Bmv2DeviceContext> type) {
223 String jsonStr = kryo.readObject(input, String.class); 255 String jsonStr = kryo.readObject(input, String.class);
224 String interpreterClassName = kryo.readObject(input, String.class); 256 String interpreterClassName = kryo.readObject(input, String.class);
225 - Bmv2Configuration configuration = Bmv2DefaultConfiguration.parse(Json.parse(jsonStr).asObject()); 257 + Bmv2Configuration configuration = parse(Json.parse(jsonStr).asObject());
226 ClassLoader loader = interpreterClassLoaders.get(interpreterClassName); 258 ClassLoader loader = interpreterClassLoaders.get(interpreterClassName);
227 if (loader == null) { 259 if (loader == null) {
228 throw new IllegalStateException("No class loader registered for interpreter: " + interpreterClassName); 260 throw new IllegalStateException("No class loader registered for interpreter: " + interpreterClassName);
......
...@@ -397,7 +397,7 @@ public final class Bmv2DeviceThriftClient implements Bmv2DeviceAgent { ...@@ -397,7 +397,7 @@ public final class Bmv2DeviceThriftClient implements Bmv2DeviceAgent {
397 } 397 }
398 398
399 @Override 399 @Override
400 - public void loadNewJsonConfig(String jsonString) throws Bmv2RuntimeException { 400 + public void uploadNewJsonConfig(String jsonString) throws Bmv2RuntimeException {
401 401
402 log.debug("Loading new JSON config on device... > deviceId={}, jsonStringLength={}", 402 log.debug("Loading new JSON config on device... > deviceId={}, jsonStringLength={}",
403 deviceId, jsonString.length()); 403 deviceId, jsonString.length());
......
...@@ -185,9 +185,11 @@ public class Bmv2DeviceProvider extends AbstractDeviceProvider { ...@@ -185,9 +185,11 @@ public class Bmv2DeviceProvider extends AbstractDeviceProvider {
185 (!Objects.equals(thisDescription, lastDescription) || 185 (!Objects.equals(thisDescription, lastDescription) ||
186 !Objects.equals(thisDescription.annotations(), lastDescription.annotations())); 186 !Objects.equals(thisDescription.annotations(), lastDescription.annotations()));
187 if (descriptionChanged || !deviceService.isAvailable(did)) { 187 if (descriptionChanged || !deviceService.isAvailable(did)) {
188 - // Device description changed or device not available in the core. 188 + if (contextService.getContext(did) == null) {
189 - if (contextService.notifyDeviceChange(did)) { 189 + // Device is a first timer.
190 - // Configuration is the expected one, we can proceed notifying the core. 190 + log.info("Setting DEFAULT context for {}", did);
191 + contextService.setContext(did, contextService.defaultContext());
192 + } else {
191 resetDeviceState(did); 193 resetDeviceState(did);
192 initPortCounters(did); 194 initPortCounters(did);
193 providerService.deviceConnected(did, thisDescription); 195 providerService.deviceConnected(did, thisDescription);
...@@ -295,7 +297,7 @@ public class Bmv2DeviceProvider extends AbstractDeviceProvider { ...@@ -295,7 +297,7 @@ public class Bmv2DeviceProvider extends AbstractDeviceProvider {
295 if (cfg != null) { 297 if (cfg != null) {
296 try { 298 try {
297 cfg.getDevicesInfo().stream().forEach(info -> { 299 cfg.getDevicesInfo().stream().forEach(info -> {
298 - // TODO: require also bmv2 internal device id from net-cfg (now is default 0) 300 + // FIXME: require also bmv2 internal device id from net-cfg (now is default 0)
299 Bmv2Device bmv2Device = new Bmv2Device(info.ip().toString(), info.port(), 0); 301 Bmv2Device bmv2Device = new Bmv2Device(info.ip().toString(), info.port(), 0);
300 triggerProbe(bmv2Device.asDeviceId()); 302 triggerProbe(bmv2Device.asDeviceId());
301 }); 303 });
......