blob: 6fe5cc93f069f878158f563789ae37b804fa903d [file] [log] [blame]
Brian O'Connorc67f9fa2014-08-07 18:17:46 -07001package net.floodlightcontroller.core.internal;
2
3/**
4 * Copyright 2012, Big Switch Networks, Inc.
5 * Originally created by David Erickson, Stanford University
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License"); you may
8 * not use this file except in compliance with the License. You may obtain
9 * a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 * License for the specific language governing permissions and limitations
17 * under the License.
18 **/
19
20import java.io.IOException;
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.Date;
25import java.util.HashMap;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.Map;
29import java.util.Set;
30import java.util.WeakHashMap;
31import java.util.concurrent.ConcurrentHashMap;
32import java.util.concurrent.ConcurrentMap;
33import java.util.concurrent.Future;
34import java.util.concurrent.atomic.AtomicInteger;
35import java.util.concurrent.locks.Lock;
36import java.util.concurrent.locks.ReentrantReadWriteLock;
37
38import net.floodlightcontroller.core.FloodlightContext;
39import net.floodlightcontroller.core.IFloodlightProviderService;
40import net.floodlightcontroller.core.IFloodlightProviderService.Role;
41import net.floodlightcontroller.core.IOFMessageListener;
42import net.floodlightcontroller.core.IOFSwitch;
43import net.floodlightcontroller.core.SwitchDriverSubHandshakeAlreadyStarted;
44import net.floodlightcontroller.core.SwitchDriverSubHandshakeCompleted;
45import net.floodlightcontroller.core.SwitchDriverSubHandshakeNotStarted;
46import net.floodlightcontroller.core.annotations.LogMessageDoc;
47import net.floodlightcontroller.core.web.serializers.DPIDSerializer;
48import net.floodlightcontroller.debugcounter.IDebugCounter;
49import net.floodlightcontroller.debugcounter.IDebugCounterService;
50import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterException;
51import net.floodlightcontroller.debugcounter.IDebugCounterService.CounterType;
52import net.floodlightcontroller.debugcounter.NullDebugCounter;
53import net.floodlightcontroller.threadpool.IThreadPoolService;
54import net.floodlightcontroller.util.LinkedHashSetWrapper;
55import net.floodlightcontroller.util.OrderedCollection;
Brian O'Connorc67f9fa2014-08-07 18:17:46 -070056
57import org.codehaus.jackson.annotate.JsonIgnore;
58import org.codehaus.jackson.annotate.JsonProperty;
59import org.codehaus.jackson.map.annotate.JsonSerialize;
60import org.jboss.netty.channel.Channel;
61import org.projectfloodlight.openflow.protocol.OFActionType;
62import org.projectfloodlight.openflow.protocol.OFCapabilities;
63import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
Jonathan Harta213bce2014-08-11 15:44:07 -070064import org.projectfloodlight.openflow.protocol.OFFactories;
65import org.projectfloodlight.openflow.protocol.OFFactory;
Brian O'Connorc67f9fa2014-08-07 18:17:46 -070066import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
67import org.projectfloodlight.openflow.protocol.OFMessage;
68import org.projectfloodlight.openflow.protocol.OFPortConfig;
69import org.projectfloodlight.openflow.protocol.OFPortDesc;
70import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
71import org.projectfloodlight.openflow.protocol.OFPortReason;
72import org.projectfloodlight.openflow.protocol.OFPortState;
73import org.projectfloodlight.openflow.protocol.OFPortStatus;
74import org.projectfloodlight.openflow.protocol.OFStatsReply;
75import org.projectfloodlight.openflow.protocol.OFStatsRequest;
76import org.projectfloodlight.openflow.protocol.OFType;
77import org.projectfloodlight.openflow.protocol.OFVersion;
78import org.projectfloodlight.openflow.types.DatapathId;
79import org.projectfloodlight.openflow.types.OFAuxId;
80import org.projectfloodlight.openflow.types.OFGroup;
81import org.projectfloodlight.openflow.types.OFPort;
82import org.projectfloodlight.openflow.types.TableId;
83import org.projectfloodlight.openflow.types.U64;
84import org.slf4j.Logger;
85import org.slf4j.LoggerFactory;
86
87
88/**
89 * This is the internal representation of an openflow switch.
90 */
91public class OFSwitchImplBase implements IOFSwitch {
92 // TODO: should we really do logging in the class or should we throw
93 // exception that can then be handled by callers?
94 protected final static Logger log = LoggerFactory.getLogger(OFSwitchImplBase.class);
95
96 private static final String HA_CHECK_SWITCH =
97 "Check the health of the indicated switch. If the problem " +
98 "persists or occurs repeatedly, it likely indicates a defect " +
99 "in the switch HA implementation.";
100
101 protected ConcurrentMap<Object, Object> attributes;
102 protected IFloodlightProviderService floodlightProvider;
103 protected IThreadPoolService threadPool;
104 protected Date connectedSince;
105 protected String stringId;
106 protected Channel channel;
107 // transaction id used for messages sent out to this switch from
108 // this controller instance. This xid has significance only between this
109 // controller<->switch pair.
110 protected AtomicInteger transactionIdSource;
111
112 // generation id used for roleRequest messages sent to switches (see section
113 // 6.3.5 of the OF1.3.4 spec). This generationId has significance between
114 // all the controllers that this switch is connected to; and only for role
115 // request messages with role MASTER or SLAVE. The set of Controllers that
116 // this switch is connected to should coordinate the next generation id,
117 // via transactional semantics.
118 protected long generationIdSource;
119
120 // Lock to protect modification of the port maps. We only need to
121 // synchronize on modifications. For read operations we are fine since
122 // we rely on ConcurrentMaps which works for our use case.
123 //private Object portLock; XXX S remove this
124
125 // Map port numbers to the appropriate OFPortDesc
126 protected ConcurrentHashMap<Integer, OFPortDesc> portsByNumber;
127 // Map port names to the appropriate OFPhyiscalPort
128 // XXX: The OF spec doesn't specify if port names need to be unique but
129 // according it's always the case in practice.
130 protected ConcurrentHashMap<String, OFPortDesc> portsByName;
131 protected Map<Integer, OFStatisticsFuture> statsFutureMap;
132 protected Map<Integer, IOFMessageListener> iofMsgListenersMap; // XXX S why is this needed?
133 protected Map<Integer, OFFeaturesReplyFuture> featuresFutureMap;
134 protected boolean connected;
135 protected Role role;
Brian O'Connorc67f9fa2014-08-07 18:17:46 -0700136 protected ReentrantReadWriteLock listenerLock;
137 protected ConcurrentMap<Short, Long> portBroadcastCacheHitMap;
138 /**
139 * When sending a role request message, the role request is added
140 * to this queue. If a role reply is received this queue is checked to
141 * verify that the reply matches the expected reply. We require in order
142 * delivery of replies. That's why we use a Queue.
143 * The RoleChanger uses a timeout to ensure we receive a timely reply.
144 * <p/>
145 * Need to synchronize on this instance if a request is sent, received,
146 * checked.
147 */
148 protected LinkedList<PendingRoleRequestEntry> pendingRoleRequests;
149
150 /** OpenFlow version for this switch */
151 protected OFVersion ofversion;
152 // Description stats reply describing this switch
153 private OFDescStatsReply switchDescription;
154 // Switch features from initial featuresReply
155 protected Set<OFCapabilities> capabilities;
156 protected int buffers;
157 protected Set<OFActionType> actions;
158 protected byte tables;
159 protected DatapathId datapathId;
160 private OFAuxId auxId;
161
162 private IDebugCounterService debugCounters;
163 private boolean debugCountersRegistered;
164 @SuppressWarnings("unused")
165 private IDebugCounter ctrSwitch, ctrSwitchPktin, ctrSwitchWrite,
166 ctrSwitchPktinDrops, ctrSwitchWriteDrops;
167
168 protected boolean startDriverHandshakeCalled = false;
169 private boolean flowTableFull = false;
170
171 private final PortManager portManager;
172
173
174
175 protected static final ThreadLocal<Map<OFSwitchImplBase, List<OFMessage>>> local_msg_buffer =
176 new ThreadLocal<Map<OFSwitchImplBase, List<OFMessage>>>() {
177 @Override
178 protected Map<OFSwitchImplBase, List<OFMessage>> initialValue() {
179 return new WeakHashMap<OFSwitchImplBase, List<OFMessage>>();
180 }
181 };
182
183
184 private static final String BASE = "switchbase";
185
186 protected static class PendingRoleRequestEntry {
187 protected int xid;
188 protected Role role;
189 // cookie is used to identify the role "generation". roleChanger uses
190 protected long cookie;
191
192 public PendingRoleRequestEntry(int xid, Role role, long cookie) {
193 this.xid = xid;
194 this.role = role;
195 this.cookie = cookie;
196 }
197 }
198
199 public OFSwitchImplBase() {
200 this.stringId = null;
201 this.attributes = new ConcurrentHashMap<Object, Object>();
202 this.connectedSince = new Date();
203 this.transactionIdSource = new AtomicInteger();
204 this.generationIdSource = 0; // XXX S this is wrong; should be negotiated
205 // XXX S no need this.portLock = new Object();
206 this.portsByNumber = new ConcurrentHashMap<Integer, OFPortDesc>();
207 this.portsByName = new ConcurrentHashMap<String, OFPortDesc>();
208 this.connected = true;
209 this.statsFutureMap = new ConcurrentHashMap<Integer, OFStatisticsFuture>();
210 this.featuresFutureMap = new ConcurrentHashMap<Integer, OFFeaturesReplyFuture>();
211 this.iofMsgListenersMap = new ConcurrentHashMap<Integer, IOFMessageListener>();
212 this.role = null;
Brian O'Connorc67f9fa2014-08-07 18:17:46 -0700213 this.listenerLock = new ReentrantReadWriteLock();
214 this.pendingRoleRequests = new LinkedList<OFSwitchImplBase.PendingRoleRequestEntry>();
215 this.portManager = new PortManager();
216 // by default the base impl declares no support for Nx_role_requests.
217 // OF1.0 switches like OVS that do support these messages should set the
218 // attribute in the associated switch driver.
219 setAttribute(SWITCH_SUPPORTS_NX_ROLE, false);
220
221 }
222
223 //*******************************************
224 // Setters and Getters
225 //*******************************************
226
227 @Override
228 public Object getAttribute(String name) {
229 if (this.attributes.containsKey(name)) {
230 return this.attributes.get(name);
231 }
232 return null;
233 }
234
235 @Override
236 public ConcurrentMap<Object, Object> getAttributes() {
237 return this.attributes;
238 }
239
240 @Override
241 public void setAttribute(String name, Object value) {
242 this.attributes.put(name, value);
243 return;
244 }
245
246 @Override
247 public Object removeAttribute(String name) {
248 return this.attributes.remove(name);
249 }
250
251 @Override
252 public boolean hasAttribute(String name) {
253 return this.attributes.containsKey(name);
254 }
255
256 @Override
257 @JsonSerialize(using = DPIDSerializer.class)
258 @JsonProperty("dpid")
259 public long getId() {
260 if (this.stringId == null)
261 throw new RuntimeException("Features reply has not yet been set");
262 return this.datapathId.getLong();
263 }
264
265 @JsonIgnore
266 @Override
267 public String getStringId() {
268 return stringId;
269 }
270
Jonathan Harta213bce2014-08-11 15:44:07 -0700271 @Override
272 public OFFactory getFactory() {
273 return OFFactories.getFactory(ofversion);
Brian O'Connorc67f9fa2014-08-07 18:17:46 -0700274 }
275
Jonathan Harta213bce2014-08-11 15:44:07 -0700276 @Override
277 public OFVersion getOFVersion() {
278 return ofversion;
279 }
280
281 @Override
Brian O'Connorc67f9fa2014-08-07 18:17:46 -0700282 public void setOFVersion(OFVersion ofv) {
Jonathan Harta213bce2014-08-11 15:44:07 -0700283 ofversion = ofv;
Brian O'Connorc67f9fa2014-08-07 18:17:46 -0700284 }
285
286 /**
287 * @param floodlightProvider the floodlightProvider to set
288 */
289 @JsonIgnore
290 @Override
291 public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
292 this.floodlightProvider = floodlightProvider;
293 }
294
295 @JsonIgnore
296 @Override
297 public void setThreadPoolService(IThreadPoolService tp) {
298 this.threadPool = tp;
299 }
300
301 @Override
302 @JsonIgnore
303 public void setDebugCounterService(IDebugCounterService debugCounters)
304 throws CounterException {
305 this.debugCounters = debugCounters;
306 registerOverloadCounters();
307 }
308
309 /* (non-Javadoc)
310 * @see java.lang.Object#toString()
311 */
312 @Override
313 public String toString() {
314 return "OFSwitchImpl [" + ((channel != null) ? channel.getRemoteAddress() : "?")
315 + " DPID[" + ((stringId != null) ? stringId : "?") + "]]";
316 }
317
318
319 //*******************************************
320 // Channel related methods
321 //*******************************************
322
323 @JsonIgnore
324 @Override
325 public void setChannel(Channel channel) {
326 this.channel = channel;
327 }
328
329 @Override
330 public void write(OFMessage m, FloodlightContext bc) throws IOException {
331 Map<OFSwitchImplBase, List<OFMessage>> msg_buffer_map = local_msg_buffer.get();
332 List<OFMessage> msg_buffer = msg_buffer_map.get(this);
333 if (msg_buffer == null) {
334 msg_buffer = new ArrayList<OFMessage>();
335 msg_buffer_map.put(this, msg_buffer);
336 }
337 // XXX S will change when iFloodlight provider changes
338 //this.floodlightProvider.handleOutgoingMessage(this, m, bc);
339 msg_buffer.add(m);
340
341 if ((msg_buffer.size() >= Controller.BATCH_MAX_SIZE) ||
342 ((m.getType() != OFType.PACKET_OUT) && (m.getType() != OFType.FLOW_MOD))) {
343 this.write(msg_buffer);
344 msg_buffer.clear();
345 }
346 }
347
348 @Override
349 @LogMessageDoc(level = "WARN",
350 message = "Sending OF message that modifies switch " +
351 "state while in the slave role: {switch}",
352 explanation = "An application has sent a message to a switch " +
353 "that is not valid when the switch is in a slave role",
354 recommendation = LogMessageDoc.REPORT_CONTROLLER_BUG)
355 public void write(List<OFMessage> msglist,
356 FloodlightContext bc) throws IOException {
357 for (OFMessage m : msglist) {
358 if (role == Role.SLAVE) {
359 switch (m.getType()) {
360 case PACKET_OUT:
361 case FLOW_MOD:
362 case PORT_MOD:
363 log.warn("Sending OF message that modifies switch " +
364 "state while in the slave role: {}",
365 m.getType().name());
366 break;
367 default:
368 break;
369 }
370 }
371 // XXX S again
372 //this.floodlightProvider.handleOutgoingMessage(this, m, bc);
373 }
374 this.write(msglist);
375 }
376
377 public void write(List<OFMessage> msglist) throws IOException {
378 this.channel.write(msglist);
379 }
380
381 @Override
382 public void disconnectSwitch() {
383 channel.close();
384 }
385
386 @Override
387 public Date getConnectedSince() {
388 return connectedSince;
389 }
390
391 @JsonIgnore
392 @Override
393 public int getNextTransactionId() {
394 return this.transactionIdSource.incrementAndGet();
395 }
396
397 @JsonIgnore
398 @Override
399 public synchronized boolean isConnected() {
400 return connected;
401 }
402
403 @Override
404 @JsonIgnore
405 public synchronized void setConnected(boolean connected) {
406 this.connected = connected;
407 }
408
409
410
411 //*******************************************
412 // Switch features related methods
413 //*******************************************
414
415 /**
416 * Set the features reply for this switch from the handshake
417 */
418 protected void setFeaturesReply(OFFeaturesReply featuresReply) {
419 if (featuresReply == null) {
420 log.error("Error setting featuresReply for switch: {}", getStringId());
421 return;
422 }
423 this.datapathId = featuresReply.getDatapathId();
424 this.capabilities = featuresReply.getCapabilities();
425 this.buffers = (int) featuresReply.getNBuffers();
426 this.tables = (byte) featuresReply.getNTables();
427 this.stringId = this.datapathId.toString();
428 if (ofversion == OFVersion.OF_13) {
429 auxId = featuresReply.getAuxiliaryId();
430 if (!auxId.equals(OFAuxId.MAIN)) {
431 log.warn("This controller does not handle auxiliary connections. "
432 + "Aux connection id {} received from switch {}",
433 auxId, getStringId());
434 }
435 }
436
437 if (ofversion == OFVersion.OF_10) {
438 this.actions = featuresReply.getActions();
439 portManager.compareAndUpdatePorts(featuresReply.getPorts(), true);
440 }
441 }
442
443 /**
444 * Set the port descriptions for this switch from the handshake for
445 * an OF1.3 switch.
446 */
447 protected void setPortDescReply(OFPortDescStatsReply pdrep) {
448 if (ofversion != OFVersion.OF_13) return;
449 if (pdrep == null) {
450 log.error("Error setting ports description for switch: {}", getStringId());
451 return;
452 }
453 portManager.updatePorts(pdrep.getEntries());
454 }
455
456 @Override
457 public int getNumBuffers() {
458 return buffers;
459 }
460 @Override
461 public Set<OFActionType> getActions() {
462 return actions;
463 }
464 @Override
465 public Set<OFCapabilities> getCapabilities() {
466 return capabilities;
467 }
468 @Override
469 public byte getNumTables() {
470 return tables;
471 }
472
473// public Future<OFFeaturesReply> getFeaturesReplyFromSwitch()
474// throws IOException {
475// // XXX S fix this later
476// OFMessage request = floodlightProvider.getOFMessageFactory_13()
477// .buildFeaturesRequest()
478// .setXid(getNextTransactionId())
479// .build();
480// OFFeaturesReplyFuture future =
481// new OFFeaturesReplyFuture(threadPool, this, (int) request.getXid());
482// this.featuresFutureMap.put((int) request.getXid(), future);
483// this.channel.write(Collections.singletonList(request));
484// return future;
485//
486// }
487//
488// public void deliverOFFeaturesReply(OFMessage reply) {
489// OFFeaturesReplyFuture future = this.featuresFutureMap.get(reply.getXid());
490// if (future != null) {
491// future.deliverFuture(this, reply);
492// // The future will ultimately unregister itself and call
493// // cancelFeaturesReply
494// return;
495// }
496// log.error("Switch {}: received unexpected featureReply", this);
497// }
498
499 @Override
500 public void cancelFeaturesReply(int transactionId) {
501 this.featuresFutureMap.remove(transactionId);
502 }
503
504 @JsonIgnore
505 public void setSwitchDescription(OFDescStatsReply desc) {
506 switchDescription = desc;
507 }
508
509 @Override
510 @JsonIgnore
511 public OFDescStatsReply getSwitchDescription() {
512 return switchDescription;
513 }
514
515 //*******************************************
516 // Switch port handling
517 //*******************************************
518
519 @Override
520 @JsonIgnore
521 public Collection<OFPortDesc> getEnabledPorts() {
522 return portManager.getEnabledPorts();
523 }
524
525 @Override
526 @JsonIgnore
527 public Collection<Integer> getEnabledPortNumbers() {
528 return portManager.getEnabledPortNumbers();
529 }
530
531 @Override
532 public OFPortDesc getPort(int portNumber) {
533 return portManager.getPort(portNumber);
534 }
535
536 @Override
537 public OFPortDesc getPort(String portName) {
538 return portManager.getPort(portName);
539 }
540
541 @Override
542 @JsonIgnore
543 public OrderedCollection<PortChangeEvent> processOFPortStatus(OFPortStatus ps) {
544 return portManager.handlePortStatusMessage(ps);
545 }
546
547 @Override
548 @JsonProperty("ports")
549 public Collection<OFPortDesc> getPorts() {
550 return portManager.getPorts();
551 }
552
553 @Override
554 public boolean portEnabled(int portNumber) {
555 return isEnabled(portManager.getPort(portNumber));
556 }
557
558 @Override
559 public boolean portEnabled(String portName) {
560 return isEnabled(portManager.getPort(portName));
561 }
562
563 private boolean isEnabled(OFPortDesc p) {
564 return (p != null &&
565 !p.getState().contains(OFPortState.LINK_DOWN) &&
566 !p.getState().contains(OFPortState.BLOCKED) &&
567 !p.getConfig().contains(OFPortConfig.PORT_DOWN));
568 }
569
570 @Override
571 public OrderedCollection<PortChangeEvent> comparePorts(Collection<OFPortDesc> ports) {
572 return portManager.comparePorts(ports);
573 }
574
575 @Override
576 @JsonIgnore
577 public OrderedCollection<PortChangeEvent> setPorts(Collection<OFPortDesc> ports) {
578 return portManager.updatePorts(ports);
579 }
580
581 /**
582 * Manages the ports of this switch.
583 *
584 * Provides methods to query and update the stored ports. The class ensures
585 * that every port name and port number is unique. When updating ports
586 * the class checks if port number <-> port name mappings have change due
587 * to the update. If a new port P has number and port that are inconsistent
588 * with the previous mapping(s) the class will delete all previous ports
589 * with name or number of the new port and then add the new port.
590 *
591 * Port names are stored as-is but they are compared case-insensitive
592 *
593 * The methods that change the stored ports return a list of
594 * PortChangeEvents that represent the changes that have been applied
595 * to the port list so that IOFSwitchListeners can be notified about the
596 * changes.
597 *
598 * Implementation notes:
599 * - We keep several different representations of the ports to allow for
600 * fast lookups
601 * - Ports are stored in unchangeable lists. When a port is modified new
602 * data structures are allocated.
603 * - We use a read-write-lock for synchronization, so multiple readers are
604 * allowed.
605 * - All port numbers have int representation (no more shorts)
606 */
607 protected class PortManager {
608 private final ReentrantReadWriteLock lock;
609 private List<OFPortDesc> portList;
610 private List<OFPortDesc> enabledPortList;
611 private List<Integer> enabledPortNumbers;
612 private Map<Integer,OFPortDesc> portsByNumber;
613 private Map<String,OFPortDesc> portsByName;
614
615 public PortManager() {
616 this.lock = new ReentrantReadWriteLock();
617 this.portList = Collections.emptyList();
618 this.enabledPortList = Collections.emptyList();
619 this.enabledPortNumbers = Collections.emptyList();
620 this.portsByName = Collections.emptyMap();
621 this.portsByNumber = Collections.emptyMap();
622 }
623
624 /**
625 * Set the internal data structure storing this switch's port
626 * to the ports specified by newPortsByNumber
627 *
628 * CALLER MUST HOLD WRITELOCK
629 *
630 * @param newPortsByNumber
631 * @throws IllegaalStateException if called without holding the
632 * writelock
633 */
634 private void updatePortsWithNewPortsByNumber(
635 Map<Integer,OFPortDesc> newPortsByNumber) {
636 if (!lock.writeLock().isHeldByCurrentThread()) {
637 throw new IllegalStateException("Method called without " +
638 "holding writeLock");
639 }
640 Map<String,OFPortDesc> newPortsByName =
641 new HashMap<String, OFPortDesc>();
642 List<OFPortDesc> newPortList =
643 new ArrayList<OFPortDesc>();
644 List<OFPortDesc> newEnabledPortList =
645 new ArrayList<OFPortDesc>();
646 List<Integer> newEnabledPortNumbers = new ArrayList<Integer>();
647
648 for(OFPortDesc p: newPortsByNumber.values()) {
649 newPortList.add(p);
650 newPortsByName.put(p.getName().toLowerCase(), p);
651 if (isEnabled(p)) {
652 newEnabledPortList.add(p);
653 newEnabledPortNumbers.add(p.getPortNo().getPortNumber());
654 }
655 }
656 portsByName = Collections.unmodifiableMap(newPortsByName);
657 portsByNumber =
658 Collections.unmodifiableMap(newPortsByNumber);
659 enabledPortList =
660 Collections.unmodifiableList(newEnabledPortList);
661 enabledPortNumbers =
662 Collections.unmodifiableList(newEnabledPortNumbers);
663 portList = Collections.unmodifiableList(newPortList);
664 }
665
666 /**
667 * Handle a OFPortStatus delete message for the given port.
668 * Updates the internal port maps/lists of this switch and returns
669 * the PortChangeEvents caused by the delete. If the given port
670 * exists as it, it will be deleted. If the name<->number for the
671 * given port is inconsistent with the ports stored by this switch
672 * the method will delete all ports with the number or name of the
673 * given port.
674 *
675 * This method will increment error/warn counters and log
676 *
677 * @param delPort the port from the port status message that should
678 * be deleted.
679 * @return ordered collection of port changes applied to this switch
680 */
681 private OrderedCollection<PortChangeEvent> handlePortStatusDelete(
682 OFPortDesc delPort) {
683 lock.writeLock().lock();
684 OrderedCollection<PortChangeEvent> events =
685 new LinkedHashSetWrapper<PortChangeEvent>();
686 try {
687 Map<Integer,OFPortDesc> newPortByNumber =
688 new HashMap<Integer, OFPortDesc>(portsByNumber);
689 OFPortDesc prevPort =
690 portsByNumber.get(delPort.getPortNo().getPortNumber());
691 if (prevPort == null) {
692 // so such port. Do we have a port with the name?
693 prevPort = portsByName.get(delPort.getName());
694 if (prevPort != null) {
695 newPortByNumber.remove(prevPort.getPortNo().getPortNumber());
696 events.add(new PortChangeEvent(prevPort,
697 PortChangeType.DELETE));
698 }
699 } else if (prevPort.getName().equals(delPort.getName())) {
700 // port exists with consistent name-number mapping
701 newPortByNumber.remove(delPort.getPortNo().getPortNumber());
702 events.add(new PortChangeEvent(delPort,
703 PortChangeType.DELETE));
704 } else {
705 // port with same number exists but its name differs. This
706 // is weird. The best we can do is to delete the existing
707 // port(s) that have delPort's name and number.
708 newPortByNumber.remove(delPort.getPortNo().getPortNumber());
709 events.add(new PortChangeEvent(prevPort,
710 PortChangeType.DELETE));
711 // is there another port that has delPort's name?
712 prevPort = portsByName.get(delPort.getName().toLowerCase());
713 if (prevPort != null) {
714 newPortByNumber.remove(prevPort.getPortNo().getPortNumber());
715 events.add(new PortChangeEvent(prevPort,
716 PortChangeType.DELETE));
717 }
718 }
719 updatePortsWithNewPortsByNumber(newPortByNumber);
720 return events;
721 } finally {
722 lock.writeLock().unlock();
723 }
724 }
725
726 /**
727 * Handle a OFPortStatus message, update the internal data structures
728 * that store ports and return the list of OFChangeEvents.
729 *
730 * This method will increment error/warn counters and log
731 *
732 * @param ps
733 * @return
734 */
735 public OrderedCollection<PortChangeEvent> handlePortStatusMessage(OFPortStatus ps) {
736 if (ps == null) {
737 throw new NullPointerException("OFPortStatus message must " +
738 "not be null");
739 }
740 lock.writeLock().lock();
741 try {
742 OFPortReason reason = ps.getReason();
743 if (reason == null) {
744 throw new IllegalArgumentException("Unknown PortStatus " +
745 "reason code " + ps.getReason());
746 }
747
748 if (log.isDebugEnabled()) {
749 log.debug("Handling OFPortStatus: {} for {}",
750 reason, ps);
751 }
752
753 if (reason == OFPortReason.DELETE)
754 return handlePortStatusDelete(ps.getDesc());
755
756 // We handle ADD and MODIFY the same way. Since OpenFlow
757 // doesn't specify what uniquely identifies a port the
758 // notion of ADD vs. MODIFY can also be hazy. So we just
759 // compare the new port to the existing ones.
760 Map<Integer,OFPortDesc> newPortByNumber =
761 new HashMap<Integer, OFPortDesc>(portsByNumber);
762 OrderedCollection<PortChangeEvent> events =
763 getSinglePortChanges(ps.getDesc());
764 for (PortChangeEvent e: events) {
765 switch(e.type) {
766 case DELETE:
767 newPortByNumber.remove(e.port.getPortNo().getPortNumber());
768 break;
769 case ADD:
770 if (reason != OFPortReason.ADD) {
771 // weird case
772 }
773 newPortByNumber.put(e.port.getPortNo().getPortNumber(),
774 e.port);
775 break;
776 case DOWN:
777 newPortByNumber.put(e.port.getPortNo().getPortNumber(),
778 e.port);
779 break;
780 case OTHER_UPDATE:
781 newPortByNumber.put(e.port.getPortNo().getPortNumber(),
782 e.port);
783 break;
784 case UP:
785 newPortByNumber.put(e.port.getPortNo().getPortNumber(),
786 e.port);
787 break;
788 }
789 }
790 updatePortsWithNewPortsByNumber(newPortByNumber);
791 return events;
792 } finally {
793 lock.writeLock().unlock();
794 }
795
796 }
797
798 /**
799 * Given a new or modified port newPort, returns the list of
800 * PortChangeEvents to "transform" the current ports stored by
801 * this switch to include / represent the new port. The ports stored
802 * by this switch are <b>NOT</b> updated.
803 *
804 * This method acquires the readlock and is thread-safe by itself.
805 * Most callers will need to acquire the write lock before calling
806 * this method though (if the caller wants to update the ports stored
807 * by this switch)
808 *
809 * @param newPort the new or modified port.
810 * @return the list of changes
811 */
812 public OrderedCollection<PortChangeEvent> getSinglePortChanges(
813 OFPortDesc newPort) {
814 lock.readLock().lock();
815 try {
816 OrderedCollection<PortChangeEvent> events =
817 new LinkedHashSetWrapper<PortChangeEvent>();
818 // Check if we have a port by the same number in our
819 // old map.
820 OFPortDesc prevPort =
821 portsByNumber.get(newPort.getPortNo().getPortNumber());
822 if (newPort.equals(prevPort)) {
823 // nothing has changed
824 return events;
825 }
826
827 if (prevPort != null &&
828 prevPort.getName().equals(newPort.getName())) {
829 // A simple modify of a existing port
830 // A previous port with this number exists and it's name
831 // also matches the new port. Find the differences
832 if (isEnabled(prevPort) && !isEnabled(newPort)) {
833 events.add(new PortChangeEvent(newPort,
834 PortChangeType.DOWN));
835 } else if (!isEnabled(prevPort) && isEnabled(newPort)) {
836 events.add(new PortChangeEvent(newPort,
837 PortChangeType.UP));
838 } else {
839 events.add(new PortChangeEvent(newPort,
840 PortChangeType.OTHER_UPDATE));
841 }
842 return events;
843 }
844
845 if (prevPort != null) {
846 // There exists a previous port with the same port
847 // number but the port name is different (otherwise we would
848 // never have gotten here)
849 // Remove the port. Name-number mapping(s) have changed
850 events.add(new PortChangeEvent(prevPort,
851 PortChangeType.DELETE));
852 }
853
854 // We now need to check if there exists a previous port sharing
855 // the same name as the new/updated port.
856 prevPort = portsByName.get(newPort.getName().toLowerCase());
857 if (prevPort != null) {
858 // There exists a previous port with the same port
859 // name but the port number is different (otherwise we
860 // never have gotten here).
861 // Remove the port. Name-number mapping(s) have changed
862 events.add(new PortChangeEvent(prevPort,
863 PortChangeType.DELETE));
864 }
865
866 // We always need to add the new port. Either no previous port
867 // existed or we just deleted previous ports with inconsistent
868 // name-number mappings
869 events.add(new PortChangeEvent(newPort, PortChangeType.ADD));
870 return events;
871 } finally {
872 lock.readLock().unlock();
873 }
874 }
875
876 /**
877 * Compare the current ports of this switch to the newPorts list and
878 * return the changes that would be applied to transfort the current
879 * ports to the new ports. No internal data structures are updated
880 * see {@link #compareAndUpdatePorts(List, boolean)}
881 *
882 * @param newPorts the list of new ports
883 * @return The list of differences between the current ports and
884 * newPortList
885 */
886 public OrderedCollection<PortChangeEvent> comparePorts(
887 Collection<OFPortDesc> newPorts) {
888 return compareAndUpdatePorts(newPorts, false);
889 }
890
891 /**
892 * Compare the current ports of this switch to the newPorts list and
893 * return the changes that would be applied to transform the current
894 * ports to the new ports. No internal data structures are updated
895 * see {@link #compareAndUpdatePorts(List, boolean)}
896 *
897 * @param newPorts the list of new ports
898 * @return The list of differences between the current ports and
899 * newPortList
900 */
901 public OrderedCollection<PortChangeEvent> updatePorts(
902 Collection<OFPortDesc> newPorts) {
903 return compareAndUpdatePorts(newPorts, true);
904 }
905
906 /**
907 * Compare the current ports stored in this switch instance with the
908 * new port list given and return the differences in the form of
909 * PortChangeEvents. If the doUpdate flag is true, newPortList will
910 * replace the current list of this switch (and update the port maps)
911 *
912 * Implementation note:
913 * Since this method can optionally modify the current ports and
914 * since it's not possible to upgrade a read-lock to a write-lock
915 * we need to hold the write-lock for the entire operation. If this
916 * becomes a problem and if compares() are common we can consider
917 * splitting in two methods but this requires lots of code duplication
918 *
919 * @param newPorts the list of new ports.
920 * @param doUpdate If true the newPortList will replace the current
921 * port list for this switch. If false this switch will not be changed.
922 * @return The list of differences between the current ports and
923 * newPorts
924 * @throws NullPointerException if newPortsList is null
925 * @throws IllegalArgumentException if either port names or port numbers
926 * are duplicated in the newPortsList.
927 */
928 private OrderedCollection<PortChangeEvent> compareAndUpdatePorts(
929 Collection<OFPortDesc> newPorts, boolean doUpdate) {
930 if (newPorts == null) {
931 throw new NullPointerException("newPortsList must not be null");
932 }
933 lock.writeLock().lock();
934 try {
935 OrderedCollection<PortChangeEvent> events =
936 new LinkedHashSetWrapper<PortChangeEvent>();
937
938 Map<Integer,OFPortDesc> newPortsByNumber =
939 new HashMap<Integer, OFPortDesc>();
940 Map<String,OFPortDesc> newPortsByName =
941 new HashMap<String, OFPortDesc>();
942 List<OFPortDesc> newEnabledPortList =
943 new ArrayList<OFPortDesc>();
944 List<Integer> newEnabledPortNumbers =
945 new ArrayList<Integer>();
946 List<OFPortDesc> newPortsList =
947 new ArrayList<OFPortDesc>(newPorts);
948
949 for (OFPortDesc p: newPortsList) {
950 if (p == null) {
951 throw new NullPointerException("portList must not " +
952 "contain null values");
953 }
954
955 // Add the port to the new maps and lists and check
956 // that every port is unique
957 OFPortDesc duplicatePort;
958 duplicatePort = newPortsByNumber.put(
959 p.getPortNo().getPortNumber(), p);
960 if (duplicatePort != null) {
961 String msg = String.format("Cannot have two ports " +
962 "with the same number: %s <-> %s",
963 p, duplicatePort);
964 throw new IllegalArgumentException(msg);
965 }
966 duplicatePort =
967 newPortsByName.put(p.getName().toLowerCase(), p);
968 if (duplicatePort != null) {
969 String msg = String.format("Cannot have two ports " +
970 "with the same name: %s <-> %s",
971 p.toString().substring(0,80),
972 duplicatePort.toString().substring(0, 80));
973 throw new IllegalArgumentException(msg);
974 }
975 if (isEnabled(p)) {
976 newEnabledPortList.add(p);
977 newEnabledPortNumbers.add(p.getPortNo().getPortNumber());
978 }
979
980 // get changes
981 events.addAll(getSinglePortChanges(p));
982 }
983 // find deleted ports
984 // We need to do this after looping through all the new ports
985 // to we can handle changed name<->number mappings correctly
986 // We could pull it into the loop of we address this but
987 // it's probably not worth it
988 for (OFPortDesc oldPort: this.portList) {
989 if (!newPortsByNumber.containsKey(oldPort.getPortNo().getPortNumber())) {
990 PortChangeEvent ev =
991 new PortChangeEvent(oldPort,
992 PortChangeType.DELETE);
993 events.add(ev);
994 }
995 }
996
997
998 if (doUpdate) {
999 portsByName = Collections.unmodifiableMap(newPortsByName);
1000 portsByNumber =
1001 Collections.unmodifiableMap(newPortsByNumber);
1002 enabledPortList =
1003 Collections.unmodifiableList(newEnabledPortList);
1004 enabledPortNumbers =
1005 Collections.unmodifiableList(newEnabledPortNumbers);
1006 portList = Collections.unmodifiableList(newPortsList);
1007 }
1008 return events;
1009 } finally {
1010 lock.writeLock().unlock();
1011 }
1012 }
1013
1014 public OFPortDesc getPort(String name) {
1015 if (name == null) {
1016 throw new NullPointerException("Port name must not be null");
1017 }
1018 lock.readLock().lock();
1019 try {
1020 return portsByName.get(name.toLowerCase());
1021 } finally {
1022 lock.readLock().unlock();
1023 }
1024 }
1025
1026 public OFPortDesc getPort(int portNumber) {
1027 lock.readLock().lock();
1028 try {
1029 return portsByNumber.get(portNumber);
1030 } finally {
1031 lock.readLock().unlock();
1032 }
1033 }
1034
1035 public List<OFPortDesc> getPorts() {
1036 lock.readLock().lock();
1037 try {
1038 return portList;
1039 } finally {
1040 lock.readLock().unlock();
1041 }
1042 }
1043
1044 public List<OFPortDesc> getEnabledPorts() {
1045 lock.readLock().lock();
1046 try {
1047 return enabledPortList;
1048 } finally {
1049 lock.readLock().unlock();
1050 }
1051 }
1052
1053 public List<Integer> getEnabledPortNumbers() {
1054 lock.readLock().lock();
1055 try {
1056 return enabledPortNumbers;
1057 } finally {
1058 lock.readLock().unlock();
1059 }
1060 }
1061 }
1062
1063
1064 //*******************************************
1065 // Switch stats handling
1066 //*******************************************
1067
1068 @Override
1069 public Future<List<OFStatsReply>> getStatistics(OFStatsRequest<?> request)
1070 throws IOException {
1071 OFStatisticsFuture future = new OFStatisticsFuture(threadPool, this,
1072 (int)request.getXid());
1073 log.info("description STAT REQUEST XID {}", request.getXid());
1074 this.statsFutureMap.put((int)request.getXid(), future);
1075
1076 this.channel.write(Collections.singletonList(request));
1077 return future;
1078 }
1079
1080 @Override
1081 public void deliverStatisticsReply(OFMessage reply) {
1082 OFStatisticsFuture future = this.statsFutureMap.get((int)reply.getXid());
1083 if (future != null) {
1084 future.deliverFuture(this, reply);
1085 // The future will ultimately unregister itself and call
1086 // cancelStatisticsReply
1087 return;
1088 }
1089 // Transaction id was not found in statsFutureMap.check the other map
1090 IOFMessageListener caller = this.iofMsgListenersMap.get(reply.getXid());
1091 if (caller != null) {
1092 caller.receive(this, reply, null);
1093 }
1094 }
1095
1096 @Override
1097 public void cancelStatisticsReply(int transactionId) {
1098 if (null == this.statsFutureMap.remove(transactionId)) {
1099 this.iofMsgListenersMap.remove(transactionId);
1100 }
1101 }
1102
1103 @Override
1104 public void cancelAllStatisticsReplies() {
1105 /* we don't need to be synchronized here. Even if another thread
1106 * modifies the map while we're cleaning up the future will eventuall
1107 * timeout */
1108 for (OFStatisticsFuture f : statsFutureMap.values()) {
1109 f.cancel(true);
1110 }
1111 statsFutureMap.clear();
1112 iofMsgListenersMap.clear();
1113 }
1114
1115
1116 //*******************************************
1117 // Switch role handling
1118 //*******************************************
1119
1120 // XXX S this is completely wrong. The generation id should be obtained
1121 // after coordinating with all controllers connected to this switch.
1122 // ie. should be part of registry service (account for 1.3 vs 1.0)
1123 // For now we are just generating this locally and keeping it constant.
1124 public U64 getNextGenerationId() {
1125 //TODO: Pankaj, fix nextGenerationId as part of Registry interface
1126 return U64.of(generationIdSource);
1127 }
1128
1129 @Override
1130 public Role getRole() {
1131 return role;
1132 }
1133
1134 @JsonIgnore
1135 @Override
1136 public void setRole(Role role) {
1137 this.role = role;
1138 }
1139
1140
1141 //*******************************************
1142 // Switch utility methods
1143 //*******************************************
1144
1145 private void registerOverloadCounters() throws CounterException {
1146 if (debugCountersRegistered) {
1147 return;
1148 }
1149 if (debugCounters == null) {
1150 log.error("Debug Counter Service not found");
1151 debugCounters = new NullDebugCounter();
1152 debugCountersRegistered = true;
1153 }
1154 // every level of the hierarchical counter has to be registered
1155 // even if they are not used
1156 ctrSwitch = debugCounters.registerCounter(
1157 BASE , stringId,
1158 "Counter for this switch",
1159 CounterType.ALWAYS_COUNT);
1160 ctrSwitchPktin = debugCounters.registerCounter(
1161 BASE, stringId + "/pktin",
1162 "Packet in counter for this switch",
1163 CounterType.ALWAYS_COUNT);
1164 ctrSwitchWrite = debugCounters.registerCounter(
1165 BASE, stringId + "/write",
1166 "Write counter for this switch",
1167 CounterType.ALWAYS_COUNT);
1168 ctrSwitchPktinDrops = debugCounters.registerCounter(
1169 BASE, stringId + "/pktin/drops",
1170 "Packet in throttle drop count",
1171 CounterType.ALWAYS_COUNT,
1172 IDebugCounterService.CTR_MDATA_WARN);
1173 ctrSwitchWriteDrops = debugCounters.registerCounter(
1174 BASE, stringId + "/write/drops",
1175 "Switch write throttle drop count",
1176 CounterType.ALWAYS_COUNT,
1177 IDebugCounterService.CTR_MDATA_WARN);
1178 }
1179
1180 @Override
1181 public void startDriverHandshake() throws IOException {
1182 log.debug("Starting driver handshake for sw {}", getStringId());
1183 if (startDriverHandshakeCalled)
1184 throw new SwitchDriverSubHandshakeAlreadyStarted();
1185 startDriverHandshakeCalled = true;
1186 }
1187
1188 @Override
1189 public boolean isDriverHandshakeComplete() {
1190 if (!startDriverHandshakeCalled)
1191 throw new SwitchDriverSubHandshakeNotStarted();
1192 return true;
1193 }
1194
1195 @Override
1196 public void processDriverHandshakeMessage(OFMessage m) {
1197 if (startDriverHandshakeCalled)
1198 throw new SwitchDriverSubHandshakeCompleted(m);
1199 else
1200 throw new SwitchDriverSubHandshakeNotStarted();
1201 }
1202
1203 @Override
1204 @JsonIgnore
1205 @LogMessageDoc(level="WARN",
1206 message="Switch {switch} flow table is full",
1207 explanation="The controller received flow table full " +
1208 "message from the switch, could be caused by increased " +
1209 "traffic pattern",
1210 recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
1211 public void setTableFull(boolean isFull) {
1212 if (isFull && !flowTableFull) {
1213 floodlightProvider.addSwitchEvent(this.datapathId.getLong(),
1214 "SWITCH_FLOW_TABLE_FULL " +
1215 "Table full error from switch", false);
1216 log.warn("Switch {} flow table is full", stringId);
1217 }
1218 flowTableFull = isFull;
1219 }
1220
1221 @Override
1222 @LogMessageDoc(level = "ERROR",
1223 message = "Failed to clear all flows on switch {switch}",
1224 explanation = "An I/O error occured while trying to clear " +
1225 "flows on the switch.",
1226 recommendation = LogMessageDoc.CHECK_SWITCH)
1227 public void clearAllFlowMods() {
1228 // Delete all pre-existing flows
1229
Jonathan Harta213bce2014-08-11 15:44:07 -07001230 // by default if match is not specified, then an empty list of matches
1231 // is sent, resulting in a wildcard-all flows
1232 // XXX fix this later to be sure it works for both 1.0 and 1.3
1233 OFMessage fm = getFactory()
Brian O'Connorc67f9fa2014-08-07 18:17:46 -07001234 .buildFlowDelete()
1235 .setOutPort(OFPort.ANY)
1236 .setOutGroup(OFGroup.ANY)
1237 .setTableId(TableId.ALL)
1238 .build();
1239
1240 try {
1241 channel.write(Collections.singletonList(fm));
1242 } catch (Exception e) {
1243 log.error("Failed to clear all flows on switch " + this, e);
1244 }
1245 }
1246
1247 @Override
1248 public void flush() {
1249 Map<OFSwitchImplBase, List<OFMessage>> msg_buffer_map = local_msg_buffer.get();
1250 List<OFMessage> msglist = msg_buffer_map.get(this);
1251 if ((msglist != null) && (msglist.size() > 0)) {
1252 try {
1253 this.write(msglist);
1254 } catch (IOException e) {
1255 log.error("Failed flushing messages", e);
1256 }
1257 msglist.clear();
1258 }
1259 }
1260
1261 public static void flush_all() {
1262 Map<OFSwitchImplBase, List<OFMessage>> msg_buffer_map = local_msg_buffer.get();
1263 for (OFSwitchImplBase sw : msg_buffer_map.keySet()) {
1264 sw.flush();
1265 }
1266 }
1267
1268 /**
1269 * Return a read lock that must be held while calling the listeners for
1270 * messages from the switch. Holding the read lock prevents the active
1271 * switch list from being modified out from under the listeners.
1272 *
1273 * @return listener read lock
1274 */
1275 @JsonIgnore
1276 public Lock getListenerReadLock() {
1277 return listenerLock.readLock();
1278 }
1279
1280 /**
1281 * Return a write lock that must be held when the controllers modifies the
1282 * list of active switches. This is to ensure that the active switch list
1283 * doesn't change out from under the listeners as they are handling a
1284 * message from the switch.
1285 *
1286 * @return listener write lock
1287 */
1288 @JsonIgnore
1289 public Lock getListenerWriteLock() {
1290 return listenerLock.writeLock();
1291 }
1292}