c71741923e26fb1acbaafe1f7aefbf919c2739bc
[onosfw.git] /
1 /*
2  * Copyright 2014-2015 Open Networking Laboratory
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
17 package org.onosproject.openflow.controller.driver;
18
19 import com.google.common.collect.Lists;
20 import org.jboss.netty.channel.Channel;
21 import org.onlab.packet.IpAddress;
22 import org.onosproject.net.Device;
23 import org.onosproject.net.driver.AbstractHandlerBehaviour;
24 import org.onosproject.openflow.controller.Dpid;
25 import org.onosproject.openflow.controller.RoleState;
26 import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
27 import org.projectfloodlight.openflow.protocol.OFErrorMsg;
28 import org.projectfloodlight.openflow.protocol.OFExperimenter;
29 import org.projectfloodlight.openflow.protocol.OFFactories;
30 import org.projectfloodlight.openflow.protocol.OFFactory;
31 import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
32 import org.projectfloodlight.openflow.protocol.OFMessage;
33 import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleRequest;
34 import org.projectfloodlight.openflow.protocol.OFPortDesc;
35 import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
36 import org.projectfloodlight.openflow.protocol.OFPortStatus;
37 import org.projectfloodlight.openflow.protocol.OFRoleReply;
38 import org.projectfloodlight.openflow.protocol.OFRoleRequest;
39 import org.projectfloodlight.openflow.protocol.OFVersion;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import java.io.IOException;
44 import java.net.InetSocketAddress;
45 import java.net.SocketAddress;
46 import java.util.ArrayList;
47 import java.util.Collections;
48 import java.util.List;
49 import java.util.concurrent.atomic.AtomicInteger;
50 import java.util.concurrent.atomic.AtomicReference;
51 import java.util.stream.Collectors;
52
53 /**
54  * An abstract representation of an OpenFlow switch. Can be extended by others
55  * to serve as a base for their vendor specific representation of a switch.
56  */
57 public abstract class AbstractOpenFlowSwitch extends AbstractHandlerBehaviour
58         implements OpenFlowSwitchDriver {
59
60     protected final Logger log = LoggerFactory.getLogger(getClass());
61
62     private Channel channel;
63     protected String channelId;
64
65     private boolean connected;
66     protected boolean startDriverHandshakeCalled = false;
67     private Dpid dpid;
68     private OpenFlowAgent agent;
69     private final AtomicInteger xidCounter = new AtomicInteger(0);
70
71     private OFVersion ofVersion;
72
73     protected List<OFPortDescStatsReply> ports = new ArrayList<>();
74
75     protected boolean tableFull;
76
77     private RoleHandler roleMan;
78
79     // TODO this is accessed from multiple threads, but volatile may have performance implications
80     protected volatile RoleState role;
81
82     protected OFFeaturesReply features;
83     protected OFDescStatsReply desc;
84
85     private final AtomicReference<List<OFMessage>> messagesPendingMastership
86             = new AtomicReference<>();
87
88     @Override
89     public void init(Dpid dpid, OFDescStatsReply desc, OFVersion ofv) {
90         this.dpid = dpid;
91         this.desc = desc;
92         this.ofVersion = ofv;
93     }
94
95     //************************
96     // Channel related
97     //************************
98
99     @Override
100     public final void disconnectSwitch() {
101         this.channel.close();
102     }
103
104     @Override
105     public void sendMsg(OFMessage msg) {
106         this.sendMsg(Collections.singletonList(msg));
107     }
108
109     @Override
110     public final void sendMsg(List<OFMessage> msgs) {
111         /*
112            It is possible that in this block, we transition to SLAVE/EQUAL.
113            If this is the case, the supplied messages will race with the
114            RoleRequest message, and they could be rejected by the switch.
115            In the interest of performance, we will not protect this block with
116            a synchronization primitive, because the message would have just been
117            dropped anyway.
118         */
119         if (role == RoleState.MASTER) {
120             // fast path send when we are master
121
122             sendMsgsOnChannel(msgs);
123             return;
124         }
125         // check to see if mastership transition is in progress
126         synchronized (messagesPendingMastership) {
127             /*
128                messagesPendingMastership is used as synchronization variable for
129                all mastership related changes. In this block, mastership (including
130                role update) will have either occurred or not.
131             */
132             if (role == RoleState.MASTER) {
133                 // transition to MASTER complete, send messages
134                 sendMsgsOnChannel(msgs);
135                 return;
136             }
137
138             List<OFMessage> messages = messagesPendingMastership.get();
139             if (messages != null) {
140                 // we are transitioning to MASTER, so add messages to queue
141                 messages.addAll(msgs);
142                 log.debug("Enqueue message for switch {}. queue size after is {}",
143                           dpid, messages.size());
144             } else {
145                 // not transitioning to MASTER
146                 log.warn("Dropping message for switch {} (role: {}, connected: {}): {}",
147                          dpid, role, channel.isConnected(), msgs);
148             }
149         }
150     }
151
152     private void sendMsgsOnChannel(List<OFMessage> msgs) {
153         if (channel.isConnected()) {
154             channel.write(msgs);
155         } else {
156             log.warn("Dropping messages for switch {} because channel is not connected: {}",
157                      dpid, msgs);
158         }
159     }
160
161     @Override
162     public final void sendRoleRequest(OFMessage msg) {
163         if (msg instanceof OFRoleRequest ||
164                 msg instanceof OFNiciraControllerRoleRequest) {
165             sendMsgsOnChannel(Collections.singletonList(msg));
166             return;
167         }
168         throw new IllegalArgumentException("Someone is trying to send " +
169                                                    "a non role request message");
170     }
171
172     @Override
173     public final void sendHandshakeMessage(OFMessage message) {
174         if (!this.isDriverHandshakeComplete()) {
175             sendMsgsOnChannel(Collections.singletonList(message));
176         }
177     }
178
179     @Override
180     public final boolean isConnected() {
181         return this.connected;
182     }
183
184     @Override
185     public final void setConnected(boolean connected) {
186         this.connected = connected;
187     }
188
189     @Override
190     public final void setChannel(Channel channel) {
191         this.channel = channel;
192         final SocketAddress address = channel.getRemoteAddress();
193         if (address instanceof InetSocketAddress) {
194             final InetSocketAddress inetAddress = (InetSocketAddress) address;
195             final IpAddress ipAddress = IpAddress.valueOf(inetAddress.getAddress());
196             if (ipAddress.isIp4()) {
197                 channelId = ipAddress.toString() + ':' + inetAddress.getPort();
198             } else {
199                 channelId = '[' + ipAddress.toString() + "]:" + inetAddress.getPort();
200             }
201         }
202     }
203
204     @Override
205     public String channelId() {
206         return channelId;
207     }
208
209     //************************
210     // Switch features related
211     //************************
212
213     @Override
214     public final long getId() {
215         return this.dpid.value();
216     }
217
218     @Override
219     public final String getStringId() {
220         return this.dpid.toString();
221     }
222
223     @Override
224     public final void setOFVersion(OFVersion ofV) {
225         this.ofVersion = ofV;
226     }
227
228     @Override
229     public void setTableFull(boolean full) {
230         this.tableFull = full;
231     }
232
233     @Override
234     public void setFeaturesReply(OFFeaturesReply featuresReply) {
235         this.features = featuresReply;
236     }
237
238     @Override
239     public abstract Boolean supportNxRole();
240
241     //************************
242     //  Message handling
243     //************************
244     /**
245      * Handle the message coming from the dataplane.
246      *
247      * @param m the actual message
248      */
249     @Override
250     public final void handleMessage(OFMessage m) {
251         if (this.role == RoleState.MASTER || m instanceof OFPortStatus) {
252             this.agent.processMessage(dpid, m);
253         }
254     }
255
256     @Override
257     public RoleState getRole() {
258         return role;
259     }
260
261     @Override
262     public final boolean connectSwitch() {
263         return this.agent.addConnectedSwitch(dpid, this);
264     }
265
266     @Override
267     public final boolean activateMasterSwitch() {
268         return this.agent.addActivatedMasterSwitch(dpid, this);
269     }
270
271     @Override
272     public final boolean activateEqualSwitch() {
273         return this.agent.addActivatedEqualSwitch(dpid, this);
274     }
275
276     @Override
277     public final void transitionToEqualSwitch() {
278         this.agent.transitionToEqualSwitch(dpid);
279     }
280
281     @Override
282     public final void transitionToMasterSwitch() {
283         this.agent.transitionToMasterSwitch(dpid);
284         synchronized (messagesPendingMastership) {
285             List<OFMessage> messages = messagesPendingMastership.get();
286             if (messages != null) {
287                 this.sendMsg(messages);
288                 log.debug("Sending {} pending messages to switch {}",
289                           messages.size(), dpid);
290                 messagesPendingMastership.set(null);
291             }
292             // perform role transition after clearing messages queue
293             this.role = RoleState.MASTER;
294         }
295     }
296
297     @Override
298     public final void removeConnectedSwitch() {
299         this.agent.removeConnectedSwitch(dpid);
300     }
301
302     @Override
303     public OFFactory factory() {
304         return OFFactories.getFactory(ofVersion);
305     }
306
307     @Override
308     public void setPortDescReply(OFPortDescStatsReply portDescReply) {
309         this.ports.add(portDescReply);
310     }
311
312     @Override
313     public void setPortDescReplies(List<OFPortDescStatsReply> portDescReplies) {
314         this.ports.addAll(portDescReplies);
315     }
316
317     @Override
318     public void returnRoleReply(RoleState requested, RoleState response) {
319         this.agent.returnRoleReply(dpid, requested, response);
320     }
321
322     @Override
323     public abstract void startDriverHandshake();
324
325     @Override
326     public abstract boolean isDriverHandshakeComplete();
327
328     @Override
329     public abstract void processDriverHandshakeMessage(OFMessage m);
330
331
332     // Role Handling
333
334     @Override
335     public void setRole(RoleState role) {
336         try {
337             if (role == RoleState.SLAVE || role == RoleState.EQUAL) {
338                 // perform role transition to SLAVE/EQUAL before sending role request
339                 this.role = role;
340             }
341             if (this.roleMan.sendRoleRequest(role, RoleRecvStatus.MATCHED_SET_ROLE)) {
342                 log.debug("Sending role {} to switch {}", role, getStringId());
343                 if (role == RoleState.MASTER) {
344                     synchronized (messagesPendingMastership) {
345                         if (messagesPendingMastership.get() == null) {
346                             log.debug("Initializing new message queue for switch {}", dpid);
347                             /*
348                                The presence of messagesPendingMastership indicates that
349                                a switch is currently transitioning to MASTER, but
350                                is still awaiting role reply from switch.
351                             */
352                             messagesPendingMastership.set(Lists.newArrayList());
353                         }
354                     }
355                 }
356             } else if (role == RoleState.MASTER) {
357                 // role request not support; transition switch to MASTER
358                 this.role = role;
359             }
360         } catch (IOException e) {
361             log.error("Unable to write to switch {}.", this.dpid);
362         }
363     }
364
365     @Override
366     public void reassertRole() {
367         // TODO should messages be sent directly or queue during reassertion?
368         if (this.getRole() == RoleState.MASTER) {
369             log.warn("Received permission error from switch {} while " +
370                     "being master. Reasserting master role.",
371                     this.getStringId());
372             this.setRole(RoleState.MASTER);
373         }
374     }
375
376     @Override
377     public void handleRole(OFMessage m) throws SwitchStateException {
378         RoleReplyInfo rri = roleMan.extractOFRoleReply((OFRoleReply) m);
379         RoleRecvStatus rrs = roleMan.deliverRoleReply(rri);
380         if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
381             if (rri.getRole() == RoleState.MASTER) {
382                 this.transitionToMasterSwitch();
383             } else if (rri.getRole() == RoleState.EQUAL ||
384                        rri.getRole() == RoleState.SLAVE) {
385                 this.transitionToEqualSwitch();
386             }
387         }  else {
388             log.warn("Failed to set role for {}", this.getStringId());
389         }
390     }
391
392     @Override
393     public void handleNiciraRole(OFMessage m) throws SwitchStateException {
394         RoleState r = this.roleMan.extractNiciraRoleReply((OFExperimenter) m);
395         if (r == null) {
396             // The message wasn't really a Nicira role reply. We just
397             // dispatch it to the OFMessage listeners in this case.
398             this.handleMessage(m);
399             return;
400         }
401
402         RoleRecvStatus rrs = this.roleMan.deliverRoleReply(
403                 new RoleReplyInfo(r, null, m.getXid()));
404         if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) {
405             if (r == RoleState.MASTER) {
406                 this.transitionToMasterSwitch();
407             } else if (r == RoleState.EQUAL ||
408                        r == RoleState.SLAVE) {
409                 this.transitionToEqualSwitch();
410             }
411         } else {
412             this.disconnectSwitch();
413         }
414     }
415
416     @Override
417     public boolean handleRoleError(OFErrorMsg error) {
418         try {
419             return RoleRecvStatus.OTHER_EXPECTATION != this.roleMan.deliverError(error);
420         } catch (SwitchStateException e) {
421             this.disconnectSwitch();
422         }
423         return true;
424     }
425
426     @Override
427     public final void setAgent(OpenFlowAgent ag) {
428         if (this.agent == null) {
429             this.agent = ag;
430         }
431     }
432
433     @Override
434     public final void setRoleHandler(RoleHandler roleHandler) {
435         if (this.roleMan == null) {
436             this.roleMan = roleHandler;
437         }
438     }
439
440     @Override
441     public void setSwitchDescription(OFDescStatsReply d) {
442         this.desc = d;
443     }
444
445     @Override
446     public int getNextTransactionId() {
447         return this.xidCounter.getAndIncrement();
448     }
449
450     @Override
451     public List<OFPortDesc> getPorts() {
452         return this.ports.stream()
453                   .flatMap(portReply -> portReply.getEntries().stream())
454                   .collect(Collectors.toList());
455     }
456
457     @Override
458     public String manufacturerDescription() {
459         return this.desc.getMfrDesc();
460     }
461
462     @Override
463     public String datapathDescription() {
464         return this.desc.getDpDesc();
465     }
466
467     @Override
468     public String hardwareDescription() {
469         return this.desc.getHwDesc();
470     }
471
472     @Override
473     public String softwareDescription() {
474         return this.desc.getSwDesc();
475     }
476
477     @Override
478     public String serialNumber() {
479         return this.desc.getSerialNum();
480     }
481
482     @Override
483     public Device.Type deviceType() {
484         return Device.Type.SWITCH;
485     }
486
487     @Override
488     public String toString() {
489         return this.getClass().getName() + " [" + ((channel != null)
490                 ? channel.getRemoteAddress() : "?")
491                 + " DPID[" + ((getStringId() != null) ? getStringId() : "?") + "]]";
492     }
493 }