Committed by
Gerrit Code Review
Simplified Bmv2 device context service and context handling in demo apps
Change-Id: I2a13ed673902d0616732d43c841f50b1ad38cd4c
Showing
6 changed files
with
155 additions
and
147 deletions
... | @@ -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 | }); | ... | ... |
-
Please register or login to post a comment