blob: 3e9b802b3c554071f91da396aca145a69c368729 [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Thomas Vachuska781d18b2014-10-27 10:31:25 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * 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
Thomas Vachuska781d18b2014-10-27 10:31:25 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * 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.
Thomas Vachuska781d18b2014-10-27 10:31:25 -070015 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.openflow.controller.impl;
tom7ef8ff92014-09-17 13:08:06 -070017
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080018import com.google.common.collect.ArrayListMultimap;
Yuta HIGUCHI7b41dc92017-06-22 19:37:06 -070019import com.google.common.collect.ImmutableList;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080020import com.google.common.collect.Multimap;
Charles Chan3b00e1b2015-08-26 23:12:52 +080021import org.onosproject.cfg.ComponentConfigService;
Charles Chanecfdfb72015-11-24 19:05:50 -080022import org.onosproject.core.CoreService;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070023import org.onosproject.net.DeviceId;
24import org.onosproject.net.config.ConfigFactory;
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -070025import org.onosproject.net.config.NetworkConfigEvent;
26import org.onosproject.net.config.NetworkConfigListener;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070027import org.onosproject.net.config.NetworkConfigRegistry;
28import org.onosproject.net.config.basics.SubjectFactories;
alshabibb452fd72015-04-22 20:46:20 -070029import org.onosproject.net.driver.DriverService;
Brian O'Connorf69e3e32018-05-10 02:25:09 -070030import org.onosproject.openflow.config.OpenFlowDeviceConfig;
Brian O'Connorabafb502014-12-02 22:26:20 -080031import org.onosproject.openflow.controller.DefaultOpenFlowPacketContext;
32import org.onosproject.openflow.controller.Dpid;
33import org.onosproject.openflow.controller.OpenFlowController;
34import org.onosproject.openflow.controller.OpenFlowEventListener;
Jian Lia78cdb22016-04-21 13:03:58 -070035import org.onosproject.openflow.controller.OpenFlowMessageListener;
Brian O'Connorabafb502014-12-02 22:26:20 -080036import org.onosproject.openflow.controller.OpenFlowPacketContext;
37import org.onosproject.openflow.controller.OpenFlowSwitch;
38import org.onosproject.openflow.controller.OpenFlowSwitchListener;
39import org.onosproject.openflow.controller.PacketListener;
40import org.onosproject.openflow.controller.RoleState;
41import org.onosproject.openflow.controller.driver.OpenFlowAgent;
Jonathan Hartbbd91d42015-02-27 11:18:04 -080042import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070043import org.osgi.service.component.annotations.Activate;
44import org.osgi.service.component.annotations.Component;
45import org.osgi.service.component.annotations.Deactivate;
46import org.osgi.service.component.annotations.Modified;
47import org.osgi.service.component.annotations.Reference;
48import org.osgi.service.component.annotations.ReferenceCardinality;
Marc De Leenheerb9311372015-07-09 11:36:49 -070049import org.projectfloodlight.openflow.protocol.OFCalientFlowStatsEntry;
50import org.projectfloodlight.openflow.protocol.OFCalientFlowStatsReply;
Marc De Leenheer631ffce2014-10-28 16:29:07 -070051import org.projectfloodlight.openflow.protocol.OFCircuitPortStatus;
52import org.projectfloodlight.openflow.protocol.OFExperimenter;
alshabib64def642014-12-02 23:27:37 -080053import org.projectfloodlight.openflow.protocol.OFFactories;
Cem Türker3baff672017-10-12 15:09:01 +030054import org.projectfloodlight.openflow.protocol.OFFlowLightweightStatsEntry;
55import org.projectfloodlight.openflow.protocol.OFFlowLightweightStatsReply;
alshabib64def642014-12-02 23:27:37 -080056import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry;
57import org.projectfloodlight.openflow.protocol.OFFlowStatsReply;
sangho6a0bb172015-02-05 12:24:48 -080058import org.projectfloodlight.openflow.protocol.OFGroupDescStatsEntry;
59import org.projectfloodlight.openflow.protocol.OFGroupDescStatsReply;
60import org.projectfloodlight.openflow.protocol.OFGroupStatsEntry;
61import org.projectfloodlight.openflow.protocol.OFGroupStatsReply;
tom7ef8ff92014-09-17 13:08:06 -070062import org.projectfloodlight.openflow.protocol.OFMessage;
63import org.projectfloodlight.openflow.protocol.OFPacketIn;
Marc De Leenheer631ffce2014-10-28 16:29:07 -070064import org.projectfloodlight.openflow.protocol.OFPortDesc;
sangho538108b2015-04-08 14:29:20 -070065import org.projectfloodlight.openflow.protocol.OFPortStatsEntry;
tom7ef8ff92014-09-17 13:08:06 -070066import org.projectfloodlight.openflow.protocol.OFPortStatus;
Cem Türker3baff672017-10-12 15:09:01 +030067import org.projectfloodlight.openflow.protocol.OFQueueStatsEntry;
68import org.projectfloodlight.openflow.protocol.OFQueueStatsReply;
Ayaka Koshibe38594c22014-10-22 13:36:12 -070069import org.projectfloodlight.openflow.protocol.OFStatsReply;
alshabib64def642014-12-02 23:27:37 -080070import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags;
Jian Li2266bff2016-04-21 11:01:25 -070071import org.projectfloodlight.openflow.protocol.OFTableStatsEntry;
72import org.projectfloodlight.openflow.protocol.OFTableStatsReply;
Marc De Leenheerb9311372015-07-09 11:36:49 -070073import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
74import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
tom7ef8ff92014-09-17 13:08:06 -070075import org.slf4j.Logger;
76import org.slf4j.LoggerFactory;
77
Yuta HIGUCHI7b41dc92017-06-22 19:37:06 -070078import java.util.ArrayList;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080079import java.util.Collection;
Marc De Leenheerb9311372015-07-09 11:36:49 -070080import java.util.Collections;
Marc De Leenheerb9311372015-07-09 11:36:49 -070081import java.util.List;
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -070082import java.util.Objects;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080083import java.util.Set;
Marc De Leenheer8aba62f2017-04-25 14:33:37 -070084import java.util.concurrent.CompletableFuture;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080085import java.util.concurrent.ConcurrentHashMap;
HIGUCHI Yuta1979f552015-12-28 21:24:26 -080086import java.util.concurrent.ConcurrentMap;
Jonathan Hart6d44d192015-05-11 18:01:19 -070087import java.util.concurrent.CopyOnWriteArraySet;
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080088import java.util.concurrent.ExecutorService;
89import java.util.concurrent.Executors;
90import java.util.concurrent.locks.Lock;
91import java.util.concurrent.locks.ReentrantLock;
Jian Li2266bff2016-04-21 11:01:25 -070092
Thomas Vachuska6f94ded2015-02-21 14:02:38 -080093import static org.onlab.util.Tools.groupedThreads;
Ozge AYAZ60aded22017-06-20 08:35:30 +000094
tom7ef8ff92014-09-17 13:08:06 -070095
Ray Milkeyd84f89b2018-08-17 14:54:17 -070096@Component(immediate = true, service = OpenFlowController.class)
tom7ef8ff92014-09-17 13:08:06 -070097public class OpenFlowControllerImpl implements OpenFlowController {
Charles Chanecfdfb72015-11-24 19:05:50 -080098 private static final String APP_ID = "org.onosproject.openflow-base";
Brian O'Connorff278502015-09-22 14:49:52 -070099 private static final String DEFAULT_OFPORT = "6633,6653";
Yuta HIGUCHI8552b172016-07-25 12:10:08 -0700100 private static final int DEFAULT_WORKER_THREADS = 0;
Andrea Campanella86e0c562017-11-23 16:38:24 +0100101 protected static final String SCHEME = "of";
tom7ef8ff92014-09-17 13:08:06 -0700102
103 private static final Logger log =
104 LoggerFactory.getLogger(OpenFlowControllerImpl.class);
105
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700106 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chanecfdfb72015-11-24 19:05:50 -0800107 protected CoreService coreService;
108
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700109 @Reference(cardinality = ReferenceCardinality.MANDATORY)
alshabibb452fd72015-04-22 20:46:20 -0700110 protected DriverService driverService;
111
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700112 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Charles Chan3b00e1b2015-08-26 23:12:52 +0800113 protected ComponentConfigService cfgService;
114
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700115 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700116 protected NetworkConfigRegistry netCfgService;
117
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700118 //@Property(name = "openflowPorts", value = DEFAULT_OFPORT,
119 // label = "Port numbers (comma separated) used by OpenFlow protocol; default is 6633,6653")
Brian O'Connore2a399e2015-09-22 15:32:50 -0700120 private String openflowPorts = DEFAULT_OFPORT;
Charles Chan3b00e1b2015-08-26 23:12:52 +0800121
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700122 //@Property(name = "workerThreads", intValue = DEFAULT_WORKER_THREADS,
123 // label = "Number of controller worker threads")
Charles Chan3b00e1b2015-08-26 23:12:52 +0800124 private int workerThreads = DEFAULT_WORKER_THREADS;
125
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700126 //@Property(name = "tlsMode", value = "",
127 // label = "TLS mode for OpenFlow channel; options are: disabled [default], enabled, strict")
Brian O'Connorf7215b82018-05-10 19:12:44 -0700128 private String tlsModeString;
129
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700130 //@Property(name = "keyStore", value = "",
131 // label = "File path to key store for TLS connections")
Brian O'Connorf7215b82018-05-10 19:12:44 -0700132 private String keyStore;
133
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700134 //@Property(name = "keyStorePassword", value = "",
135 // label = "Key store password")
Brian O'Connorf7215b82018-05-10 19:12:44 -0700136 private String keyStorePassword;
137
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700138 //@Property(name = "trustStore", value = "",
139 // label = "File path to trust store for TLS connections")
Brian O'Connorf7215b82018-05-10 19:12:44 -0700140 private String trustStore;
141
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700142 //@Property(name = "trustStorePassword", value = "",
143 // label = "Trust store password")
Brian O'Connorf7215b82018-05-10 19:12:44 -0700144 private String trustStorePassword;
145
Ray Milkey7ec0d1b2015-11-13 08:51:35 -0800146 protected ExecutorService executorMsgs =
Andrea Campanelladda93562016-03-02 11:08:12 -0800147 Executors.newFixedThreadPool(32, groupedThreads("onos/of", "event-stats-%d", log));
Pavlin Radoslavov369c6432014-12-03 16:25:14 -0800148
149 private final ExecutorService executorBarrier =
Andrea Campanelladda93562016-03-02 11:08:12 -0800150 Executors.newFixedThreadPool(4, groupedThreads("onos/of", "event-barrier-%d", log));
alshabib8f1cf4a2014-09-17 14:44:48 -0700151
Prince Pereirae7798032016-07-08 16:31:58 +0530152 //Separate executor thread for handling error messages and barrier replies for same failed
153 // transactions to avoid context switching of thread
154 protected ExecutorService executorErrorMsgs =
155 Executors.newSingleThreadExecutor(groupedThreads("onos/of", "event-error-msg-%d", log));
156
157 //concurrent hashmap to track failed transactions
158 protected ConcurrentMap<Long, Boolean> errorMsgs =
159 new ConcurrentHashMap<>();
HIGUCHI Yuta1979f552015-12-28 21:24:26 -0800160 protected ConcurrentMap<Dpid, OpenFlowSwitch> connectedSwitches =
Jonathan Hart6d44d192015-05-11 18:01:19 -0700161 new ConcurrentHashMap<>();
HIGUCHI Yuta1979f552015-12-28 21:24:26 -0800162 protected ConcurrentMap<Dpid, OpenFlowSwitch> activeMasterSwitches =
Jonathan Hart6d44d192015-05-11 18:01:19 -0700163 new ConcurrentHashMap<>();
HIGUCHI Yuta1979f552015-12-28 21:24:26 -0800164 protected ConcurrentMap<Dpid, OpenFlowSwitch> activeEqualSwitches =
Jonathan Hart6d44d192015-05-11 18:01:19 -0700165 new ConcurrentHashMap<>();
tom7ef8ff92014-09-17 13:08:06 -0700166
Marc De Leenheer8aba62f2017-04-25 14:33:37 -0700167 // Key: dpid, value: map with key: long (XID), value: completable future
168 protected ConcurrentMap<Dpid, ConcurrentMap<Long, CompletableFuture<OFMessage>>> responses =
169 new ConcurrentHashMap<>();
170
tom7ef8ff92014-09-17 13:08:06 -0700171 protected OpenFlowSwitchAgent agent = new OpenFlowSwitchAgent();
Jonathan Hart6d44d192015-05-11 18:01:19 -0700172 protected Set<OpenFlowSwitchListener> ofSwitchListener = new CopyOnWriteArraySet<>();
tom7ef8ff92014-09-17 13:08:06 -0700173
174 protected Multimap<Integer, PacketListener> ofPacketListener =
175 ArrayListMultimap.create();
176
Jonathan Hart6d44d192015-05-11 18:01:19 -0700177 protected Set<OpenFlowEventListener> ofEventListener = new CopyOnWriteArraySet<>();
tom7ef8ff92014-09-17 13:08:06 -0700178
Jian Lia78cdb22016-04-21 13:03:58 -0700179 protected Set<OpenFlowMessageListener> ofMessageListener = new CopyOnWriteArraySet<>();
Jian Li28247b52016-01-07 17:24:15 -0800180
sangho6a0bb172015-02-05 12:24:48 -0800181 protected Multimap<Dpid, OFFlowStatsEntry> fullFlowStats =
182 ArrayListMultimap.create();
183
Cem Türker3baff672017-10-12 15:09:01 +0300184 protected Multimap<Dpid, OFFlowLightweightStatsEntry> fullFlowLightweightStats =
185 ArrayListMultimap.create();
186
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700187 protected Multimap<Dpid, OFTableStatsEntry> fullTableStats =
188 ArrayListMultimap.create();
189
sangho6a0bb172015-02-05 12:24:48 -0800190 protected Multimap<Dpid, OFGroupStatsEntry> fullGroupStats =
191 ArrayListMultimap.create();
192
193 protected Multimap<Dpid, OFGroupDescStatsEntry> fullGroupDescStats =
alshabib64def642014-12-02 23:27:37 -0800194 ArrayListMultimap.create();
195
Yuta HIGUCHI7b41dc92017-06-22 19:37:06 -0700196 // deprecated in 1.11.0, no longer referenced from anywhere
197 @Deprecated
sangho538108b2015-04-08 14:29:20 -0700198 protected Multimap<Dpid, OFPortStatsEntry> fullPortStats =
199 ArrayListMultimap.create();
200
Ozge AYAZ60aded22017-06-20 08:35:30 +0000201 protected Multimap<Dpid, OFQueueStatsEntry> fullQueueStats =
202 ArrayListMultimap.create();
203
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700204 protected final ConfigFactory factory =
205 new ConfigFactory<DeviceId, OpenFlowDeviceConfig>(
206 SubjectFactories.DEVICE_SUBJECT_FACTORY,
207 OpenFlowDeviceConfig.class, OpenFlowDeviceConfig.CONFIG_KEY) {
208 @Override
209 public OpenFlowDeviceConfig createConfig() {
210 return new OpenFlowDeviceConfig();
211 }
212 };
213
tom7ef8ff92014-09-17 13:08:06 -0700214 private final Controller ctrl = new Controller();
215
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700216 private final NetworkConfigListener netCfgListener = new NetworkConfigListener() {
217 @Override
218 public boolean isRelevant(NetworkConfigEvent event) {
219 return OpenFlowDeviceConfig.class.equals(event.configClass());
220 }
221
222 @Override
223 public void event(NetworkConfigEvent event) {
224 // We only receive NetworkConfigEvents
225 OpenFlowDeviceConfig prevConfig = null;
226 if (event.prevConfig().isPresent()) {
227 prevConfig = (OpenFlowDeviceConfig) event.prevConfig().get();
228 }
229
230 OpenFlowDeviceConfig newConfig = null;
231 if (event.config().isPresent()) {
232 newConfig = (OpenFlowDeviceConfig) event.config().get();
233 }
234
235 boolean closeConnection = false;
236 if (prevConfig != null && newConfig != null) {
237 if (!Objects.equals(prevConfig.keyAlias(), newConfig.keyAlias())) {
238 closeConnection = true;
239 }
240 } else if (prevConfig != null) {
241 // config was removed
242 closeConnection = true;
243 }
244 if (closeConnection) {
245 if (event.subject() instanceof DeviceId) {
246 DeviceId deviceId = (DeviceId) event.subject();
247 Dpid dpid = Dpid.dpid(deviceId.uri());
248 OpenFlowSwitch sw = getSwitch(dpid);
249 if (sw != null && ctrl.tlsParams.mode == Controller.TlsMode.STRICT) {
250 sw.disconnectSwitch();
251 log.info("Disconnecting switch {} because key has been updated or removed", dpid);
252 }
253 }
254 }
255 }
256 };
257
tom7ef8ff92014-09-17 13:08:06 -0700258 @Activate
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800259 public void activate(ComponentContext context) {
Andrea Campanella3556f362016-04-28 15:18:10 -0700260 coreService.registerApplication(APP_ID, this::cleanup);
Charles Chan3b00e1b2015-08-26 23:12:52 +0800261 cfgService.registerProperties(getClass());
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700262 netCfgService.registerConfigFactory(factory);
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700263 netCfgService.addListener(netCfgListener);
Brian O'Connorff278502015-09-22 14:49:52 -0700264 ctrl.setConfigParams(context.getProperties());
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700265 ctrl.start(agent, driverService, netCfgService);
tom7ef8ff92014-09-17 13:08:06 -0700266 }
267
Andrea Campanella3556f362016-04-28 15:18:10 -0700268 private void cleanup() {
269 // Close listening channel and all OF channels. Clean information about switches
270 // before deactivating
Charles Chanecfdfb72015-11-24 19:05:50 -0800271 ctrl.stop();
272 connectedSwitches.values().forEach(OpenFlowSwitch::disconnectSwitch);
Andrea Campanella3556f362016-04-28 15:18:10 -0700273 connectedSwitches.clear();
274 activeMasterSwitches.clear();
275 activeEqualSwitches.clear();
Charles Chanecfdfb72015-11-24 19:05:50 -0800276 }
277
tom7ef8ff92014-09-17 13:08:06 -0700278 @Deactivate
279 public void deactivate() {
Thiago Santos61725402016-08-05 17:58:56 -0300280 cleanup();
Charles Chan3b00e1b2015-08-26 23:12:52 +0800281 cfgService.unregisterProperties(getClass(), false);
Brian O'Connor1bd4a9f2018-05-10 22:40:41 -0700282 netCfgService.removeListener(netCfgListener);
Brian O'Connorf69e3e32018-05-10 02:25:09 -0700283 netCfgService.unregisterConfigFactory(factory);
tom7ef8ff92014-09-17 13:08:06 -0700284 }
285
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800286 @Modified
287 public void modified(ComponentContext context) {
Brian O'Connorff278502015-09-22 14:49:52 -0700288 ctrl.setConfigParams(context.getProperties());
Jonathan Hartbbd91d42015-02-27 11:18:04 -0800289 }
290
tom7ef8ff92014-09-17 13:08:06 -0700291 @Override
292 public Iterable<OpenFlowSwitch> getSwitches() {
293 return connectedSwitches.values();
294 }
295
296 @Override
297 public Iterable<OpenFlowSwitch> getMasterSwitches() {
298 return activeMasterSwitches.values();
299 }
300
301 @Override
302 public Iterable<OpenFlowSwitch> getEqualSwitches() {
303 return activeEqualSwitches.values();
304 }
305
306 @Override
307 public OpenFlowSwitch getSwitch(Dpid dpid) {
308 return connectedSwitches.get(dpid);
309 }
310
311 @Override
312 public OpenFlowSwitch getMasterSwitch(Dpid dpid) {
313 return activeMasterSwitches.get(dpid);
314 }
315
316 @Override
317 public OpenFlowSwitch getEqualSwitch(Dpid dpid) {
318 return activeEqualSwitches.get(dpid);
319 }
320
321 @Override
322 public void addListener(OpenFlowSwitchListener listener) {
alshabib8f1cf4a2014-09-17 14:44:48 -0700323 if (!ofSwitchListener.contains(listener)) {
324 this.ofSwitchListener.add(listener);
tom7ef8ff92014-09-17 13:08:06 -0700325 }
326 }
327
328 @Override
329 public void removeListener(OpenFlowSwitchListener listener) {
alshabib8f1cf4a2014-09-17 14:44:48 -0700330 this.ofSwitchListener.remove(listener);
tom7ef8ff92014-09-17 13:08:06 -0700331 }
332
333 @Override
Jian Lia78cdb22016-04-21 13:03:58 -0700334 public void addMessageListener(OpenFlowMessageListener listener) {
335 ofMessageListener.add(listener);
336 }
337
338 @Override
339 public void removeMessageListener(OpenFlowMessageListener listener) {
340 ofMessageListener.remove(listener);
341 }
342
343 @Override
tom7ef8ff92014-09-17 13:08:06 -0700344 public void addPacketListener(int priority, PacketListener listener) {
345 ofPacketListener.put(priority, listener);
346 }
347
348 @Override
349 public void removePacketListener(PacketListener listener) {
350 ofPacketListener.values().remove(listener);
351 }
352
353 @Override
alshabibeec3a062014-09-17 18:01:26 -0700354 public void addEventListener(OpenFlowEventListener listener) {
355 ofEventListener.add(listener);
356 }
357
358 @Override
359 public void removeEventListener(OpenFlowEventListener listener) {
360 ofEventListener.remove(listener);
361 }
362
363 @Override
tom7ef8ff92014-09-17 13:08:06 -0700364 public void write(Dpid dpid, OFMessage msg) {
365 this.getSwitch(dpid).sendMsg(msg);
366 }
367
368 @Override
Marc De Leenheer8aba62f2017-04-25 14:33:37 -0700369 public CompletableFuture<OFMessage> writeResponse(Dpid dpid, OFMessage msg) {
370 write(dpid, msg);
371
372 ConcurrentMap<Long, CompletableFuture<OFMessage>> xids =
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700373 responses.computeIfAbsent(dpid, k -> new ConcurrentHashMap<>());
Marc De Leenheer8aba62f2017-04-25 14:33:37 -0700374
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700375 CompletableFuture<OFMessage> future = new CompletableFuture<>();
Marc De Leenheer8aba62f2017-04-25 14:33:37 -0700376 xids.put(msg.getXid(), future);
377
378 return future;
379 }
380
381 @Override
tom7ef8ff92014-09-17 13:08:06 -0700382 public void processPacket(Dpid dpid, OFMessage msg) {
sangyun-han69ed4462016-07-27 12:10:12 +0900383 OpenFlowSwitch sw = this.getSwitch(dpid);
Laszlo Pappb68fe7e2017-11-24 17:06:59 +0000384 if (log.isTraceEnabled()) {
385 log.trace("Processing message from switch {} via openflow: {}", dpid, msg);
386 }
sangyun-han69ed4462016-07-27 12:10:12 +0900387
Marc De Leenheer8aba62f2017-04-25 14:33:37 -0700388 // Check if someone is waiting for this message
389 ConcurrentMap<Long, CompletableFuture<OFMessage>> xids = responses.get(dpid);
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700390 if (xids != null) {
391 CompletableFuture<OFMessage> future = xids.remove(msg.getXid());
392 if (future != null) {
393 future.complete(msg);
394 }
Marc De Leenheer8aba62f2017-04-25 14:33:37 -0700395 }
396
tom7ef8ff92014-09-17 13:08:06 -0700397 switch (msg.getType()) {
398 case PORT_STATUS:
alshabib8f1cf4a2014-09-17 14:44:48 -0700399 for (OpenFlowSwitchListener l : ofSwitchListener) {
tom7ef8ff92014-09-17 13:08:06 -0700400 l.portChanged(dpid, (OFPortStatus) msg);
401 }
402 break;
Ayaka Koshibe38594c22014-10-22 13:36:12 -0700403 case FEATURES_REPLY:
404 for (OpenFlowSwitchListener l : ofSwitchListener) {
405 l.switchChanged(dpid);
406 }
407 break;
tom7ef8ff92014-09-17 13:08:06 -0700408 case PACKET_IN:
sangyun-han69ed4462016-07-27 12:10:12 +0900409 if (sw == null) {
Yuta HIGUCHI6ee6b8c2017-05-09 14:44:30 -0700410 log.error("Ignoring PACKET_IN, switch {} is not found", dpid);
sangyun-han69ed4462016-07-27 12:10:12 +0900411 break;
412 }
tom7ef8ff92014-09-17 13:08:06 -0700413 OpenFlowPacketContext pktCtx = DefaultOpenFlowPacketContext
Ozge AYAZ60aded22017-06-20 08:35:30 +0000414 .packetContextFromPacketIn(sw, (OFPacketIn) msg);
tom7ef8ff92014-09-17 13:08:06 -0700415 for (PacketListener p : ofPacketListener.values()) {
416 p.handlePacket(pktCtx);
417 }
418 break;
Pavlin Radoslavov369c6432014-12-03 16:25:14 -0800419 // TODO: Consider using separate threadpool for sensitive messages.
420 // ie. Back to back error could cause us to starve.
421 case FLOW_REMOVED:
Andrea Campanelladda93562016-03-02 11:08:12 -0800422 executorMsgs.execute(new OFMessageHandler(dpid, msg));
Pavlin Radoslavov369c6432014-12-03 16:25:14 -0800423 break;
Prince Pereirae7798032016-07-08 16:31:58 +0530424 case ERROR:
425 log.debug("Received error message from {}: {}", dpid, msg);
426 errorMsgs.putIfAbsent(msg.getXid(), true);
427 executorErrorMsgs.execute(new OFMessageHandler(dpid, msg));
428 break;
Ayaka Koshibe38594c22014-10-22 13:36:12 -0700429 case STATS_REPLY:
Yuta HIGUCHI7b41dc92017-06-22 19:37:06 -0700430 processStatsReply(dpid, (OFStatsReply) msg);
alshabib64def642014-12-02 23:27:37 -0800431 break;
alshabib8f1cf4a2014-09-17 14:44:48 -0700432 case BARRIER_REPLY:
Prince Pereirae7798032016-07-08 16:31:58 +0530433 if (errorMsgs.containsKey(msg.getXid())) {
434 //To make oferror msg handling and corresponding barrier reply serialized,
435 // executorErrorMsgs is used for both transaction
436 errorMsgs.remove(msg.getXid());
437 executorErrorMsgs.execute(new OFMessageHandler(dpid, msg));
438 } else {
439 executorBarrier.execute(new OFMessageHandler(dpid, msg));
440 }
alshabib8f1cf4a2014-09-17 14:44:48 -0700441 break;
Marc De Leenheer631ffce2014-10-28 16:29:07 -0700442 case EXPERIMENTER:
sangyun-han69ed4462016-07-27 12:10:12 +0900443 if (sw == null) {
444 log.error("Switch {} is not found", dpid);
445 break;
446 }
Marc De Leenheerb9311372015-07-09 11:36:49 -0700447 long experimenter = ((OFExperimenter) msg).getExperimenter();
448 if (experimenter == 0x748771) {
449 // LINC-OE port stats
Marc De Leenheer631ffce2014-10-28 16:29:07 -0700450 OFCircuitPortStatus circuitPortStatus = (OFCircuitPortStatus) msg;
sangyun-han69ed4462016-07-27 12:10:12 +0900451 OFPortStatus.Builder portStatus = sw.factory().buildPortStatus();
452 OFPortDesc.Builder portDesc = sw.factory().buildPortDesc();
Marc De Leenheer631ffce2014-10-28 16:29:07 -0700453 portDesc.setPortNo(circuitPortStatus.getPortNo())
454 .setHwAddr(circuitPortStatus.getHwAddr())
455 .setName(circuitPortStatus.getName())
456 .setConfig(circuitPortStatus.getConfig())
457 .setState(circuitPortStatus.getState());
458 portStatus.setReason(circuitPortStatus.getReason()).setDesc(portDesc.build());
459 for (OpenFlowSwitchListener l : ofSwitchListener) {
460 l.portChanged(dpid, portStatus.build());
461 }
462 } else {
463 log.warn("Handling experimenter type {} not yet implemented",
464 ((OFExperimenter) msg).getExperimenter(), msg);
465 }
466 break;
tom7ef8ff92014-09-17 13:08:06 -0700467 default:
468 log.warn("Handling message type {} not yet implemented {}",
469 msg.getType(), msg);
470 }
471 }
472
Yuta HIGUCHI7b41dc92017-06-22 19:37:06 -0700473 private void processStatsReply(Dpid dpid, OFStatsReply reply) {
474 switch (reply.getStatsType()) {
475 case QUEUE:
476 Collection<OFQueueStatsEntry> queueStatsEntries = publishQueueStats(dpid, (OFQueueStatsReply) reply);
477 if (queueStatsEntries != null) {
478 OFQueueStatsReply.Builder rep =
479 OFFactories.getFactory(reply.getVersion()).buildQueueStatsReply();
480 rep.setEntries(ImmutableList.copyOf(queueStatsEntries));
481 rep.setXid(reply.getXid());
482 executorMsgs.execute(new OFMessageHandler(dpid, rep.build()));
483 }
484 break;
485
486 case PORT_DESC:
487 for (OpenFlowSwitchListener l : ofSwitchListener) {
488 l.switchChanged(dpid);
489 }
490 break;
491
492 case FLOW:
493 Collection<OFFlowStatsEntry> flowStats = publishFlowStats(dpid, (OFFlowStatsReply) reply);
494 if (flowStats != null) {
495 OFFlowStatsReply.Builder rep =
496 OFFactories.getFactory(reply.getVersion()).buildFlowStatsReply();
497 rep.setEntries(ImmutableList.copyOf(flowStats));
498 rep.setXid(reply.getXid());
499 executorMsgs.execute(new OFMessageHandler(dpid, rep.build()));
500 }
501 break;
Cem Türker3baff672017-10-12 15:09:01 +0300502 case FLOW_LIGHTWEIGHT:
503 Collection<OFFlowLightweightStatsEntry> flowLightweightStats =
504 publishFlowStatsLightweight(dpid, (OFFlowLightweightStatsReply) reply);
505 if (flowLightweightStats != null) {
506 OFFlowLightweightStatsReply.Builder rep =
507 OFFactories.getFactory(reply.getVersion()).buildFlowLightweightStatsReply();
508 rep.setEntries(ImmutableList.copyOf(flowLightweightStats));
509 rep.setXid(reply.getXid());
510 executorMsgs.execute(new OFMessageHandler(dpid, rep.build()));
511 }
512 break;
Yuta HIGUCHI7b41dc92017-06-22 19:37:06 -0700513 case TABLE:
514 Collection<OFTableStatsEntry> tableStats = publishTableStats(dpid, (OFTableStatsReply) reply);
515 if (tableStats != null) {
516 OFTableStatsReply.Builder rep =
517 OFFactories.getFactory(reply.getVersion()).buildTableStatsReply();
518 rep.setEntries(ImmutableList.copyOf(tableStats));
519 executorMsgs.execute(new OFMessageHandler(dpid, rep.build()));
520 }
521 break;
522
523 case GROUP:
524 Collection<OFGroupStatsEntry> groupStats = publishGroupStats(dpid, (OFGroupStatsReply) reply);
525 if (groupStats != null) {
526 OFGroupStatsReply.Builder rep =
527 OFFactories.getFactory(reply.getVersion()).buildGroupStatsReply();
528 rep.setEntries(ImmutableList.copyOf(groupStats));
529 rep.setXid(reply.getXid());
530 executorMsgs.execute(new OFMessageHandler(dpid, rep.build()));
531 }
532 break;
533
534 case GROUP_DESC:
535 Collection<OFGroupDescStatsEntry> groupDescStats = publishGroupDescStats(dpid,
536 (OFGroupDescStatsReply) reply);
537 if (groupDescStats != null) {
538 OFGroupDescStatsReply.Builder rep =
539 OFFactories.getFactory(reply.getVersion()).buildGroupDescStatsReply();
540 rep.setEntries(ImmutableList.copyOf(groupDescStats));
541 rep.setXid(reply.getXid());
542 executorMsgs.execute(new OFMessageHandler(dpid, rep.build()));
543 }
544 break;
545
546 case PORT:
547 executorMsgs.execute(new OFMessageHandler(dpid, reply));
548 break;
549
550 case METER:
551 executorMsgs.execute(new OFMessageHandler(dpid, reply));
552 break;
553
554 case EXPERIMENTER:
555 if (reply instanceof OFCalientFlowStatsReply) {
556 OpenFlowSwitch sw = this.getSwitch(dpid);
557 // Convert Calient flow statistics to regular flow stats
558 // TODO: parse remaining fields such as power levels etc. when we have proper monitoring API
559 if (sw == null) {
560 log.error("Switch {} is not found", dpid);
561 break;
562 }
563 OFFlowStatsReply.Builder fsr = sw.factory().buildFlowStatsReply();
564 List<OFFlowStatsEntry> entries = new ArrayList<>();
565 for (OFCalientFlowStatsEntry entry : ((OFCalientFlowStatsReply) reply).getEntries()) {
566
567 // Single instruction, i.e., output to port
568 OFActionOutput action = sw.factory()
569 .actions()
570 .buildOutput()
571 .setPort(entry.getOutPort())
572 .build();
573 OFInstruction instruction = sw.factory()
574 .instructions()
575 .applyActions(Collections.singletonList(action));
576 OFFlowStatsEntry fs = sw.factory().buildFlowStatsEntry()
577 .setMatch(entry.getMatch())
578 .setTableId(entry.getTableId())
579 .setDurationSec(entry.getDurationSec())
580 .setDurationNsec(entry.getDurationNsec())
581 .setPriority(entry.getPriority())
582 .setIdleTimeout(entry.getIdleTimeout())
583 .setHardTimeout(entry.getHardTimeout())
584 .setFlags(entry.getFlags())
585 .setCookie(entry.getCookie())
586 .setInstructions(Collections.singletonList(instruction))
587 .build();
588 entries.add(fs);
589 }
590 fsr.setEntries(entries);
591
592 flowStats = publishFlowStats(dpid, fsr.build());
593 if (flowStats != null) {
594 OFFlowStatsReply.Builder rep =
595 sw.factory().buildFlowStatsReply();
596 rep.setEntries(ImmutableList.copyOf(flowStats));
597 executorMsgs.execute(new OFMessageHandler(dpid, rep.build()));
598 }
599 } else {
600 executorMsgs.execute(new OFMessageHandler(dpid, reply));
601 }
602 break;
603 default:
604 log.warn("Discarding unknown stats reply type {}", reply.getStatsType());
605 break;
606 }
607 }
608
sangho6a0bb172015-02-05 12:24:48 -0800609 private synchronized Collection<OFFlowStatsEntry> publishFlowStats(Dpid dpid,
610 OFFlowStatsReply reply) {
alshabib64def642014-12-02 23:27:37 -0800611 //TODO: Get rid of synchronized
sangho6a0bb172015-02-05 12:24:48 -0800612 fullFlowStats.putAll(dpid, reply.getEntries());
alshabib64def642014-12-02 23:27:37 -0800613 if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
sangho6a0bb172015-02-05 12:24:48 -0800614 return fullFlowStats.removeAll(dpid);
615 }
616 return null;
617 }
618
Cem Türker3baff672017-10-12 15:09:01 +0300619 private synchronized Collection<OFFlowLightweightStatsEntry> publishFlowStatsLightweight(
620 Dpid dpid,
621 OFFlowLightweightStatsReply reply) {
622 //TODO: Get rid of synchronized
623 fullFlowLightweightStats.putAll(dpid, reply.getEntries());
624 if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
625 return fullFlowLightweightStats.removeAll(dpid);
626 }
627 return null;
628 }
629
Srikanth Vavilapalli95810f52015-09-14 15:49:56 -0700630 private synchronized Collection<OFTableStatsEntry> publishTableStats(Dpid dpid,
631 OFTableStatsReply reply) {
632 //TODO: Get rid of synchronized
633 fullTableStats.putAll(dpid, reply.getEntries());
634 if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
635 return fullTableStats.removeAll(dpid);
636 }
637 return null;
638 }
639
sangho6a0bb172015-02-05 12:24:48 -0800640 private synchronized Collection<OFGroupStatsEntry> publishGroupStats(Dpid dpid,
641 OFGroupStatsReply reply) {
642 //TODO: Get rid of synchronized
643 fullGroupStats.putAll(dpid, reply.getEntries());
644 if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
645 return fullGroupStats.removeAll(dpid);
646 }
647 return null;
648 }
649
650 private synchronized Collection<OFGroupDescStatsEntry> publishGroupDescStats(Dpid dpid,
651 OFGroupDescStatsReply reply) {
652 //TODO: Get rid of synchronized
653 fullGroupDescStats.putAll(dpid, reply.getEntries());
654 if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
655 return fullGroupDescStats.removeAll(dpid);
alshabib64def642014-12-02 23:27:37 -0800656 }
657 return null;
658 }
659
Ozge AYAZ60aded22017-06-20 08:35:30 +0000660 private synchronized Collection<OFQueueStatsEntry> publishQueueStats(Dpid dpid, OFQueueStatsReply reply) {
661 fullQueueStats.putAll(dpid, reply.getEntries());
662 if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
663 return fullQueueStats.removeAll(dpid);
664 }
665 return null;
666 }
667
tom7ef8ff92014-09-17 13:08:06 -0700668 @Override
669 public void setRole(Dpid dpid, RoleState role) {
Yuta HIGUCHI79cbd1c2014-10-02 16:57:57 -0700670 final OpenFlowSwitch sw = getSwitch(dpid);
671 if (sw == null) {
672 log.debug("Switch not connected. Ignoring setRole({}, {})", dpid, role);
673 return;
674 }
675 sw.setRole(role);
tom7ef8ff92014-09-17 13:08:06 -0700676 }
677
678 /**
679 * Implementation of an OpenFlow Agent which is responsible for
680 * keeping track of connected switches and the state in which
681 * they are.
682 */
683 public class OpenFlowSwitchAgent implements OpenFlowAgent {
684
685 private final Logger log = LoggerFactory.getLogger(OpenFlowSwitchAgent.class);
686 private final Lock switchLock = new ReentrantLock();
687
688 @Override
689 public boolean addConnectedSwitch(Dpid dpid, OpenFlowSwitch sw) {
alshabib9eab22f2014-10-20 17:17:31 -0700690
tom7ef8ff92014-09-17 13:08:06 -0700691 if (connectedSwitches.get(dpid) != null) {
692 log.error("Trying to add connectedSwitch but found a previous "
693 + "value for dpid: {}", dpid);
694 return false;
695 } else {
Yuta HIGUCHIeb3f30b2014-10-22 11:34:49 -0700696 log.info("Added switch {}", dpid);
tom7ef8ff92014-09-17 13:08:06 -0700697 connectedSwitches.put(dpid, sw);
alshabib8f1cf4a2014-09-17 14:44:48 -0700698 for (OpenFlowSwitchListener l : ofSwitchListener) {
tom7ef8ff92014-09-17 13:08:06 -0700699 l.switchAdded(dpid);
700 }
701 return true;
702 }
703 }
704
705 @Override
706 public boolean validActivation(Dpid dpid) {
707 if (connectedSwitches.get(dpid) == null) {
708 log.error("Trying to activate switch but is not in "
709 + "connected switches: dpid {}. Aborting ..",
710 dpid);
711 return false;
712 }
713 if (activeMasterSwitches.get(dpid) != null ||
714 activeEqualSwitches.get(dpid) != null) {
715 log.error("Trying to activate switch but it is already "
716 + "activated: dpid {}. Found in activeMaster: {} "
Ray Milkey6bc43c22015-11-06 13:22:38 -0800717 + "Found in activeEqual: {}. Aborting ..",
718 dpid,
719 (activeMasterSwitches.get(dpid) == null) ? 'N' : 'Y',
720 (activeEqualSwitches.get(dpid) == null) ? 'N' : 'Y');
tom7ef8ff92014-09-17 13:08:06 -0700721 return false;
722 }
723 return true;
724 }
725
726
727 @Override
728 public boolean addActivatedMasterSwitch(Dpid dpid, OpenFlowSwitch sw) {
729 switchLock.lock();
730 try {
731 if (!validActivation(dpid)) {
732 return false;
733 }
734 activeMasterSwitches.put(dpid, sw);
735 return true;
736 } finally {
737 switchLock.unlock();
738 }
739 }
740
741 @Override
742 public boolean addActivatedEqualSwitch(Dpid dpid, OpenFlowSwitch sw) {
743 switchLock.lock();
744 try {
745 if (!validActivation(dpid)) {
746 return false;
747 }
748 activeEqualSwitches.put(dpid, sw);
749 log.info("Added Activated EQUAL Switch {}", dpid);
750 return true;
751 } finally {
752 switchLock.unlock();
753 }
754 }
755
756 @Override
757 public void transitionToMasterSwitch(Dpid dpid) {
758 switchLock.lock();
759 try {
760 if (activeMasterSwitches.containsKey(dpid)) {
761 return;
762 }
763 OpenFlowSwitch sw = activeEqualSwitches.remove(dpid);
764 if (sw == null) {
765 sw = getSwitch(dpid);
766 if (sw == null) {
767 log.error("Transition to master called on sw {}, but switch "
768 + "was not found in controller-cache", dpid);
769 return;
770 }
771 }
772 log.info("Transitioned switch {} to MASTER", dpid);
773 activeMasterSwitches.put(dpid, sw);
774 } finally {
775 switchLock.unlock();
776 }
777 }
778
779
780 @Override
781 public void transitionToEqualSwitch(Dpid dpid) {
782 switchLock.lock();
783 try {
784 if (activeEqualSwitches.containsKey(dpid)) {
785 return;
786 }
787 OpenFlowSwitch sw = activeMasterSwitches.remove(dpid);
788 if (sw == null) {
789 sw = getSwitch(dpid);
790 if (sw == null) {
791 log.error("Transition to equal called on sw {}, but switch "
792 + "was not found in controller-cache", dpid);
793 return;
794 }
795 }
796 log.info("Transitioned switch {} to EQUAL", dpid);
797 activeEqualSwitches.put(dpid, sw);
798 } finally {
799 switchLock.unlock();
800 }
801
802 }
803
804 @Override
805 public void removeConnectedSwitch(Dpid dpid) {
806 connectedSwitches.remove(dpid);
807 OpenFlowSwitch sw = activeMasterSwitches.remove(dpid);
808 if (sw == null) {
Thomas Vachuska3358af22015-05-19 18:40:34 -0700809 log.debug("sw was null for {}", dpid);
tom7ef8ff92014-09-17 13:08:06 -0700810 sw = activeEqualSwitches.remove(dpid);
811 }
alshabib8f1cf4a2014-09-17 14:44:48 -0700812 for (OpenFlowSwitchListener l : ofSwitchListener) {
tom7ef8ff92014-09-17 13:08:06 -0700813 l.switchRemoved(dpid);
814 }
815 }
816
817 @Override
Jian Lia78cdb22016-04-21 13:03:58 -0700818 public void processDownstreamMessage(Dpid dpid, List<OFMessage> m) {
819 for (OpenFlowMessageListener listener : ofMessageListener) {
820 listener.handleOutgoingMessage(dpid, m);
821 }
822 }
823
824
825 @Override
tom7ef8ff92014-09-17 13:08:06 -0700826 public void processMessage(Dpid dpid, OFMessage m) {
827 processPacket(dpid, m);
Jian Lia78cdb22016-04-21 13:03:58 -0700828
829 for (OpenFlowMessageListener listener : ofMessageListener) {
830 listener.handleIncomingMessage(dpid, m);
831 }
tom7ef8ff92014-09-17 13:08:06 -0700832 }
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700833
834 @Override
Ayaka Koshibe3ef2b0d2014-10-31 13:58:27 -0700835 public void returnRoleReply(Dpid dpid, RoleState requested, RoleState response) {
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700836 for (OpenFlowSwitchListener l : ofSwitchListener) {
Ayaka Koshibe3ef2b0d2014-10-31 13:58:27 -0700837 l.receivedRoleReply(dpid, requested, response);
Ayaka Koshibeab91cc42014-09-25 10:20:52 -0700838 }
839 }
tom7ef8ff92014-09-17 13:08:06 -0700840 }
841
Jian Li152b8852015-12-07 14:47:25 -0800842 /**
Jian Li2266bff2016-04-21 11:01:25 -0700843 * OpenFlow message handler.
Jian Li152b8852015-12-07 14:47:25 -0800844 */
Ray Milkey7ec0d1b2015-11-13 08:51:35 -0800845 protected final class OFMessageHandler implements Runnable {
alshabib8f1cf4a2014-09-17 14:44:48 -0700846
Ray Milkey9c9cde42018-01-12 14:22:06 -0800847 final OFMessage msg;
848 final Dpid dpid;
alshabib8f1cf4a2014-09-17 14:44:48 -0700849
850 public OFMessageHandler(Dpid dpid, OFMessage msg) {
851 this.msg = msg;
852 this.dpid = dpid;
853 }
854
855 @Override
856 public void run() {
alshabibeec3a062014-09-17 18:01:26 -0700857 for (OpenFlowEventListener listener : ofEventListener) {
alshabib8f1cf4a2014-09-17 14:44:48 -0700858 listener.handleMessage(dpid, msg);
859 }
860 }
alshabib8f1cf4a2014-09-17 14:44:48 -0700861 }
tom7ef8ff92014-09-17 13:08:06 -0700862}