blob: 18f1e8ca214d6bd2da605553cd7d81a4bff8bf04 [file] [log] [blame]
wu5f6c5b82017-08-04 16:45:19 +08001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.pi.demo.app.common;
18
Carmelo Cascone6e854042017-09-11 21:37:53 +020019import com.google.common.base.MoreObjects;
wu5f6c5b82017-08-04 16:45:19 +080020import com.google.common.collect.ImmutableList;
21import com.google.common.collect.ImmutableMap;
22import com.google.common.collect.ImmutableSet;
23import com.google.common.collect.Lists;
24import com.google.common.collect.Maps;
25import com.google.common.collect.Sets;
26import org.apache.felix.scr.annotations.Activate;
27import org.apache.felix.scr.annotations.Component;
28import org.apache.felix.scr.annotations.Deactivate;
29import org.apache.felix.scr.annotations.Reference;
30import org.apache.felix.scr.annotations.ReferenceCardinality;
31import org.onosproject.app.ApplicationAdminService;
32import org.onosproject.core.ApplicationId;
33import org.onosproject.core.CoreService;
34import org.onosproject.net.ConnectPoint;
35import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.Host;
38import org.onosproject.net.Port;
39import org.onosproject.net.device.DeviceEvent;
40import org.onosproject.net.device.DeviceListener;
41import org.onosproject.net.device.DeviceService;
42import org.onosproject.net.flow.DefaultFlowRule;
43import org.onosproject.net.flow.FlowRule;
44import org.onosproject.net.flow.FlowRuleOperations;
45import org.onosproject.net.flow.FlowRuleService;
wu5f6c5b82017-08-04 16:45:19 +080046import org.onosproject.net.host.HostService;
47import org.onosproject.net.pi.model.PiPipeconf;
Carmelo Cascone6e854042017-09-11 21:37:53 +020048import org.onosproject.net.pi.model.PiPipeconfId;
wu5f6c5b82017-08-04 16:45:19 +080049import org.onosproject.net.pi.model.PiPipelineInterpreter;
50import org.onosproject.net.pi.runtime.PiPipeconfService;
51import org.onosproject.net.pi.runtime.PiTableId;
52import org.onosproject.net.topology.Topology;
wu5f6c5b82017-08-04 16:45:19 +080053import org.onosproject.net.topology.TopologyGraph;
wu5f6c5b82017-08-04 16:45:19 +080054import org.onosproject.net.topology.TopologyService;
55import org.onosproject.net.topology.TopologyVertex;
56import org.slf4j.Logger;
57
58import java.util.Collection;
wu5f6c5b82017-08-04 16:45:19 +080059import java.util.List;
60import java.util.Map;
61import java.util.Set;
62import java.util.concurrent.ConcurrentMap;
63import java.util.concurrent.ExecutorService;
64import java.util.concurrent.Executors;
Carmelo Cascone3929cc82017-09-06 13:34:25 +020065import java.util.concurrent.ScheduledExecutorService;
wu5f6c5b82017-08-04 16:45:19 +080066import java.util.concurrent.TimeUnit;
67import java.util.concurrent.locks.Lock;
68import java.util.concurrent.locks.ReentrantLock;
69import java.util.stream.Collectors;
70import java.util.stream.Stream;
71
Carmelo Cascone6e854042017-09-11 21:37:53 +020072import static com.google.common.base.Preconditions.checkArgument;
wu5f6c5b82017-08-04 16:45:19 +080073import static com.google.common.base.Preconditions.checkNotNull;
Carmelo Cascone1fb27d32017-08-25 20:40:20 +020074import static java.lang.String.format;
wu5f6c5b82017-08-04 16:45:19 +080075import static java.util.stream.Collectors.toSet;
76import static java.util.stream.Stream.concat;
77import static org.onlab.util.Tools.groupedThreads;
78import static org.onosproject.net.device.DeviceEvent.Type.*;
wu5f6c5b82017-08-04 16:45:19 +080079import static org.slf4j.LoggerFactory.getLogger;
80
81/**
82 * Abstract implementation of an app providing fabric connectivity for a 2-stage Clos topology of P4Runtime devices.
83 */
84@Component(immediate = true)
85public abstract class AbstractUpgradableFabricApp {
86
87 private static final Map<String, AbstractUpgradableFabricApp> APP_HANDLES = Maps.newConcurrentMap();
88
Carmelo Cascone3929cc82017-09-06 13:34:25 +020089 // TOPO_SIZE should be the same of the --size argument when running bmv2-demo.py
90 private static final int TOPO_SIZE = 2;
91 private static final boolean WITH_IMBALANCED_STRIPING = false;
92 protected static final int HASHED_LINKS = TOPO_SIZE + (WITH_IMBALANCED_STRIPING ? 1 : 0);
93
wu5f6c5b82017-08-04 16:45:19 +080094 private static final int FLOW_PRIORITY = 100;
Carmelo Cascone3929cc82017-09-06 13:34:25 +020095 private static final int CHECK_TOPOLOGY_INTERVAL_SECONDS = 5;
wu5f6c5b82017-08-04 16:45:19 +080096
97 private static final int CLEANUP_SLEEP = 2000;
98
99 protected final Logger log = getLogger(getClass());
100
wu5f6c5b82017-08-04 16:45:19 +0800101 private final DeviceListener deviceListener = new InternalDeviceListener();
wu5f6c5b82017-08-04 16:45:19 +0800102
103 private final ExecutorService executorService = Executors
104 .newFixedThreadPool(8, groupedThreads("onos/pi-demo-app", "pi-app-task", log));
105
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200106 private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
107
wu5f6c5b82017-08-04 16:45:19 +0800108 private final String appName;
wu5f6c5b82017-08-04 16:45:19 +0800109
110 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
111 protected TopologyService topologyService;
112
113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected DeviceService deviceService;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
117 private HostService hostService;
118
119 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
120 private FlowRuleService flowRuleService;
121
122 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
123 private ApplicationAdminService appService;
124
125 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
126 private CoreService coreService;
127
128 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
129 private PiPipeconfService piPipeconfService;
130
131 private boolean appActive = false;
132 private boolean appFreezed = false;
133
134 private boolean otherAppFound = false;
135 private AbstractUpgradableFabricApp otherApp;
136
137 private boolean flowRuleGenerated = false;
138 private ApplicationId appId;
139
Carmelo Cascone6e854042017-09-11 21:37:53 +0200140 private Collection<PiPipeconf> appPipeconfs;
wu5f6c5b82017-08-04 16:45:19 +0800141
142 private Set<DeviceId> leafSwitches;
143 private Set<DeviceId> spineSwitches;
144
145 private Map<DeviceId, List<FlowRule>> deviceFlowRules;
146 private Map<DeviceId, Boolean> pipeconfFlags;
147 private Map<DeviceId, Boolean> ruleFlags;
148
149 private ConcurrentMap<DeviceId, Lock> deviceLocks = Maps.newConcurrentMap();
150
151 /**
152 * Creates a new PI fabric app.
153 *
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200154 * @param appName app name
Carmelo Cascone6e854042017-09-11 21:37:53 +0200155 * @param appPipeconfs collection of compatible pipeconfs
wu5f6c5b82017-08-04 16:45:19 +0800156 */
Carmelo Cascone6e854042017-09-11 21:37:53 +0200157 protected AbstractUpgradableFabricApp(String appName, Collection<PiPipeconf> appPipeconfs) {
wu5f6c5b82017-08-04 16:45:19 +0800158 this.appName = checkNotNull(appName);
Carmelo Cascone6e854042017-09-11 21:37:53 +0200159 this.appPipeconfs = checkNotNull(appPipeconfs);
160 checkArgument(appPipeconfs.size() > 0, "appPipeconfs cannot have size 0");
wu5f6c5b82017-08-04 16:45:19 +0800161 }
162
163 @Activate
164 public void activate() {
165 log.info("Starting...");
166
167 appActive = true;
168 appFreezed = false;
169
170 if (APP_HANDLES.size() > 0) {
171 if (APP_HANDLES.size() > 1) {
172 throw new IllegalStateException("Found more than 1 active app handles");
173 }
174 otherAppFound = true;
175 otherApp = APP_HANDLES.values().iterator().next();
176 log.info("Found other fabric app active, signaling to freeze to {}...", otherApp.appName);
177 otherApp.setAppFreezed(true);
178 }
179
180 APP_HANDLES.put(appName, this);
181
182 appId = coreService.registerApplication(appName);
wu5f6c5b82017-08-04 16:45:19 +0800183 deviceService.addListener(deviceListener);
Carmelo Cascone6e854042017-09-11 21:37:53 +0200184 appPipeconfs.forEach(piPipeconfService::register);
wu5f6c5b82017-08-04 16:45:19 +0800185
186 init();
187
188 log.info("STARTED", appId.id());
189 }
190
191 @Deactivate
192 public void deactivate() {
193 log.info("Stopping...");
194 try {
195 executorService.shutdown();
196 executorService.awaitTermination(5, TimeUnit.SECONDS);
197 } catch (InterruptedException e) {
198 List<Runnable> runningTasks = executorService.shutdownNow();
199 log.warn("Unable to stop the following tasks: {}", runningTasks);
200 }
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200201 scheduledExecutorService.shutdown();
wu5f6c5b82017-08-04 16:45:19 +0800202 deviceService.removeListener(deviceListener);
wu5f6c5b82017-08-04 16:45:19 +0800203 flowRuleService.removeFlowRulesById(appId);
Carmelo Cascone6e854042017-09-11 21:37:53 +0200204 appPipeconfs.stream()
205 .map(PiPipeconf::id)
206 .forEach(piPipeconfService::remove);
wu5f6c5b82017-08-04 16:45:19 +0800207
208 appActive = false;
209 APP_HANDLES.remove(appName);
210
211 log.info("STOPPED");
212 }
213
214 private void init() {
215
216 // Reset any previous state
217 synchronized (this) {
218 flowRuleGenerated = Boolean.FALSE;
219 leafSwitches = Sets.newHashSet();
220 spineSwitches = Sets.newHashSet();
221 deviceFlowRules = Maps.newConcurrentMap();
222 ruleFlags = Maps.newConcurrentMap();
223 pipeconfFlags = Maps.newConcurrentMap();
224 }
225
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200226 /*
227 Schedules a thread that periodically checks the topology, as soon as it corresponds to the expected
228 one, it generates the necessary flow rules and starts the deploy process on each device.
229 */
230 scheduledExecutorService.scheduleAtFixedRate(this::checkTopologyAndGenerateFlowRules,
231 0, CHECK_TOPOLOGY_INTERVAL_SECONDS, TimeUnit.SECONDS);
wu5f6c5b82017-08-04 16:45:19 +0800232 }
233
234 private void setAppFreezed(boolean appFreezed) {
235 this.appFreezed = appFreezed;
236 if (appFreezed) {
237 log.info("Freezing...");
238 } else {
239 log.info("Unfreezing...!");
240 }
241 }
242
243 /**
244 * Perform device initialization. Returns true if the operation was successful, false otherwise.
245 *
246 * @param deviceId a device id
247 * @return a boolean value
248 */
249 public abstract boolean initDevice(DeviceId deviceId);
250
251 /**
252 * Generates a list of flow rules for the given leaf switch, source host, destination hosts, spine switches and
253 * topology.
254 *
255 * @param leaf a leaf device id
256 * @param srcHost a source host
257 * @param dstHosts a collection of destination hosts
258 * @param spines a collection of spine device IDs
259 * @param topology a topology
260 * @return a list of flow rules
261 * @throws FlowRuleGeneratorException if flow rules cannot be generated
262 */
263 public abstract List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
264 Collection<DeviceId> spines, Topology topology)
265 throws FlowRuleGeneratorException;
266
267 /**
268 * Generates a list of flow rules for the given spine switch, destination hosts and topology.
269 *
270 * @param deviceId a spine device id
271 * @param dstHosts a collection of destination hosts
272 * @param topology a topology
273 * @return a list of flow rules
274 * @throws FlowRuleGeneratorException if flow rules cannot be generated
275 */
276 public abstract List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topology)
277 throws FlowRuleGeneratorException;
278
279 private void deployAllDevices() {
280 if (otherAppFound && otherApp.appActive) {
281 log.info("Deactivating other app...");
282 appService.deactivate(otherApp.appId);
283 try {
284 Thread.sleep(CLEANUP_SLEEP);
285 } catch (InterruptedException e) {
286 log.warn("Cleanup sleep interrupted!");
287 Thread.interrupted();
288 }
289 }
290
291 Stream.concat(leafSwitches.stream(), spineSwitches.stream())
292 .map(deviceService::getDevice)
293 .forEach(device -> spawnTask(() -> deployDevice(device)));
294 }
295
Carmelo Cascone6e854042017-09-11 21:37:53 +0200296 private boolean matchPipeconf(PiPipeconfId piPipeconfId) {
297 return appPipeconfs.stream()
298 .anyMatch(p -> p.id().equals(piPipeconfId));
299 }
300
wu5f6c5b82017-08-04 16:45:19 +0800301 /**
302 * Executes a device deploy.
303 *
304 * @param device a device
305 */
306 public void deployDevice(Device device) {
307
308 DeviceId deviceId = device.id();
309
310 // Synchronize executions over the same device.
311 Lock lock = deviceLocks.computeIfAbsent(deviceId, k -> new ReentrantLock());
312 lock.lock();
313
314 try {
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200315 // Set pipeconf flag if not already done.
wu5f6c5b82017-08-04 16:45:19 +0800316 if (!pipeconfFlags.getOrDefault(deviceId, false)) {
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200317 if (piPipeconfService.ofDevice(deviceId).isPresent() &&
Carmelo Cascone6e854042017-09-11 21:37:53 +0200318 matchPipeconf(piPipeconfService.ofDevice(deviceId).get())) {
wu5f6c5b82017-08-04 16:45:19 +0800319 pipeconfFlags.put(device.id(), true);
320 } else {
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200321 log.warn("Wrong pipeconf for {}, expecting {}, but found {}, aborting deploy",
Carmelo Cascone6e854042017-09-11 21:37:53 +0200322 deviceId, MoreObjects.toStringHelper(appPipeconfs),
323 piPipeconfService.ofDevice(deviceId).get());
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200324 return;
wu5f6c5b82017-08-04 16:45:19 +0800325 }
326 }
327
328 // Initialize device.
329 if (!initDevice(deviceId)) {
330 log.warn("Failed to initialize device {}", deviceId);
331 }
332
333 // Install rules.
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200334 if (!ruleFlags.getOrDefault(deviceId, false) &&
335 deviceFlowRules.containsKey(deviceId)) {
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200336 log.info("Installing {} rules for {}...", deviceFlowRules.get(deviceId).size(), deviceId);
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200337 installFlowRules(deviceFlowRules.get(deviceId));
338 ruleFlags.put(deviceId, true);
wu5f6c5b82017-08-04 16:45:19 +0800339 }
340 } finally {
341 lock.unlock();
342 }
343 }
344
345 private void spawnTask(Runnable task) {
346 executorService.execute(task);
347 }
348
349
350 private void installFlowRules(Collection<FlowRule> rules) {
351 FlowRuleOperations.Builder opsBuilder = FlowRuleOperations.builder();
352 rules.forEach(opsBuilder::add);
353 flowRuleService.apply(opsBuilder.build());
354 }
355
wu5f6c5b82017-08-04 16:45:19 +0800356 /**
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200357 * Generates flow rules to provide host-to-host connectivity for the given topology and hosts.
wu5f6c5b82017-08-04 16:45:19 +0800358 */
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200359 private synchronized void checkTopologyAndGenerateFlowRules() {
360
361 Topology topo = topologyService.currentTopology();
362 Set<Host> hosts = Sets.newHashSet(hostService.getHosts());
wu5f6c5b82017-08-04 16:45:19 +0800363
364 if (flowRuleGenerated) {
365 log.debug("Flow rules have been already generated, aborting...");
366 return;
367 }
368
369 log.debug("Starting flow rules generator...");
370
371 TopologyGraph graph = topologyService.getGraph(topo);
372 Set<DeviceId> spines = Sets.newHashSet();
373 Set<DeviceId> leafs = Sets.newHashSet();
374 graph.getVertexes().stream()
375 .map(TopologyVertex::deviceId)
376 .forEach(did -> (isSpine(did, topo) ? spines : leafs).add(did));
377
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200378 if (spines.size() != TOPO_SIZE || leafs.size() != TOPO_SIZE) {
wu5f6c5b82017-08-04 16:45:19 +0800379 log.info("Invalid leaf/spine switches count, aborting... > leafCount={}, spineCount={}",
380 spines.size(), leafs.size());
381 return;
382 }
383
384 for (DeviceId did : spines) {
385 int portCount = deviceService.getPorts(did).size();
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200386 // Expected port count: num leafs + 1 redundant leaf link (if imbalanced)
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200387 if (portCount != HASHED_LINKS) {
wu5f6c5b82017-08-04 16:45:19 +0800388 log.info("Invalid port count for spine, aborting... > deviceId={}, portCount={}", did, portCount);
389 return;
390 }
391 }
392 for (DeviceId did : leafs) {
393 int portCount = deviceService.getPorts(did).size();
394 // Expected port count: num spines + host port + 1 redundant spine link
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200395 if (portCount != HASHED_LINKS + 1) {
wu5f6c5b82017-08-04 16:45:19 +0800396 log.info("Invalid port count for leaf, aborting... > deviceId={}, portCount={}", did, portCount);
397 return;
398 }
399 }
400
401 // Check hosts, number and exactly one per leaf
402 Map<DeviceId, Host> hostMap = Maps.newHashMap();
403 hosts.forEach(h -> hostMap.put(h.location().deviceId(), h));
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200404 if (hosts.size() != TOPO_SIZE || !leafs.equals(hostMap.keySet())) {
wu5f6c5b82017-08-04 16:45:19 +0800405 log.info("Wrong host configuration, aborting... > hostCount={}, hostMapz={}", hosts.size(), hostMap);
406 return;
407 }
408
409 List<FlowRule> newFlowRules = Lists.newArrayList();
410
411 try {
412 for (DeviceId deviceId : leafs) {
413 Host srcHost = hostMap.get(deviceId);
414 Set<Host> dstHosts = hosts.stream().filter(h -> h != srcHost).collect(toSet());
415 newFlowRules.addAll(generateLeafRules(deviceId, srcHost, dstHosts, spines, topo));
416 }
417 for (DeviceId deviceId : spines) {
418 newFlowRules.addAll(generateSpineRules(deviceId, hosts, topo));
419 }
420 } catch (FlowRuleGeneratorException e) {
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200421 log.warn("Exception while executing flow rule generator: {}", e.getMessage());
wu5f6c5b82017-08-04 16:45:19 +0800422 return;
423 }
424
425 if (newFlowRules.size() == 0) {
426 // Something went wrong
427 log.error("0 flow rules generated, BUG?");
428 return;
429 }
430
431 // All good!
432 // Divide flow rules per device id...
433 ImmutableMap.Builder<DeviceId, List<FlowRule>> mapBuilder = ImmutableMap.builder();
434 concat(spines.stream(), leafs.stream())
435 .map(deviceId -> ImmutableList.copyOf(newFlowRules
436 .stream()
437 .filter(fr -> fr.deviceId().equals(deviceId))
438 .iterator()))
439 .forEach(frs -> mapBuilder.put(frs.get(0).deviceId(), frs));
440 this.deviceFlowRules = mapBuilder.build();
441
442 this.leafSwitches = ImmutableSet.copyOf(leafs);
443 this.spineSwitches = ImmutableSet.copyOf(spines);
444
445 // Avoid other executions to modify the generated flow rules.
446 flowRuleGenerated = true;
447
448 log.info("Generated {} flow rules for {} devices", newFlowRules.size(), spines.size() + leafs.size());
449
450 spawnTask(this::deployAllDevices);
451 }
452
453 /**
454 * Returns a new, pre-configured flow rule builder.
455 *
456 * @param did a device id
457 * @param tableName a table name
458 * @return a new flow rule builder
459 */
460 protected FlowRule.Builder flowRuleBuilder(DeviceId did, String tableName) throws FlowRuleGeneratorException {
461
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200462 final Device device = deviceService.getDevice(did);
463 if (!device.is(PiPipelineInterpreter.class)) {
464 throw new FlowRuleGeneratorException(format("Device %s has no PiPipelineInterpreter", did));
wu5f6c5b82017-08-04 16:45:19 +0800465 }
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200466 final PiPipelineInterpreter interpreter = device.as(PiPipelineInterpreter.class);
467 final int flowRuleTableId;
wu5f6c5b82017-08-04 16:45:19 +0800468 if (interpreter.mapPiTableId(PiTableId.of(tableName)).isPresent()) {
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200469 flowRuleTableId = interpreter.mapPiTableId(PiTableId.of(tableName)).get();
wu5f6c5b82017-08-04 16:45:19 +0800470 } else {
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200471 throw new FlowRuleGeneratorException(format("Unknown table %s in interpreter", tableName));
wu5f6c5b82017-08-04 16:45:19 +0800472 }
473
474 return DefaultFlowRule.builder()
475 .forDevice(did)
476 .forTable(flowRuleTableId)
477 .fromApp(appId)
478 .withPriority(FLOW_PRIORITY)
479 .makePermanent();
480 }
481
482 private List<Port> getHostPorts(DeviceId deviceId, Topology topology) {
483 // Get all non-fabric ports.
484 return deviceService
485 .getPorts(deviceId)
486 .stream()
487 .filter(p -> !isFabricPort(p, topology))
488 .collect(Collectors.toList());
489 }
490
491 private boolean isSpine(DeviceId deviceId, Topology topology) {
492 // True if all ports are fabric.
493 return getHostPorts(deviceId, topology).size() == 0;
494 }
495
496 protected boolean isFabricPort(Port port, Topology topology) {
497 // True if the port connects this device to another infrastructure device.
498 return topologyService.isInfrastructure(topology, new ConnectPoint(port.element().id(), port.number()));
499 }
500
501 /**
wu5f6c5b82017-08-04 16:45:19 +0800502 * A listener of device events that executes a device deploy task each time a device is added, updated or
503 * re-connects.
504 */
505 private class InternalDeviceListener implements DeviceListener {
506 @Override
507 public void event(DeviceEvent event) {
508 spawnTask(() -> deployDevice(event.subject()));
509 }
510
511 @Override
512 public boolean isRelevant(DeviceEvent event) {
513 return !appFreezed &&
514 (event.type() == DEVICE_ADDED ||
515 event.type() == DEVICE_UPDATED ||
516 (event.type() == DEVICE_AVAILABILITY_CHANGED &&
517 deviceService.isAvailable(event.subject().id())));
518 }
519 }
520
521 /**
wu5f6c5b82017-08-04 16:45:19 +0800522 * An exception occurred while generating flow rules for this fabric.
523 */
524 public class FlowRuleGeneratorException extends Exception {
525
526 public FlowRuleGeneratorException() {
527 }
528
529 public FlowRuleGeneratorException(String msg) {
530 super(msg);
531 }
wu5f6c5b82017-08-04 16:45:19 +0800532 }
533}