Charles Chan
Committed by Gerrit Code Review

CORD-348 Fabric multicast support - error handling

Automatically failover to backup spine if
- ingress - transit link down
- transit - egress link down
- transit device down

Can recover from fatal error with human involved
- ingress switch down
- egress switch down
- all links to spine down

Scan through McastRouteStore when
- SR activate
- link up

Also include following features
- Use flow objective context in McastHandler
- Update Mcast VLAN config sample

Change-Id: I75007d9efd7646e7c4e57fa6d3fc6943543153cf
...@@ -42,25 +42,33 @@ import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; ...@@ -42,25 +42,33 @@ import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
42 import org.onosproject.net.flowobjective.DefaultFilteringObjective; 42 import org.onosproject.net.flowobjective.DefaultFilteringObjective;
43 import org.onosproject.net.flowobjective.DefaultForwardingObjective; 43 import org.onosproject.net.flowobjective.DefaultForwardingObjective;
44 import org.onosproject.net.flowobjective.DefaultNextObjective; 44 import org.onosproject.net.flowobjective.DefaultNextObjective;
45 +import org.onosproject.net.flowobjective.DefaultObjectiveContext;
45 import org.onosproject.net.flowobjective.FilteringObjective; 46 import org.onosproject.net.flowobjective.FilteringObjective;
46 import org.onosproject.net.flowobjective.ForwardingObjective; 47 import org.onosproject.net.flowobjective.ForwardingObjective;
47 import org.onosproject.net.flowobjective.NextObjective; 48 import org.onosproject.net.flowobjective.NextObjective;
49 +import org.onosproject.net.flowobjective.ObjectiveContext;
48 import org.onosproject.net.mcast.McastEvent; 50 import org.onosproject.net.mcast.McastEvent;
49 import org.onosproject.net.mcast.McastRouteInfo; 51 import org.onosproject.net.mcast.McastRouteInfo;
50 import org.onosproject.net.topology.TopologyService; 52 import org.onosproject.net.topology.TopologyService;
51 -import org.onosproject.segmentrouting.storekey.McastNextObjectiveStoreKey; 53 +import org.onosproject.segmentrouting.storekey.McastStoreKey;
52 import org.onosproject.store.serializers.KryoNamespaces; 54 import org.onosproject.store.serializers.KryoNamespaces;
53 import org.onosproject.store.service.ConsistentMap; 55 import org.onosproject.store.service.ConsistentMap;
54 import org.onosproject.store.service.Serializer; 56 import org.onosproject.store.service.Serializer;
55 import org.onosproject.store.service.StorageService; 57 import org.onosproject.store.service.StorageService;
58 +import org.onosproject.store.service.Versioned;
56 import org.slf4j.Logger; 59 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory; 60 import org.slf4j.LoggerFactory;
58 61
59 import java.util.Collection; 62 import java.util.Collection;
60 import java.util.Collections; 63 import java.util.Collections;
64 +import java.util.Iterator;
61 import java.util.List; 65 import java.util.List;
66 +import java.util.Map;
62 import java.util.Optional; 67 import java.util.Optional;
63 import java.util.Set; 68 import java.util.Set;
69 +import java.util.stream.Collectors;
70 +
71 +import static com.google.common.base.Preconditions.checkState;
64 72
65 /** 73 /**
66 * Handles multicast-related events. 74 * Handles multicast-related events.
...@@ -71,8 +79,27 @@ public class McastHandler { ...@@ -71,8 +79,27 @@ public class McastHandler {
71 private final ApplicationId coreAppId; 79 private final ApplicationId coreAppId;
72 private StorageService storageService; 80 private StorageService storageService;
73 private TopologyService topologyService; 81 private TopologyService topologyService;
74 - private final KryoNamespace.Builder kryoBuilder; 82 + private final ConsistentMap<McastStoreKey, NextObjective> mcastNextObjStore;
75 - private final ConsistentMap<McastNextObjectiveStoreKey, NextObjective> mcastNextObjStore; 83 + private final KryoNamespace.Builder mcastKryo;
84 + private final ConsistentMap<McastStoreKey, McastRole> mcastRoleStore;
85 +
86 + /**
87 + * Role in the multicast tree.
88 + */
89 + public enum McastRole {
90 + /**
91 + * The device is the ingress device of this group.
92 + */
93 + INGRESS,
94 + /**
95 + * The device is the transit device of this group.
96 + */
97 + TRANSIT,
98 + /**
99 + * The device is the egress device of this group.
100 + */
101 + EGRESS
102 + }
76 103
77 /** 104 /**
78 * Constructs the McastEventHandler. 105 * Constructs the McastEventHandler.
...@@ -81,19 +108,36 @@ public class McastHandler { ...@@ -81,19 +108,36 @@ public class McastHandler {
81 */ 108 */
82 public McastHandler(SegmentRoutingManager srManager) { 109 public McastHandler(SegmentRoutingManager srManager) {
83 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME); 110 coreAppId = srManager.coreService.getAppId(CoreService.CORE_APP_NAME);
84 -
85 this.srManager = srManager; 111 this.srManager = srManager;
86 this.storageService = srManager.storageService; 112 this.storageService = srManager.storageService;
87 this.topologyService = srManager.topologyService; 113 this.topologyService = srManager.topologyService;
88 - 114 + mcastKryo = new KryoNamespace.Builder()
89 - kryoBuilder = new KryoNamespace.Builder()
90 .register(KryoNamespaces.API) 115 .register(KryoNamespaces.API)
91 - .register(McastNextObjectiveStoreKey.class); 116 + .register(McastStoreKey.class)
117 + .register(McastRole.class);
92 mcastNextObjStore = storageService 118 mcastNextObjStore = storageService
93 - .<McastNextObjectiveStoreKey, NextObjective>consistentMapBuilder() 119 + .<McastStoreKey, NextObjective>consistentMapBuilder()
94 .withName("onos-mcast-nextobj-store") 120 .withName("onos-mcast-nextobj-store")
95 - .withSerializer(Serializer.using(kryoBuilder.build())) 121 + .withSerializer(Serializer.using(mcastKryo.build()))
96 .build(); 122 .build();
123 + mcastRoleStore = storageService
124 + .<McastStoreKey, McastRole>consistentMapBuilder()
125 + .withName("onos-mcast-role-store")
126 + .withSerializer(Serializer.using(mcastKryo.build()))
127 + .build();
128 + }
129 +
130 + /**
131 + * Read initial multicast from mcast store.
132 + */
133 + public void init() {
134 + srManager.multicastRouteService.getRoutes().forEach(mcastRoute -> {
135 + ConnectPoint source = srManager.multicastRouteService.fetchSource(mcastRoute);
136 + Set<ConnectPoint> sinks = srManager.multicastRouteService.fetchSinks(mcastRoute);
137 + sinks.forEach(sink -> {
138 + processSinkAddedInternal(source, sink, mcastRoute.group());
139 + });
140 + });
97 } 141 }
98 142
99 /** 143 /**
...@@ -166,6 +210,9 @@ public class McastHandler { ...@@ -166,6 +210,9 @@ public class McastHandler {
166 210
167 // Process the egress device 211 // Process the egress device
168 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan); 212 boolean isLast = removePortFromDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
213 + if (isLast) {
214 + mcastRoleStore.remove(new McastStoreKey(mcastIp, sink.deviceId()));
215 + }
169 216
170 // If this is the last sink on the device, also update upstream 217 // If this is the last sink on the device, also update upstream
171 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp); 218 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
...@@ -176,6 +223,7 @@ public class McastHandler { ...@@ -176,6 +223,7 @@ public class McastHandler {
176 if (isLast) { 223 if (isLast) {
177 isLast = removePortFromDevice(link.src().deviceId(), link.src().port(), 224 isLast = removePortFromDevice(link.src().deviceId(), link.src().port(),
178 mcastIp, assignedVlan); 225 mcastIp, assignedVlan);
226 + mcastRoleStore.remove(new McastStoreKey(mcastIp, link.src().deviceId()));
179 } 227 }
180 } 228 }
181 } 229 }
...@@ -192,6 +240,9 @@ public class McastHandler { ...@@ -192,6 +240,9 @@ public class McastHandler {
192 IpAddress mcastIp) { 240 IpAddress mcastIp) {
193 VlanId assignedVlan = assignedVlan(); 241 VlanId assignedVlan = assignedVlan();
194 242
243 + // Process the ingress device
244 + addFilterToDevice(source.deviceId(), source.port(), assignedVlan);
245 +
195 // When source and sink are on the same device 246 // When source and sink are on the same device
196 if (source.deviceId().equals(sink.deviceId())) { 247 if (source.deviceId().equals(sink.deviceId())) {
197 // Source and sink are on even the same port. There must be something wrong. 248 // Source and sink are on even the same port. There must be something wrong.
...@@ -200,22 +251,88 @@ public class McastHandler { ...@@ -200,22 +251,88 @@ public class McastHandler {
200 return; 251 return;
201 } 252 }
202 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan); 253 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
254 + mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()), McastRole.INGRESS);
203 return; 255 return;
204 } 256 }
205 257
206 - // Process the ingress device
207 - addFilterToDevice(source.deviceId(), source.port(), assignedVlan);
208 -
209 // Find a path. If present, create/update groups and flows for each hop 258 // Find a path. If present, create/update groups and flows for each hop
210 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp); 259 Optional<Path> mcastPath = getPath(source.deviceId(), sink.deviceId(), mcastIp);
211 if (mcastPath.isPresent()) { 260 if (mcastPath.isPresent()) {
212 - mcastPath.get().links().forEach(link -> { 261 + List<Link> links = mcastPath.get().links();
262 + checkState(links.size() == 2,
263 + "Path in leaf-spine topology should always be two hops: ", links);
264 +
265 + links.forEach(link -> {
213 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan); 266 addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan);
214 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp, assignedVlan); 267 addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp, assignedVlan);
215 }); 268 });
269 +
216 // Process the egress device 270 // Process the egress device
217 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan); 271 addPortToDevice(sink.deviceId(), sink.port(), mcastIp, assignedVlan);
272 +
273 + // Setup mcast roles
274 + mcastRoleStore.put(new McastStoreKey(mcastIp, source.deviceId()),
275 + McastRole.INGRESS);
276 + mcastRoleStore.put(new McastStoreKey(mcastIp, links.get(0).dst().deviceId()),
277 + McastRole.TRANSIT);
278 + mcastRoleStore.put(new McastStoreKey(mcastIp, sink.deviceId()),
279 + McastRole.EGRESS);
280 + } else {
281 + log.warn("Unable to find a path from {} to {}. Abort sinkAdded",
282 + source.deviceId(), sink.deviceId());
283 + }
284 + }
285 +
286 + /**
287 + * Processes the LINK_DOWN event.
288 + *
289 + * @param affectedLink Link that is going down
290 + */
291 + protected void processLinkDown(Link affectedLink) {
292 + VlanId assignedVlan = assignedVlan();
293 +
294 + getAffectedGroups(affectedLink).forEach(mcastIp -> {
295 + // Find out the ingress, transit and egress device of affected group
296 + DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
297 + .stream().findAny().orElse(null);
298 + DeviceId transitDevice = getDevice(mcastIp, McastRole.TRANSIT)
299 + .stream().findAny().orElse(null);
300 + Set<DeviceId> egressDevices = getDevice(mcastIp, McastRole.EGRESS);
301 + if (ingressDevice == null || transitDevice == null || egressDevices == null) {
302 + log.warn("Missing ingress {}, transit {}, or egress {} devices",
303 + ingressDevice, transitDevice, egressDevices);
304 + return;
305 + }
306 +
307 + // Remove entire transit
308 + removeGroupFromDevice(transitDevice, mcastIp, assignedVlan);
309 +
310 + // Remove transit-facing port on ingress device
311 + PortNumber ingressTransitPort = ingressTransitPort(mcastIp);
312 + if (ingressTransitPort != null) {
313 + removePortFromDevice(ingressDevice, ingressTransitPort, mcastIp, assignedVlan);
314 + mcastRoleStore.remove(new McastStoreKey(mcastIp, transitDevice));
315 + }
316 +
317 + // Construct a new path for each egress device
318 + egressDevices.forEach(egressDevice -> {
319 + Optional<Path> mcastPath = getPath(ingressDevice, egressDevice, mcastIp);
320 + if (mcastPath.isPresent()) {
321 + List<Link> links = mcastPath.get().links();
322 + links.forEach(link -> {
323 + addPortToDevice(link.src().deviceId(), link.src().port(), mcastIp, assignedVlan);
324 + addFilterToDevice(link.dst().deviceId(), link.dst().port(), assignedVlan);
325 + });
326 + // Setup new transit mcast role
327 + mcastRoleStore.put(new McastStoreKey(mcastIp,
328 + links.get(0).dst().deviceId()), McastRole.TRANSIT);
329 + } else {
330 + log.warn("Fail to recover egress device {} from link failure {}",
331 + egressDevice, affectedLink);
332 + removeGroupFromDevice(egressDevice, mcastIp, assignedVlan);
218 } 333 }
334 + });
335 + });
219 } 336 }
220 337
221 /** 338 /**
...@@ -228,7 +345,8 @@ public class McastHandler { ...@@ -228,7 +345,8 @@ public class McastHandler {
228 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan) { 345 private void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan) {
229 // Do nothing if the port is configured as suppressed 346 // Do nothing if the port is configured as suppressed
230 ConnectPoint connectPt = new ConnectPoint(deviceId, port); 347 ConnectPoint connectPt = new ConnectPoint(deviceId, port);
231 - if (srManager.deviceConfiguration.suppressSubnet().contains(connectPt) || 348 + if (srManager.deviceConfiguration == null ||
349 + srManager.deviceConfiguration.suppressSubnet().contains(connectPt) ||
232 srManager.deviceConfiguration.suppressHost().contains(connectPt)) { 350 srManager.deviceConfiguration.suppressHost().contains(connectPt)) {
233 log.info("Ignore suppressed port {}", connectPt); 351 log.info("Ignore suppressed port {}", connectPt);
234 return; 352 return;
...@@ -236,8 +354,13 @@ public class McastHandler { ...@@ -236,8 +354,13 @@ public class McastHandler {
236 354
237 FilteringObjective.Builder filtObjBuilder = 355 FilteringObjective.Builder filtObjBuilder =
238 filterObjBuilder(deviceId, port, assignedVlan); 356 filterObjBuilder(deviceId, port, assignedVlan);
239 - srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add()); 357 + ObjectiveContext context = new DefaultObjectiveContext(
240 - // TODO add objective context 358 + (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
359 + deviceId, port.toLong(), assignedVlan),
360 + (objective, error) ->
361 + log.warn("Failed to add filter on {}/{}, vlan {}: {}",
362 + deviceId, port.toLong(), assignedVlan, error));
363 + srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
241 } 364 }
242 365
243 /** 366 /**
...@@ -251,17 +374,14 @@ public class McastHandler { ...@@ -251,17 +374,14 @@ public class McastHandler {
251 */ 374 */
252 private void addPortToDevice(DeviceId deviceId, PortNumber port, 375 private void addPortToDevice(DeviceId deviceId, PortNumber port,
253 IpAddress mcastIp, VlanId assignedVlan) { 376 IpAddress mcastIp, VlanId assignedVlan) {
254 - log.info("Add port {} to {}. mcastIp={}, assignedVlan={}", 377 + McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
255 - port, deviceId, mcastIp, assignedVlan);
256 - McastNextObjectiveStoreKey mcastNextObjectiveStoreKey =
257 - new McastNextObjectiveStoreKey(mcastIp, deviceId);
258 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder(); 378 ImmutableSet.Builder<PortNumber> portBuilder = ImmutableSet.builder();
259 - if (!mcastNextObjStore.containsKey(mcastNextObjectiveStoreKey)) { 379 + if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
260 // First time someone request this mcast group via this device 380 // First time someone request this mcast group via this device
261 portBuilder.add(port); 381 portBuilder.add(port);
262 } else { 382 } else {
263 // This device already serves some subscribers of this mcast group 383 // This device already serves some subscribers of this mcast group
264 - NextObjective nextObj = mcastNextObjStore.get(mcastNextObjectiveStoreKey).value(); 384 + NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
265 // Stop if the port is already in the nextobj 385 // Stop if the port is already in the nextobj
266 Set<PortNumber> existingPorts = getPorts(nextObj.next()); 386 Set<PortNumber> existingPorts = getPorts(nextObj.next());
267 if (existingPorts.contains(port)) { 387 if (existingPorts.contains(port)) {
...@@ -271,14 +391,19 @@ public class McastHandler { ...@@ -271,14 +391,19 @@ public class McastHandler {
271 portBuilder.addAll(existingPorts).add(port).build(); 391 portBuilder.addAll(existingPorts).add(port).build();
272 } 392 }
273 // Create, store and apply the new nextObj and fwdObj 393 // Create, store and apply the new nextObj and fwdObj
394 + ObjectiveContext context = new DefaultObjectiveContext(
395 + (objective) -> log.debug("Successfully add {} on {}/{}, vlan {}",
396 + mcastIp, deviceId, port.toLong(), assignedVlan),
397 + (objective, error) ->
398 + log.warn("Failed to add {} on {}/{}, vlan {}: {}",
399 + mcastIp, deviceId, port.toLong(), assignedVlan, error));
274 NextObjective newNextObj = 400 NextObjective newNextObj =
275 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add(); 401 nextObjBuilder(mcastIp, assignedVlan, portBuilder.build()).add();
276 ForwardingObjective fwdObj = 402 ForwardingObjective fwdObj =
277 - fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(); 403 + fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(context);
278 - mcastNextObjStore.put(mcastNextObjectiveStoreKey, newNextObj); 404 + mcastNextObjStore.put(mcastStoreKey, newNextObj);
279 srManager.flowObjectiveService.next(deviceId, newNextObj); 405 srManager.flowObjectiveService.next(deviceId, newNextObj);
280 srManager.flowObjectiveService.forward(deviceId, fwdObj); 406 srManager.flowObjectiveService.forward(deviceId, fwdObj);
281 - // TODO add objective callback
282 } 407 }
283 408
284 /** 409 /**
...@@ -294,19 +419,17 @@ public class McastHandler { ...@@ -294,19 +419,17 @@ public class McastHandler {
294 */ 419 */
295 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port, 420 private boolean removePortFromDevice(DeviceId deviceId, PortNumber port,
296 IpAddress mcastIp, VlanId assignedVlan) { 421 IpAddress mcastIp, VlanId assignedVlan) {
297 - log.info("Remove port {} from {}. mcastIp={}, assignedVlan={}", 422 + McastStoreKey mcastStoreKey =
298 - port, deviceId, mcastIp, assignedVlan); 423 + new McastStoreKey(mcastIp, deviceId);
299 - McastNextObjectiveStoreKey mcastNextObjectiveStoreKey =
300 - new McastNextObjectiveStoreKey(mcastIp, deviceId);
301 // This device is not serving this multicast group 424 // This device is not serving this multicast group
302 - if (!mcastNextObjStore.containsKey(mcastNextObjectiveStoreKey)) { 425 + if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
303 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port); 426 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
304 return false; 427 return false;
305 } 428 }
306 - NextObjective nextObj = mcastNextObjStore.get(mcastNextObjectiveStoreKey).value(); 429 + NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
307 430
308 Set<PortNumber> existingPorts = getPorts(nextObj.next()); 431 Set<PortNumber> existingPorts = getPorts(nextObj.next());
309 - // This device does not serve this multicast group 432 + // This port does not serve this multicast group
310 if (!existingPorts.contains(port)) { 433 if (!existingPorts.contains(port)) {
311 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port); 434 log.warn("{} is not serving {} on port {}. Abort.", deviceId, mcastIp, port);
312 return false; 435 return false;
...@@ -322,22 +445,91 @@ public class McastHandler { ...@@ -322,22 +445,91 @@ public class McastHandler {
322 // NOTE: Rely on GroupStore garbage collection rather than explicitly 445 // NOTE: Rely on GroupStore garbage collection rather than explicitly
323 // remove L3MG since there might be other flows/groups refer to 446 // remove L3MG since there might be other flows/groups refer to
324 // the same L2IG 447 // the same L2IG
325 - fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(); 448 + ObjectiveContext context = new DefaultObjectiveContext(
326 - mcastNextObjStore.remove(mcastNextObjectiveStoreKey); 449 + (objective) -> log.debug("Successfully remove {} on {}/{}, vlan {}",
450 + mcastIp, deviceId, port.toLong(), assignedVlan),
451 + (objective, error) ->
452 + log.warn("Failed to remove {} on {}/{}, vlan {}: {}",
453 + mcastIp, deviceId, port.toLong(), assignedVlan, error));
454 + fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
455 + mcastNextObjStore.remove(mcastStoreKey);
327 srManager.flowObjectiveService.forward(deviceId, fwdObj); 456 srManager.flowObjectiveService.forward(deviceId, fwdObj);
328 } else { 457 } else {
329 // If this is not the last sink, update flows and groups 458 // If this is not the last sink, update flows and groups
459 + ObjectiveContext context = new DefaultObjectiveContext(
460 + (objective) -> log.debug("Successfully update {} on {}/{}, vlan {}",
461 + mcastIp, deviceId, port.toLong(), assignedVlan),
462 + (objective, error) ->
463 + log.warn("Failed to update {} on {}/{}, vlan {}: {}",
464 + mcastIp, deviceId, port.toLong(), assignedVlan, error));
330 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add(); 465 newNextObj = nextObjBuilder(mcastIp, assignedVlan, existingPorts).add();
331 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add(); 466 fwdObj = fwdObjBuilder(mcastIp, assignedVlan, newNextObj.id()).add();
332 - mcastNextObjStore.put(mcastNextObjectiveStoreKey, newNextObj); 467 + mcastNextObjStore.put(mcastStoreKey, newNextObj);
333 srManager.flowObjectiveService.next(deviceId, newNextObj); 468 srManager.flowObjectiveService.next(deviceId, newNextObj);
334 srManager.flowObjectiveService.forward(deviceId, fwdObj); 469 srManager.flowObjectiveService.forward(deviceId, fwdObj);
335 } 470 }
336 - // TODO add objective callback
337 -
338 return existingPorts.isEmpty(); 471 return existingPorts.isEmpty();
339 } 472 }
340 473
474 +
475 + /**
476 + * Removes entire group on given device.
477 + *
478 + * @param deviceId device ID
479 + * @param mcastIp multicast group to be removed
480 + * @param assignedVlan assigned VLAN ID
481 + */
482 + private void removeGroupFromDevice(DeviceId deviceId, IpAddress mcastIp,
483 + VlanId assignedVlan) {
484 + McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, deviceId);
485 + // This device is not serving this multicast group
486 + if (!mcastNextObjStore.containsKey(mcastStoreKey)) {
487 + log.warn("{} is not serving {}. Abort.", deviceId, mcastIp);
488 + return;
489 + }
490 + NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
491 + // NOTE: Rely on GroupStore garbage collection rather than explicitly
492 + // remove L3MG since there might be other flows/groups refer to
493 + // the same L2IG
494 + ObjectiveContext context = new DefaultObjectiveContext(
495 + (objective) -> log.debug("Successfully remove {} on {}, vlan {}",
496 + mcastIp, deviceId, assignedVlan),
497 + (objective, error) ->
498 + log.warn("Failed to remove {} on {}, vlan {}: {}",
499 + mcastIp, deviceId, assignedVlan, error));
500 + ForwardingObjective fwdObj = fwdObjBuilder(mcastIp, assignedVlan, nextObj.id()).remove(context);
501 + srManager.flowObjectiveService.forward(deviceId, fwdObj);
502 + mcastNextObjStore.remove(mcastStoreKey);
503 + mcastRoleStore.remove(mcastStoreKey);
504 + }
505 +
506 + /**
507 + * Remove all groups on given device.
508 + *
509 + * @param deviceId device ID
510 + */
511 + public void removeDevice(DeviceId deviceId) {
512 + Iterator<Map.Entry<McastStoreKey, Versioned<NextObjective>>> itNextObj =
513 + mcastNextObjStore.entrySet().iterator();
514 + while (itNextObj.hasNext()) {
515 + Map.Entry<McastStoreKey, Versioned<NextObjective>> entry = itNextObj.next();
516 + if (entry.getKey().deviceId().equals(deviceId)) {
517 + removeGroupFromDevice(entry.getKey().deviceId(), entry.getKey().mcastIp(), assignedVlan());
518 + itNextObj.remove();
519 + }
520 + }
521 +
522 + Iterator<Map.Entry<McastStoreKey, Versioned<McastRole>>> itRole =
523 + mcastRoleStore.entrySet().iterator();
524 + while (itRole.hasNext()) {
525 + Map.Entry<McastStoreKey, Versioned<McastRole>> entry = itRole.next();
526 + if (entry.getKey().deviceId().equals(deviceId)) {
527 + itRole.remove();
528 + }
529 + }
530 +
531 + }
532 +
341 /** 533 /**
342 * Creates a next objective builder for multicast. 534 * Creates a next objective builder for multicast.
343 * 535 *
...@@ -456,16 +648,15 @@ public class McastHandler { ...@@ -456,16 +648,15 @@ public class McastHandler {
456 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) { 648 private Optional<Path> getPath(DeviceId src, DeviceId dst, IpAddress mcastIp) {
457 List<Path> allPaths = Lists.newArrayList( 649 List<Path> allPaths = Lists.newArrayList(
458 topologyService.getPaths(topologyService.currentTopology(), src, dst)); 650 topologyService.getPaths(topologyService.currentTopology(), src, dst));
651 + log.debug("{} path(s) found from {} to {}", allPaths.size(), src, dst);
459 if (allPaths.isEmpty()) { 652 if (allPaths.isEmpty()) {
460 - log.warn("Fail to find a path from {} to {}. Abort.", src, dst);
461 return Optional.empty(); 653 return Optional.empty();
462 } 654 }
463 655
464 // If one of the available path is used before, use the same path 656 // If one of the available path is used before, use the same path
465 - McastNextObjectiveStoreKey mcastNextObjectiveStoreKey = 657 + McastStoreKey mcastStoreKey = new McastStoreKey(mcastIp, src);
466 - new McastNextObjectiveStoreKey(mcastIp, src); 658 + if (mcastNextObjStore.containsKey(mcastStoreKey)) {
467 - if (mcastNextObjStore.containsKey(mcastNextObjectiveStoreKey)) { 659 + NextObjective nextObj = mcastNextObjStore.get(mcastStoreKey).value();
468 - NextObjective nextObj = mcastNextObjStore.get(mcastNextObjectiveStoreKey).value();
469 Set<PortNumber> existingPorts = getPorts(nextObj.next()); 660 Set<PortNumber> existingPorts = getPorts(nextObj.next());
470 for (Path path : allPaths) { 661 for (Path path : allPaths) {
471 PortNumber srcPort = path.links().get(0).src().port(); 662 PortNumber srcPort = path.links().get(0).src().port();
...@@ -480,6 +671,37 @@ public class McastHandler { ...@@ -480,6 +671,37 @@ public class McastHandler {
480 } 671 }
481 672
482 /** 673 /**
674 + * Gets device(s) of given role in given multicast group.
675 + *
676 + * @param mcastIp multicast IP
677 + * @param role multicast role
678 + * @return set of device ID or empty set if not found
679 + */
680 + private Set<DeviceId> getDevice(IpAddress mcastIp, McastRole role) {
681 + return mcastRoleStore.entrySet().stream()
682 + .filter(entry -> entry.getKey().mcastIp().equals(mcastIp) &&
683 + entry.getValue().value() == role)
684 + .map(Map.Entry::getKey).map(McastStoreKey::deviceId)
685 + .collect(Collectors.toSet());
686 + }
687 +
688 + /**
689 + * Gets groups which is affected by the link down event.
690 + *
691 + * @param link link going down
692 + * @return a set of multicast IpAddress
693 + */
694 + private Set<IpAddress> getAffectedGroups(Link link) {
695 + DeviceId deviceId = link.src().deviceId();
696 + PortNumber port = link.src().port();
697 + return mcastNextObjStore.entrySet().stream()
698 + .filter(entry -> entry.getKey().deviceId().equals(deviceId) &&
699 + getPorts(entry.getValue().value().next()).contains(port))
700 + .map(Map.Entry::getKey).map(McastStoreKey::mcastIp)
701 + .collect(Collectors.toSet());
702 + }
703 +
704 + /**
483 * Gets egress VLAN from McastConfig. 705 * Gets egress VLAN from McastConfig.
484 * 706 *
485 * @return egress VLAN or VlanId.NONE if not configured 707 * @return egress VLAN or VlanId.NONE if not configured
...@@ -500,4 +722,34 @@ public class McastHandler { ...@@ -500,4 +722,34 @@ public class McastHandler {
500 VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET) : 722 VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET) :
501 egressVlan(); 723 egressVlan();
502 } 724 }
725 +
726 + /**
727 + * Gets the spine-facing port on ingress device of given multicast group.
728 + *
729 + * @param mcastIp multicast IP
730 + * @return spine-facing port on ingress device
731 + */
732 + private PortNumber ingressTransitPort(IpAddress mcastIp) {
733 + DeviceId ingressDevice = getDevice(mcastIp, McastRole.INGRESS)
734 + .stream().findAny().orElse(null);
735 + if (ingressDevice != null) {
736 + NextObjective nextObj = mcastNextObjStore
737 + .get(new McastStoreKey(mcastIp, ingressDevice)).value();
738 + Set<PortNumber> ports = getPorts(nextObj.next());
739 +
740 + for (PortNumber port : ports) {
741 + // Spine-facing port should have no subnet and no xconnect
742 + if (srManager.deviceConfiguration != null &&
743 + srManager.deviceConfiguration.getPortSubnet(ingressDevice, port) == null &&
744 + srManager.deviceConfiguration.getXConnects().values().stream()
745 + .allMatch(connectPoints ->
746 + connectPoints.stream().noneMatch(connectPoint ->
747 + connectPoint.port().equals(port))
748 + )) {
749 + return port;
750 + }
751 + }
752 + }
753 + return null;
754 + }
503 } 755 }
......
...@@ -762,6 +762,8 @@ public class SegmentRoutingManager implements SegmentRoutingService { ...@@ -762,6 +762,8 @@ public class SegmentRoutingManager implements SegmentRoutingService {
762 defaultRoutingHandler.populateRoutingRulesForLinkStatusChange(null); 762 defaultRoutingHandler.populateRoutingRulesForLinkStatusChange(null);
763 //log.trace("processLinkAdded: re-starting route population process"); 763 //log.trace("processLinkAdded: re-starting route population process");
764 //defaultRoutingHandler.startPopulationProcess(); 764 //defaultRoutingHandler.startPopulationProcess();
765 +
766 + mcastHandler.init();
765 } 767 }
766 768
767 private void processLinkRemoved(Link link) { 769 private void processLinkRemoved(Link link) {
...@@ -775,6 +777,8 @@ public class SegmentRoutingManager implements SegmentRoutingService { ...@@ -775,6 +777,8 @@ public class SegmentRoutingManager implements SegmentRoutingService {
775 defaultRoutingHandler.populateRoutingRulesForLinkStatusChange(link); 777 defaultRoutingHandler.populateRoutingRulesForLinkStatusChange(link);
776 //log.trace("processLinkRemoved: re-starting route population process"); 778 //log.trace("processLinkRemoved: re-starting route population process");
777 //defaultRoutingHandler.startPopulationProcess(); 779 //defaultRoutingHandler.startPopulationProcess();
780 +
781 + mcastHandler.processLinkDown(link);
778 } 782 }
779 783
780 private void processDeviceAdded(Device device) { 784 private void processDeviceAdded(Device device) {
...@@ -784,17 +788,22 @@ public class SegmentRoutingManager implements SegmentRoutingService { ...@@ -784,17 +788,22 @@ public class SegmentRoutingManager implements SegmentRoutingService {
784 + "processed after config completes.", device.id()); 788 + "processed after config completes.", device.id());
785 return; 789 return;
786 } 790 }
791 + processDeviceAddedInternal(device.id());
792 + }
793 +
794 + private void processDeviceAddedInternal(DeviceId deviceId) {
787 // Irrespective of whether the local is a MASTER or not for this device, 795 // Irrespective of whether the local is a MASTER or not for this device,
788 // we need to create a SR-group-handler instance. This is because in a 796 // we need to create a SR-group-handler instance. This is because in a
789 // multi-instance setup, any instance can initiate forwarding/next-objectives 797 // multi-instance setup, any instance can initiate forwarding/next-objectives
790 // for any switch (even if this instance is a SLAVE or not even connected 798 // for any switch (even if this instance is a SLAVE or not even connected
791 // to the switch). To handle this, a default-group-handler instance is necessary 799 // to the switch). To handle this, a default-group-handler instance is necessary
792 // per switch. 800 // per switch.
793 - if (groupHandlerMap.get(device.id()) == null) { 801 + log.debug("Current groupHandlerMap devs: {}", groupHandlerMap.keySet());
802 + if (groupHandlerMap.get(deviceId) == null) {
794 DefaultGroupHandler groupHandler; 803 DefaultGroupHandler groupHandler;
795 try { 804 try {
796 groupHandler = DefaultGroupHandler. 805 groupHandler = DefaultGroupHandler.
797 - createGroupHandler(device.id(), 806 + createGroupHandler(deviceId,
798 appId, 807 appId,
799 deviceConfiguration, 808 deviceConfiguration,
800 linkService, 809 linkService,
...@@ -804,23 +813,25 @@ public class SegmentRoutingManager implements SegmentRoutingService { ...@@ -804,23 +813,25 @@ public class SegmentRoutingManager implements SegmentRoutingService {
804 log.warn(e.getMessage() + " Aborting processDeviceAdded."); 813 log.warn(e.getMessage() + " Aborting processDeviceAdded.");
805 return; 814 return;
806 } 815 }
807 - groupHandlerMap.put(device.id(), groupHandler); 816 + log.debug("updating groupHandlerMap with new config for device: {}",
817 + deviceId);
818 + groupHandlerMap.put(deviceId, groupHandler);
808 // Also, in some cases, drivers may need extra 819 // Also, in some cases, drivers may need extra
809 // information to process rules (eg. Router IP/MAC); and so, we send 820 // information to process rules (eg. Router IP/MAC); and so, we send
810 // port addressing rules to the driver as well irrespective of whether 821 // port addressing rules to the driver as well irrespective of whether
811 // this instance is the master or not. 822 // this instance is the master or not.
812 - defaultRoutingHandler.populatePortAddressingRules(device.id()); 823 + defaultRoutingHandler.populatePortAddressingRules(deviceId);
813 } 824 }
814 - if (mastershipService.isLocalMaster(device.id())) { 825 + if (mastershipService.isLocalMaster(deviceId)) {
815 - hostHandler.readInitialHosts(device.id()); 826 + hostHandler.readInitialHosts(deviceId);
816 - DefaultGroupHandler groupHandler = groupHandlerMap.get(device.id()); 827 + DefaultGroupHandler groupHandler = groupHandlerMap.get(deviceId);
817 groupHandler.createGroupsFromSubnetConfig(); 828 groupHandler.createGroupsFromSubnetConfig();
818 - routingRulePopulator.populateSubnetBroadcastRule(device.id()); 829 + routingRulePopulator.populateSubnetBroadcastRule(deviceId);
819 - groupHandler.createGroupsForXConnect(device.id()); 830 + groupHandler.createGroupsForXConnect(deviceId);
820 - routingRulePopulator.populateXConnectBroadcastRule(device.id()); 831 + routingRulePopulator.populateXConnectBroadcastRule(deviceId);
821 } 832 }
822 833
823 - netcfgHandler.initVRouters(device.id()); 834 + netcfgHandler.initVRouters(deviceId);
824 } 835 }
825 836
826 private void processDeviceRemoved(Device device) { 837 private void processDeviceRemoved(Device device) {
...@@ -829,34 +840,29 @@ public class SegmentRoutingManager implements SegmentRoutingService { ...@@ -829,34 +840,29 @@ public class SegmentRoutingManager implements SegmentRoutingService {
829 .forEach(entry -> { 840 .forEach(entry -> {
830 nsNextObjStore.remove(entry.getKey()); 841 nsNextObjStore.remove(entry.getKey());
831 }); 842 });
832 -
833 subnetNextObjStore.entrySet().stream() 843 subnetNextObjStore.entrySet().stream()
834 .filter(entry -> entry.getKey().deviceId().equals(device.id())) 844 .filter(entry -> entry.getKey().deviceId().equals(device.id()))
835 .forEach(entry -> { 845 .forEach(entry -> {
836 subnetNextObjStore.remove(entry.getKey()); 846 subnetNextObjStore.remove(entry.getKey());
837 }); 847 });
838 -
839 portNextObjStore.entrySet().stream() 848 portNextObjStore.entrySet().stream()
840 .filter(entry -> entry.getKey().deviceId().equals(device.id())) 849 .filter(entry -> entry.getKey().deviceId().equals(device.id()))
841 .forEach(entry -> { 850 .forEach(entry -> {
842 portNextObjStore.remove(entry.getKey()); 851 portNextObjStore.remove(entry.getKey());
843 }); 852 });
844 -
845 xConnectNextObjStore.entrySet().stream() 853 xConnectNextObjStore.entrySet().stream()
846 .filter(entry -> entry.getKey().deviceId().equals(device.id())) 854 .filter(entry -> entry.getKey().deviceId().equals(device.id()))
847 .forEach(entry -> { 855 .forEach(entry -> {
848 xConnectNextObjStore.remove(entry.getKey()); 856 xConnectNextObjStore.remove(entry.getKey());
849 }); 857 });
850 -
851 subnetVidStore.entrySet().stream() 858 subnetVidStore.entrySet().stream()
852 .filter(entry -> entry.getKey().deviceId().equals(device.id())) 859 .filter(entry -> entry.getKey().deviceId().equals(device.id()))
853 .forEach(entry -> { 860 .forEach(entry -> {
854 subnetVidStore.remove(entry.getKey()); 861 subnetVidStore.remove(entry.getKey());
855 }); 862 });
856 -
857 groupHandlerMap.remove(device.id()); 863 groupHandlerMap.remove(device.id());
858 -
859 defaultRoutingHandler.purgeEcmpGraph(device.id()); 864 defaultRoutingHandler.purgeEcmpGraph(device.id());
865 + mcastHandler.removeDevice(device.id());
860 } 866 }
861 867
862 private void processPortRemoved(Device device, Port port) { 868 private void processPortRemoved(Device device, Port port) {
...@@ -900,48 +906,11 @@ public class SegmentRoutingManager implements SegmentRoutingService { ...@@ -900,48 +906,11 @@ public class SegmentRoutingManager implements SegmentRoutingService {
900 tunnelHandler, policyStore); 906 tunnelHandler, policyStore);
901 907
902 for (Device device : deviceService.getDevices()) { 908 for (Device device : deviceService.getDevices()) {
903 - // Irrespective of whether the local is a MASTER or not for this device, 909 + processDeviceAddedInternal(device.id());
904 - // we need to create a SR-group-handler instance. This is because in a
905 - // multi-instance setup, any instance can initiate forwarding/next-objectives
906 - // for any switch (even if this instance is a SLAVE or not even connected
907 - // to the switch). To handle this, a default-group-handler instance is necessary
908 - // per switch.
909 - log.debug("Current groupHandlerMap devs: {}", groupHandlerMap.keySet());
910 - if (groupHandlerMap.get(device.id()) == null) {
911 - DefaultGroupHandler groupHandler;
912 - try {
913 - groupHandler = DefaultGroupHandler.
914 - createGroupHandler(device.id(),
915 - appId,
916 - deviceConfiguration,
917 - linkService,
918 - flowObjectiveService,
919 - segmentRoutingManager);
920 - } catch (DeviceConfigNotFoundException e) {
921 - log.warn(e.getMessage() + " Aborting configureNetwork.");
922 - return;
923 - }
924 - log.debug("updating groupHandlerMap with new config for "
925 - + "device: {}", device.id());
926 - groupHandlerMap.put(device.id(), groupHandler);
927 -
928 - // Also, in some cases, drivers may need extra
929 - // information to process rules (eg. Router IP/MAC); and so, we send
930 - // port addressing rules to the driver as well, irrespective of whether
931 - // this instance is the master or not.
932 - defaultRoutingHandler.populatePortAddressingRules(device.id());
933 - }
934 - if (mastershipService.isLocalMaster(device.id())) {
935 - hostHandler.readInitialHosts(device.id());
936 - DefaultGroupHandler groupHandler = groupHandlerMap.get(device.id());
937 - groupHandler.createGroupsFromSubnetConfig();
938 - routingRulePopulator.populateSubnetBroadcastRule(device.id());
939 - groupHandler.createGroupsForXConnect(device.id());
940 - routingRulePopulator.populateXConnectBroadcastRule(device.id());
941 - }
942 } 910 }
943 911
944 defaultRoutingHandler.startPopulationProcess(); 912 defaultRoutingHandler.startPopulationProcess();
913 + mcastHandler.init();
945 } 914 }
946 915
947 @Override 916 @Override
......
...@@ -26,7 +26,7 @@ import java.util.Objects; ...@@ -26,7 +26,7 @@ import java.util.Objects;
26 /** 26 /**
27 * Key of multicast next objective store. 27 * Key of multicast next objective store.
28 */ 28 */
29 -public class McastNextObjectiveStoreKey { 29 +public class McastStoreKey {
30 private final IpAddress mcastIp; 30 private final IpAddress mcastIp;
31 private final DeviceId deviceId; 31 private final DeviceId deviceId;
32 32
...@@ -36,7 +36,7 @@ public class McastNextObjectiveStoreKey { ...@@ -36,7 +36,7 @@ public class McastNextObjectiveStoreKey {
36 * @param mcastIp multicast group IP address 36 * @param mcastIp multicast group IP address
37 * @param deviceId device ID 37 * @param deviceId device ID
38 */ 38 */
39 - public McastNextObjectiveStoreKey(IpAddress mcastIp, DeviceId deviceId) { 39 + public McastStoreKey(IpAddress mcastIp, DeviceId deviceId) {
40 checkNotNull(mcastIp, "mcastIp cannot be null"); 40 checkNotNull(mcastIp, "mcastIp cannot be null");
41 checkNotNull(deviceId, "deviceId cannot be null"); 41 checkNotNull(deviceId, "deviceId cannot be null");
42 checkArgument(mcastIp.isMulticast(), "mcastIp must be a multicast address"); 42 checkArgument(mcastIp.isMulticast(), "mcastIp must be a multicast address");
...@@ -67,11 +67,11 @@ public class McastNextObjectiveStoreKey { ...@@ -67,11 +67,11 @@ public class McastNextObjectiveStoreKey {
67 if (this == o) { 67 if (this == o) {
68 return true; 68 return true;
69 } 69 }
70 - if (!(o instanceof McastNextObjectiveStoreKey)) { 70 + if (!(o instanceof McastStoreKey)) {
71 return false; 71 return false;
72 } 72 }
73 - McastNextObjectiveStoreKey that = 73 + McastStoreKey that =
74 - (McastNextObjectiveStoreKey) o; 74 + (McastStoreKey) o;
75 return (Objects.equals(this.mcastIp, that.mcastIp) && 75 return (Objects.equals(this.mcastIp, that.mcastIp) &&
76 Objects.equals(this.deviceId, that.deviceId)); 76 Objects.equals(this.deviceId, that.deviceId));
77 } 77 }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
5 <meta http-equiv="Content-Style-Type" content="text/css"> 5 <meta http-equiv="Content-Style-Type" content="text/css">
6 <title></title> 6 <title></title>
7 <meta name="Generator" content="Cocoa HTML Writer"> 7 <meta name="Generator" content="Cocoa HTML Writer">
8 - <meta name="CocoaVersion" content="1404.34"> 8 + <meta name="CocoaVersion" content="1404.46">
9 <style type="text/css"> 9 <style type="text/css">
10 p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #0433ff; -webkit-text-stroke: #0433ff} 10 p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #0433ff; -webkit-text-stroke: #0433ff}
11 p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #ff9300; -webkit-text-stroke: #ff9300} 11 p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #ff9300; -webkit-text-stroke: #ff9300}
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
14 p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #00c7fc; -webkit-text-stroke: #00c7fc} 14 p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #00c7fc; -webkit-text-stroke: #00c7fc}
15 p.p6 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #ff2600; -webkit-text-stroke: #ff2600} 15 p.p6 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #ff2600; -webkit-text-stroke: #ff2600}
16 p.p7 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #000000; -webkit-text-stroke: #000000} 16 p.p7 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #000000; -webkit-text-stroke: #000000}
17 + p.p8 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 14.0px; font: 12.0px Menlo; color: #00c7fc; -webkit-text-stroke: #000000}
17 span.s1 {font-kerning: none} 18 span.s1 {font-kerning: none}
18 span.s2 {font-kerning: none; color: #0433ff; -webkit-text-stroke: 0px #0433ff} 19 span.s2 {font-kerning: none; color: #0433ff; -webkit-text-stroke: 0px #0433ff}
19 span.s3 {font-kerning: none; color: #000000; -webkit-text-stroke: 0px #000000} 20 span.s3 {font-kerning: none; color: #000000; -webkit-text-stroke: 0px #000000}
...@@ -22,7 +23,8 @@ ...@@ -22,7 +23,8 @@
22 span.s6 {font-kerning: none; color: #77bb41; -webkit-text-stroke: 0px #77bb41} 23 span.s6 {font-kerning: none; color: #77bb41; -webkit-text-stroke: 0px #77bb41}
23 span.s7 {font-kerning: none; color: #00c7fc; -webkit-text-stroke: 0px #00c7fc} 24 span.s7 {font-kerning: none; color: #00c7fc; -webkit-text-stroke: 0px #00c7fc}
24 span.s8 {font-kerning: none; color: #ff2600; -webkit-text-stroke: 0px #ff2600} 25 span.s8 {font-kerning: none; color: #ff2600; -webkit-text-stroke: 0px #ff2600}
25 - span.s9 {font-kerning: none; color: #669c35; -webkit-text-stroke: 0px #669c35} 26 + span.s9 {font-kerning: none; color: #000000}
27 + span.s10 {font-kerning: none; color: #669c35; -webkit-text-stroke: 0px #669c35}
26 span.Apple-tab-span {white-space:pre} 28 span.Apple-tab-span {white-space:pre}
27 </style> 29 </style>
28 </head> 30 </head>
...@@ -31,7 +33,7 @@ ...@@ -31,7 +33,7 @@
31 <p class="p2"><span class="s1">Orange: vSG LAN connectivity (cross-connect)</span></p> 33 <p class="p2"><span class="s1">Orange: vSG LAN connectivity (cross-connect)</span></p>
32 <p class="p3"><span class="s1">Magenta: vSG WAN connectivity (default route)</span></p> 34 <p class="p3"><span class="s1">Magenta: vSG WAN connectivity (default route)</span></p>
33 <p class="p4"><span class="s1">Green: vRouter integration</span></p> 35 <p class="p4"><span class="s1">Green: vRouter integration</span></p>
34 -<p class="p5"><span class="s1">Light Blue: PIM integration</span></p> 36 +<p class="p5"><span class="s1">Light Blue: Multicast</span></p>
35 <p class="p6"><span class="s1">Red: Link validation</span></p> 37 <p class="p6"><span class="s1">Red: Link validation</span></p>
36 <p class="p7"><span class="s1"><br> 38 <p class="p7"><span class="s1"><br>
37 </span></p> 39 </span></p>
...@@ -221,7 +223,11 @@ ...@@ -221,7 +223,11 @@
221 <p class="p7"><span class="s1"><span class="Apple-converted-space">        </span>"org.onosproject.core" : {</span></p> 223 <p class="p7"><span class="s1"><span class="Apple-converted-space">        </span>"org.onosproject.core" : {</span></p>
222 <p class="p7"><span class="s1"><span class="Apple-converted-space">            </span>"core" : {</span></p> 224 <p class="p7"><span class="s1"><span class="Apple-converted-space">            </span>"core" : {</span></p>
223 <p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"linkDiscoveryMode" : "STRICT" </span><span class="s8">// enable strict link validation</span></p> 225 <p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"linkDiscoveryMode" : "STRICT" </span><span class="s8">// enable strict link validation</span></p>
224 -<p class="p7"><span class="s1"><span class="Apple-converted-space">            </span>} <span class="Apple-converted-space">   </span></span></p> 226 +<p class="p7"><span class="s1"><span class="Apple-converted-space">            </span>},</span></p>
227 +<p class="p8"><span class="s9"><span class="Apple-tab-span"> </span><span class="Apple-tab-span"> </span><span class="Apple-converted-space">  </span>"multicast": { </span><span class="s1">// The VLAN we expect to see on the multicast ingress and egress</span></p>
228 +<p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"ingressVlan": "None",</span></p>
229 +<p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"egressVlan": "None"</span></p>
230 +<p class="p7"><span class="s1"><span class="Apple-converted-space">            </span>}<span class="Apple-converted-space"> </span></span></p>
225 <p class="p7"><span class="s1"><span class="Apple-converted-space">        </span>},</span></p> 231 <p class="p7"><span class="s1"><span class="Apple-converted-space">        </span>},</span></p>
226 <p class="p7"><span class="s1"><span class="Apple-converted-space">        </span>"org.onosproject.segmentrouting" : {</span></p> 232 <p class="p7"><span class="s1"><span class="Apple-converted-space">        </span>"org.onosproject.segmentrouting" : {</span></p>
227 <p class="p7"><span class="s1"><span class="Apple-converted-space">            </span>"segmentrouting" : {</span></p> 233 <p class="p7"><span class="s1"><span class="Apple-converted-space">            </span>"segmentrouting" : {</span></p>
...@@ -243,7 +249,7 @@ ...@@ -243,7 +249,7 @@
243 <p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"controlPlaneConnectPoint" : "of:0000000000000002/31", </span><span class="s6">// location of Quagga</span></p> 249 <p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"controlPlaneConnectPoint" : "of:0000000000000002/31", </span><span class="s6">// location of Quagga</span></p>
244 <p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"ospfEnabled" : "true", </span><span class="s6">// enable OSPF</span></p> 250 <p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"ospfEnabled" : "true", </span><span class="s6">// enable OSPF</span></p>
245 <p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"pimEnabled" : "true", </span><span class="s7">// enable PIM</span></p> 251 <p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"pimEnabled" : "true", </span><span class="s7">// enable PIM</span></p>
246 -<p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"interfaces" : [ "external-quagga" ] </span><span class="s9">// </span><span class="s6">VR only handles peers on these ports</span></p> 252 +<p class="p7"><span class="s1"><span class="Apple-converted-space">                </span>"interfaces" : [ "external-quagga" ] </span><span class="s10">// </span><span class="s6">VR only handles peers on these ports</span></p>
247 <p class="p7"><span class="s1"><span class="Apple-converted-space">            </span>}</span></p> 253 <p class="p7"><span class="s1"><span class="Apple-converted-space">            </span>}</span></p>
248 <p class="p7"><span class="s1"><span class="Apple-converted-space">        </span>}</span></p> 254 <p class="p7"><span class="s1"><span class="Apple-converted-space">        </span>}</span></p>
249 <p class="p7"><span class="s1"><span class="Apple-converted-space">    </span>}</span></p> 255 <p class="p7"><span class="s1"><span class="Apple-converted-space">    </span>}</span></p>
......
...@@ -228,6 +228,10 @@ ...@@ -228,6 +228,10 @@
228 "org.onosproject.core" : { 228 "org.onosproject.core" : {
229 "core" : { 229 "core" : {
230 "linkDiscoveryMode" : "STRICT" 230 "linkDiscoveryMode" : "STRICT"
231 + },
232 + "multicast": {
233 + "ingressVlan": "None",
234 + "egressVlan": "None"
231 } 235 }
232 }, 236 },
233 "org.onosproject.segmentrouting" : { 237 "org.onosproject.segmentrouting" : {
......