blob: d3c1a14c43f75d2622e136889a9b4fa49eb3ca13 [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;
Carmelo Casconeca94bcf2017-10-27 14:16:59 -070078import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
79import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED;
80import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_UPDATED;
wu5f6c5b82017-08-04 16:45:19 +080081import static org.slf4j.LoggerFactory.getLogger;
82
83/**
84 * Abstract implementation of an app providing fabric connectivity for a 2-stage Clos topology of P4Runtime devices.
85 */
86@Component(immediate = true)
87public abstract class AbstractUpgradableFabricApp {
88
89 private static final Map<String, AbstractUpgradableFabricApp> APP_HANDLES = Maps.newConcurrentMap();
90
Carmelo Cascone3929cc82017-09-06 13:34:25 +020091 // TOPO_SIZE should be the same of the --size argument when running bmv2-demo.py
92 private static final int TOPO_SIZE = 2;
93 private static final boolean WITH_IMBALANCED_STRIPING = false;
94 protected static final int HASHED_LINKS = TOPO_SIZE + (WITH_IMBALANCED_STRIPING ? 1 : 0);
95
wu5f6c5b82017-08-04 16:45:19 +080096 private static final int FLOW_PRIORITY = 100;
Carmelo Cascone3929cc82017-09-06 13:34:25 +020097 private static final int CHECK_TOPOLOGY_INTERVAL_SECONDS = 5;
wu5f6c5b82017-08-04 16:45:19 +080098
99 private static final int CLEANUP_SLEEP = 2000;
100
101 protected final Logger log = getLogger(getClass());
102
wu5f6c5b82017-08-04 16:45:19 +0800103 private final DeviceListener deviceListener = new InternalDeviceListener();
wu5f6c5b82017-08-04 16:45:19 +0800104
105 private final ExecutorService executorService = Executors
106 .newFixedThreadPool(8, groupedThreads("onos/pi-demo-app", "pi-app-task", log));
107
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200108 private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
109
wu5f6c5b82017-08-04 16:45:19 +0800110 private final String appName;
wu5f6c5b82017-08-04 16:45:19 +0800111
112 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
113 protected TopologyService topologyService;
114
115 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
116 protected DeviceService deviceService;
117
118 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
119 private HostService hostService;
120
121 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
122 private FlowRuleService flowRuleService;
123
124 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
125 private ApplicationAdminService appService;
126
127 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
128 private CoreService coreService;
129
130 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
131 private PiPipeconfService piPipeconfService;
132
133 private boolean appActive = false;
134 private boolean appFreezed = false;
135
136 private boolean otherAppFound = false;
137 private AbstractUpgradableFabricApp otherApp;
138
139 private boolean flowRuleGenerated = false;
140 private ApplicationId appId;
141
Carmelo Cascone6e854042017-09-11 21:37:53 +0200142 private Collection<PiPipeconf> appPipeconfs;
wu5f6c5b82017-08-04 16:45:19 +0800143
144 private Set<DeviceId> leafSwitches;
145 private Set<DeviceId> spineSwitches;
146
147 private Map<DeviceId, List<FlowRule>> deviceFlowRules;
148 private Map<DeviceId, Boolean> pipeconfFlags;
149 private Map<DeviceId, Boolean> ruleFlags;
150
151 private ConcurrentMap<DeviceId, Lock> deviceLocks = Maps.newConcurrentMap();
152
153 /**
154 * Creates a new PI fabric app.
155 *
Carmelo Casconeca94bcf2017-10-27 14:16:59 -0700156 * @param appName app name
Carmelo Cascone6e854042017-09-11 21:37:53 +0200157 * @param appPipeconfs collection of compatible pipeconfs
wu5f6c5b82017-08-04 16:45:19 +0800158 */
Carmelo Cascone6e854042017-09-11 21:37:53 +0200159 protected AbstractUpgradableFabricApp(String appName, Collection<PiPipeconf> appPipeconfs) {
wu5f6c5b82017-08-04 16:45:19 +0800160 this.appName = checkNotNull(appName);
Carmelo Cascone6e854042017-09-11 21:37:53 +0200161 this.appPipeconfs = checkNotNull(appPipeconfs);
162 checkArgument(appPipeconfs.size() > 0, "appPipeconfs cannot have size 0");
wu5f6c5b82017-08-04 16:45:19 +0800163 }
164
165 @Activate
166 public void activate() {
167 log.info("Starting...");
168
169 appActive = true;
170 appFreezed = false;
171
172 if (APP_HANDLES.size() > 0) {
173 if (APP_HANDLES.size() > 1) {
174 throw new IllegalStateException("Found more than 1 active app handles");
175 }
176 otherAppFound = true;
177 otherApp = APP_HANDLES.values().iterator().next();
178 log.info("Found other fabric app active, signaling to freeze to {}...", otherApp.appName);
179 otherApp.setAppFreezed(true);
180 }
181
182 APP_HANDLES.put(appName, this);
183
184 appId = coreService.registerApplication(appName);
wu5f6c5b82017-08-04 16:45:19 +0800185 deviceService.addListener(deviceListener);
wu5f6c5b82017-08-04 16:45:19 +0800186
187 init();
188
189 log.info("STARTED", appId.id());
190 }
191
192 @Deactivate
193 public void deactivate() {
194 log.info("Stopping...");
195 try {
196 executorService.shutdown();
197 executorService.awaitTermination(5, TimeUnit.SECONDS);
198 } catch (InterruptedException e) {
199 List<Runnable> runningTasks = executorService.shutdownNow();
200 log.warn("Unable to stop the following tasks: {}", runningTasks);
201 }
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200202 scheduledExecutorService.shutdown();
wu5f6c5b82017-08-04 16:45:19 +0800203 deviceService.removeListener(deviceListener);
wu5f6c5b82017-08-04 16:45:19 +0800204 flowRuleService.removeFlowRulesById(appId);
wu5f6c5b82017-08-04 16:45:19 +0800205
206 appActive = false;
207 APP_HANDLES.remove(appName);
208
209 log.info("STOPPED");
210 }
211
212 private void init() {
213
214 // Reset any previous state
215 synchronized (this) {
216 flowRuleGenerated = Boolean.FALSE;
217 leafSwitches = Sets.newHashSet();
218 spineSwitches = Sets.newHashSet();
219 deviceFlowRules = Maps.newConcurrentMap();
220 ruleFlags = Maps.newConcurrentMap();
221 pipeconfFlags = Maps.newConcurrentMap();
222 }
223
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200224 /*
225 Schedules a thread that periodically checks the topology, as soon as it corresponds to the expected
226 one, it generates the necessary flow rules and starts the deploy process on each device.
227 */
228 scheduledExecutorService.scheduleAtFixedRate(this::checkTopologyAndGenerateFlowRules,
Carmelo Casconeca94bcf2017-10-27 14:16:59 -0700229 0, CHECK_TOPOLOGY_INTERVAL_SECONDS, TimeUnit.SECONDS);
wu5f6c5b82017-08-04 16:45:19 +0800230 }
231
232 private void setAppFreezed(boolean appFreezed) {
233 this.appFreezed = appFreezed;
234 if (appFreezed) {
235 log.info("Freezing...");
236 } else {
237 log.info("Unfreezing...!");
238 }
239 }
240
241 /**
242 * Perform device initialization. Returns true if the operation was successful, false otherwise.
243 *
244 * @param deviceId a device id
245 * @return a boolean value
246 */
247 public abstract boolean initDevice(DeviceId deviceId);
248
249 /**
250 * Generates a list of flow rules for the given leaf switch, source host, destination hosts, spine switches and
251 * topology.
252 *
253 * @param leaf a leaf device id
254 * @param srcHost a source host
255 * @param dstHosts a collection of destination hosts
256 * @param spines a collection of spine device IDs
257 * @param topology a topology
258 * @return a list of flow rules
259 * @throws FlowRuleGeneratorException if flow rules cannot be generated
260 */
261 public abstract List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
262 Collection<DeviceId> spines, Topology topology)
263 throws FlowRuleGeneratorException;
264
265 /**
266 * Generates a list of flow rules for the given spine switch, destination hosts and topology.
267 *
268 * @param deviceId a spine device id
269 * @param dstHosts a collection of destination hosts
270 * @param topology a topology
271 * @return a list of flow rules
272 * @throws FlowRuleGeneratorException if flow rules cannot be generated
273 */
274 public abstract List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topology)
275 throws FlowRuleGeneratorException;
276
277 private void deployAllDevices() {
278 if (otherAppFound && otherApp.appActive) {
279 log.info("Deactivating other app...");
280 appService.deactivate(otherApp.appId);
281 try {
282 Thread.sleep(CLEANUP_SLEEP);
283 } catch (InterruptedException e) {
284 log.warn("Cleanup sleep interrupted!");
285 Thread.interrupted();
286 }
287 }
288
289 Stream.concat(leafSwitches.stream(), spineSwitches.stream())
290 .map(deviceService::getDevice)
291 .forEach(device -> spawnTask(() -> deployDevice(device)));
292 }
293
Carmelo Cascone6e854042017-09-11 21:37:53 +0200294 private boolean matchPipeconf(PiPipeconfId piPipeconfId) {
295 return appPipeconfs.stream()
296 .anyMatch(p -> p.id().equals(piPipeconfId));
297 }
298
wu5f6c5b82017-08-04 16:45:19 +0800299 /**
300 * Executes a device deploy.
301 *
302 * @param device a device
303 */
304 public void deployDevice(Device device) {
305
306 DeviceId deviceId = device.id();
307
308 // Synchronize executions over the same device.
309 Lock lock = deviceLocks.computeIfAbsent(deviceId, k -> new ReentrantLock());
310 lock.lock();
311
312 try {
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200313 // Set pipeconf flag if not already done.
wu5f6c5b82017-08-04 16:45:19 +0800314 if (!pipeconfFlags.getOrDefault(deviceId, false)) {
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200315 if (piPipeconfService.ofDevice(deviceId).isPresent() &&
Carmelo Cascone6e854042017-09-11 21:37:53 +0200316 matchPipeconf(piPipeconfService.ofDevice(deviceId).get())) {
wu5f6c5b82017-08-04 16:45:19 +0800317 pipeconfFlags.put(device.id(), true);
318 } else {
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200319 log.warn("Wrong pipeconf for {}, expecting {}, but found {}, aborting deploy",
Carmelo Cascone6e854042017-09-11 21:37:53 +0200320 deviceId, MoreObjects.toStringHelper(appPipeconfs),
321 piPipeconfService.ofDevice(deviceId).get());
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200322 return;
wu5f6c5b82017-08-04 16:45:19 +0800323 }
324 }
325
326 // Initialize device.
327 if (!initDevice(deviceId)) {
328 log.warn("Failed to initialize device {}", deviceId);
329 }
330
331 // Install rules.
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200332 if (!ruleFlags.getOrDefault(deviceId, false) &&
333 deviceFlowRules.containsKey(deviceId)) {
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200334 log.info("Installing {} rules for {}...", deviceFlowRules.get(deviceId).size(), deviceId);
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200335 installFlowRules(deviceFlowRules.get(deviceId));
336 ruleFlags.put(deviceId, true);
wu5f6c5b82017-08-04 16:45:19 +0800337 }
338 } finally {
339 lock.unlock();
340 }
341 }
342
343 private void spawnTask(Runnable task) {
344 executorService.execute(task);
345 }
346
347
348 private void installFlowRules(Collection<FlowRule> rules) {
349 FlowRuleOperations.Builder opsBuilder = FlowRuleOperations.builder();
350 rules.forEach(opsBuilder::add);
351 flowRuleService.apply(opsBuilder.build());
352 }
353
wu5f6c5b82017-08-04 16:45:19 +0800354 /**
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200355 * Generates flow rules to provide host-to-host connectivity for the given topology and hosts.
wu5f6c5b82017-08-04 16:45:19 +0800356 */
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200357 private synchronized void checkTopologyAndGenerateFlowRules() {
358
359 Topology topo = topologyService.currentTopology();
360 Set<Host> hosts = Sets.newHashSet(hostService.getHosts());
wu5f6c5b82017-08-04 16:45:19 +0800361
362 if (flowRuleGenerated) {
363 log.debug("Flow rules have been already generated, aborting...");
364 return;
365 }
366
367 log.debug("Starting flow rules generator...");
368
369 TopologyGraph graph = topologyService.getGraph(topo);
370 Set<DeviceId> spines = Sets.newHashSet();
371 Set<DeviceId> leafs = Sets.newHashSet();
372 graph.getVertexes().stream()
373 .map(TopologyVertex::deviceId)
374 .forEach(did -> (isSpine(did, topo) ? spines : leafs).add(did));
375
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200376 if (spines.size() != TOPO_SIZE || leafs.size() != TOPO_SIZE) {
wu5f6c5b82017-08-04 16:45:19 +0800377 log.info("Invalid leaf/spine switches count, aborting... > leafCount={}, spineCount={}",
378 spines.size(), leafs.size());
379 return;
380 }
381
382 for (DeviceId did : spines) {
383 int portCount = deviceService.getPorts(did).size();
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200384 // Expected port count: num leafs + 1 redundant leaf link (if imbalanced)
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200385 if (portCount != HASHED_LINKS) {
wu5f6c5b82017-08-04 16:45:19 +0800386 log.info("Invalid port count for spine, aborting... > deviceId={}, portCount={}", did, portCount);
387 return;
388 }
389 }
390 for (DeviceId did : leafs) {
391 int portCount = deviceService.getPorts(did).size();
392 // Expected port count: num spines + host port + 1 redundant spine link
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200393 if (portCount != HASHED_LINKS + 1) {
wu5f6c5b82017-08-04 16:45:19 +0800394 log.info("Invalid port count for leaf, aborting... > deviceId={}, portCount={}", did, portCount);
395 return;
396 }
397 }
398
399 // Check hosts, number and exactly one per leaf
400 Map<DeviceId, Host> hostMap = Maps.newHashMap();
401 hosts.forEach(h -> hostMap.put(h.location().deviceId(), h));
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200402 if (hosts.size() != TOPO_SIZE || !leafs.equals(hostMap.keySet())) {
wu5f6c5b82017-08-04 16:45:19 +0800403 log.info("Wrong host configuration, aborting... > hostCount={}, hostMapz={}", hosts.size(), hostMap);
404 return;
405 }
406
407 List<FlowRule> newFlowRules = Lists.newArrayList();
408
409 try {
410 for (DeviceId deviceId : leafs) {
411 Host srcHost = hostMap.get(deviceId);
412 Set<Host> dstHosts = hosts.stream().filter(h -> h != srcHost).collect(toSet());
413 newFlowRules.addAll(generateLeafRules(deviceId, srcHost, dstHosts, spines, topo));
414 }
415 for (DeviceId deviceId : spines) {
416 newFlowRules.addAll(generateSpineRules(deviceId, hosts, topo));
417 }
418 } catch (FlowRuleGeneratorException e) {
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200419 log.warn("Exception while executing flow rule generator: {}", e.getMessage());
wu5f6c5b82017-08-04 16:45:19 +0800420 return;
421 }
422
423 if (newFlowRules.size() == 0) {
424 // Something went wrong
425 log.error("0 flow rules generated, BUG?");
426 return;
427 }
428
429 // All good!
430 // Divide flow rules per device id...
431 ImmutableMap.Builder<DeviceId, List<FlowRule>> mapBuilder = ImmutableMap.builder();
432 concat(spines.stream(), leafs.stream())
433 .map(deviceId -> ImmutableList.copyOf(newFlowRules
434 .stream()
435 .filter(fr -> fr.deviceId().equals(deviceId))
436 .iterator()))
437 .forEach(frs -> mapBuilder.put(frs.get(0).deviceId(), frs));
438 this.deviceFlowRules = mapBuilder.build();
439
440 this.leafSwitches = ImmutableSet.copyOf(leafs);
441 this.spineSwitches = ImmutableSet.copyOf(spines);
442
443 // Avoid other executions to modify the generated flow rules.
444 flowRuleGenerated = true;
445
446 log.info("Generated {} flow rules for {} devices", newFlowRules.size(), spines.size() + leafs.size());
447
448 spawnTask(this::deployAllDevices);
449 }
450
451 /**
452 * Returns a new, pre-configured flow rule builder.
453 *
Carmelo Casconeca94bcf2017-10-27 14:16:59 -0700454 * @param did a device id
455 * @param tableId a table id
wu5f6c5b82017-08-04 16:45:19 +0800456 * @return a new flow rule builder
457 */
Carmelo Casconeca94bcf2017-10-27 14:16:59 -0700458 protected FlowRule.Builder flowRuleBuilder(DeviceId did, PiTableId tableId) throws FlowRuleGeneratorException {
wu5f6c5b82017-08-04 16:45:19 +0800459
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200460 final Device device = deviceService.getDevice(did);
461 if (!device.is(PiPipelineInterpreter.class)) {
462 throw new FlowRuleGeneratorException(format("Device %s has no PiPipelineInterpreter", did));
wu5f6c5b82017-08-04 16:45:19 +0800463 }
Carmelo Cascone1fb27d32017-08-25 20:40:20 +0200464 final PiPipelineInterpreter interpreter = device.as(PiPipelineInterpreter.class);
465 final int flowRuleTableId;
Carmelo Casconeca94bcf2017-10-27 14:16:59 -0700466 if (interpreter.mapPiTableId(tableId).isPresent()) {
467 flowRuleTableId = interpreter.mapPiTableId(tableId).get();
wu5f6c5b82017-08-04 16:45:19 +0800468 } else {
Carmelo Casconeca94bcf2017-10-27 14:16:59 -0700469 throw new FlowRuleGeneratorException(format("Unknown table '%s' in interpreter", tableId));
wu5f6c5b82017-08-04 16:45:19 +0800470 }
471
472 return DefaultFlowRule.builder()
473 .forDevice(did)
474 .forTable(flowRuleTableId)
475 .fromApp(appId)
476 .withPriority(FLOW_PRIORITY)
477 .makePermanent();
478 }
479
480 private List<Port> getHostPorts(DeviceId deviceId, Topology topology) {
481 // Get all non-fabric ports.
482 return deviceService
483 .getPorts(deviceId)
484 .stream()
485 .filter(p -> !isFabricPort(p, topology))
486 .collect(Collectors.toList());
487 }
488
489 private boolean isSpine(DeviceId deviceId, Topology topology) {
490 // True if all ports are fabric.
491 return getHostPorts(deviceId, topology).size() == 0;
492 }
493
494 protected boolean isFabricPort(Port port, Topology topology) {
495 // True if the port connects this device to another infrastructure device.
496 return topologyService.isInfrastructure(topology, new ConnectPoint(port.element().id(), port.number()));
497 }
498
499 /**
wu5f6c5b82017-08-04 16:45:19 +0800500 * A listener of device events that executes a device deploy task each time a device is added, updated or
501 * re-connects.
502 */
503 private class InternalDeviceListener implements DeviceListener {
504 @Override
505 public void event(DeviceEvent event) {
506 spawnTask(() -> deployDevice(event.subject()));
507 }
508
509 @Override
510 public boolean isRelevant(DeviceEvent event) {
511 return !appFreezed &&
512 (event.type() == DEVICE_ADDED ||
513 event.type() == DEVICE_UPDATED ||
514 (event.type() == DEVICE_AVAILABILITY_CHANGED &&
515 deviceService.isAvailable(event.subject().id())));
516 }
517 }
518
519 /**
wu5f6c5b82017-08-04 16:45:19 +0800520 * An exception occurred while generating flow rules for this fabric.
521 */
522 public class FlowRuleGeneratorException extends Exception {
523
524 public FlowRuleGeneratorException() {
525 }
526
527 public FlowRuleGeneratorException(String msg) {
528 super(msg);
529 }
wu5f6c5b82017-08-04 16:45:19 +0800530 }
531}