OPNFLWPLUG-929 : Remove deprecated guava library
[openflowplugin.git] / openflowplugin-impl / src / main / java / org / opendaylight / openflowplugin / impl / connection / HandshakeManagerImpl.java
1 /**
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.openflowplugin.impl.connection;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.util.concurrent.FutureCallback;
12 import com.google.common.util.concurrent.Futures;
13 import com.google.common.util.concurrent.JdkFutureAdapters;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import com.google.common.util.concurrent.MoreExecutors;
16 import com.google.common.util.concurrent.SettableFuture;
17 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
18 import java.util.List;
19 import java.util.Objects;
20 import java.util.concurrent.Future;
21 import org.opendaylight.openflowjava.protocol.api.connection.ConnectionAdapter;
22 import org.opendaylight.openflowplugin.api.OFConstants;
23 import org.opendaylight.openflowplugin.api.openflow.md.core.ErrorHandler;
24 import org.opendaylight.openflowplugin.api.openflow.md.core.HandshakeListener;
25 import org.opendaylight.openflowplugin.api.openflow.md.core.HandshakeManager;
26 import org.opendaylight.openflowplugin.impl.util.MessageFactory;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesInputBuilder;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.GetFeaturesOutput;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.HelloInput;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.HelloMessage;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.hello.Elements;
32 import org.opendaylight.yangtools.yang.common.RpcError;
33 import org.opendaylight.yangtools.yang.common.RpcResult;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 public class HandshakeManagerImpl implements HandshakeManager {
38
39     private static final long ACTIVE_XID = 20L;
40
41     private static final Logger LOG = LoggerFactory.getLogger(HandshakeManagerImpl.class);
42
43     private Short lastProposedVersion;
44     private Short lastReceivedVersion;
45     private final List<Short> versionOrder;
46
47     private final ConnectionAdapter connectionAdapter;
48     private Short version;
49     private final ErrorHandler errorHandler;
50
51     private final Short highestVersion;
52
53     private Long activeXid;
54
55     private final HandshakeListener handshakeListener;
56
57     private boolean useVersionBitmap; // not final just for unit test
58
59     /**
60      * Constructor.
61      *
62      * @param connectionAdapter connection adaptor for switch
63      * @param highestVersion    highest openflow version
64      * @param versionOrder      list of version in order for connection protocol negotiation
65      * @param errorHandler      the ErrorHandler
66      * @param handshakeListener the HandshakeListener
67      * @param useVersionBitmap  should use negotiation bit map
68      */
69     public HandshakeManagerImpl(ConnectionAdapter connectionAdapter, Short highestVersion, List<Short> versionOrder,
70             ErrorHandler errorHandler, HandshakeListener handshakeListener, boolean useVersionBitmap) {
71         this.highestVersion = highestVersion;
72         this.versionOrder = versionOrder;
73         this.connectionAdapter = connectionAdapter;
74         this.errorHandler = errorHandler;
75         this.handshakeListener = handshakeListener;
76         this.useVersionBitmap = useVersionBitmap;
77     }
78
79     @Override
80     @SuppressWarnings("checkstyle:IllegalCatch")
81     public synchronized void shake(HelloMessage receivedHello) {
82
83         if (version != null) {
84             // Some switches respond with a second HELLO acknowledging our HELLO
85             // but we've already completed the handshake based on the negotiated
86             // version and have registered this switch.
87             LOG.debug("Hello recieved after handshake already settled ... ignoring.");
88             return;
89         }
90
91         LOG.trace("handshake STARTED");
92         setActiveXid(ACTIVE_XID);
93
94         try {
95             if (receivedHello == null) {
96                 // first Hello sending
97                 sendHelloMessage(highestVersion, getNextXid());
98                 lastProposedVersion = highestVersion;
99                 LOG.trace("ret - firstHello+wait");
100                 return;
101             }
102
103             // process the 2. and later hellos
104             Short remoteVersion = receivedHello.getVersion();
105             List<Elements> elements = receivedHello.getElements();
106             setActiveXid(receivedHello.getXid());
107             List<Boolean> remoteVersionBitmap = MessageFactory.digVersions(elements);
108             LOG.debug("Hello message: version={}, xid={}, bitmap={}", remoteVersion, receivedHello.getXid(),
109                       remoteVersionBitmap);
110
111             if (useVersionBitmap && remoteVersionBitmap != null) {
112                 // versionBitmap on both sides -> ONE STEP DECISION
113                 handleVersionBitmapNegotiation(elements);
114             } else {
115                 // versionBitmap missing at least on one side -> STEP-BY-STEP NEGOTIATION applying
116                 handleStepByStepVersionNegotiation(remoteVersion);
117             }
118         } catch (Exception ex) {
119             errorHandler.handleException(ex);
120             LOG.trace("ret - shake fail - closing");
121             handshakeListener.onHandshakeFailure();
122         }
123     }
124
125     /**
126      * Handles the version negotiation step by step.
127      *
128      * @param remoteVersion remote version
129      * @throws Exception exception
130      */
131     @SuppressWarnings("checkstyle:IllegalCatch")
132     private void handleStepByStepVersionNegotiation(final Short remoteVersion) throws Exception {
133         LOG.debug("remoteVersion:{} lastProposedVersion:{}, highestVersion:{}", remoteVersion, lastProposedVersion,
134                   highestVersion);
135
136         if (lastProposedVersion == null) {
137             // first hello has not been sent yet, send it and either wait for next remote
138             // version or proceed
139             lastProposedVersion = proposeNextVersion(remoteVersion);
140             final Long nextHelloXid = getNextXid();
141             ListenableFuture<Void> helloResult = sendHelloMessage(lastProposedVersion, nextHelloXid);
142             Futures.addCallback(helloResult, new FutureCallback<Void>() {
143                 @Override
144                 public void onSuccess(Void result) {
145                     try {
146                         stepByStepVersionSubStep(remoteVersion, lastProposedVersion);
147                     } catch (Exception e) {
148                         errorHandler.handleException(e);
149                         handshakeListener.onHandshakeFailure();
150                     }
151                 }
152
153                 @Override
154                 public void onFailure(Throwable throwable) {
155                     LOG.info("hello sending seriously failed [{}]", nextHelloXid);
156                     LOG.trace("detail of hello send problem", throwable);
157                 }
158             }, MoreExecutors.directExecutor());
159         } else {
160             stepByStepVersionSubStep(remoteVersion, lastProposedVersion);
161         }
162     }
163
164     private void stepByStepVersionSubStep(Short remoteVersion, Short lastProposedVersion) throws Exception {
165         if (remoteVersion.equals(lastProposedVersion)) {
166             postHandshake(lastProposedVersion, getNextXid());
167             LOG.trace("ret - OK - switch answered with lastProposedVersion");
168         } else {
169             checkNegotiationStalling(remoteVersion);
170
171             if (remoteVersion > (lastProposedVersion == null ? highestVersion : this.lastProposedVersion)) {
172                 // wait for next version
173                 LOG.trace("ret - wait");
174             } else {
175                 //propose lower version
176                 handleLowerVersionProposal(remoteVersion);
177             }
178         }
179     }
180
181     /**
182      * Handles a proposal for a lower version.
183      *
184      * @param remoteVersion remote version
185      * @throws Exception exception
186      */
187     private void handleLowerVersionProposal(Short remoteVersion) throws Exception {
188         Short proposedVersion;
189         // find the version from header version field
190         proposedVersion = proposeNextVersion(remoteVersion);
191         lastProposedVersion = proposedVersion;
192         sendHelloMessage(proposedVersion, getNextXid());
193
194         if (!Objects.equals(proposedVersion, remoteVersion)) {
195             LOG.trace("ret - sent+wait");
196         } else {
197             LOG.trace("ret - sent+OK");
198             postHandshake(proposedVersion, getNextXid());
199         }
200     }
201
202     /**
203      * Handles the negotiation of the version bitmap.
204      *
205      * @param elements version elements
206      * @throws Exception exception
207      */
208     private void handleVersionBitmapNegotiation(List<Elements> elements) throws Exception {
209         final Short proposedVersion = proposeCommonBitmapVersion(elements);
210         if (lastProposedVersion == null) {
211             // first hello has not been sent yet
212             Long nexHelloXid = getNextXid();
213             ListenableFuture<Void> helloDone = sendHelloMessage(proposedVersion, nexHelloXid);
214             Futures.addCallback(helloDone, new FutureCallback<Void>() {
215                 @Override
216                 public void onSuccess(Void result) {
217                     LOG.trace("ret - DONE - versionBitmap");
218                     postHandshake(proposedVersion, getNextXid());
219                 }
220
221                 @Override
222                 public void onFailure(Throwable throwable) {
223                     // NOOP
224                 }
225             }, MoreExecutors.directExecutor());
226             LOG.trace("next proposal [{}] with versionBitmap hooked ..", nexHelloXid);
227         } else {
228             LOG.trace("ret - DONE - versionBitmap");
229             postHandshake(proposedVersion, getNextXid());
230         }
231     }
232
233     private Long getNextXid() {
234         activeXid += 1;
235         return activeXid;
236     }
237
238     private void setActiveXid(Long xid) {
239         this.activeXid = xid;
240     }
241
242     /**
243      * Checks negotiation stalling.
244      *
245      * @param remoteVersion remove version
246      */
247     private void checkNegotiationStalling(Short remoteVersion) {
248         if (lastReceivedVersion != null && lastReceivedVersion.equals(remoteVersion)) {
249             throw new IllegalStateException("version negotiation stalled: version = " + remoteVersion);
250         }
251         lastReceivedVersion = remoteVersion;
252     }
253
254     @Override
255     public Short getVersion() {
256         return version;
257     }
258
259     /**
260      * find common highest supported bitmap version.
261      *
262      * @param list bitmap list
263      * @return proposed bitmap value
264      */
265     protected Short proposeCommonBitmapVersion(List<Elements> list) {
266         Short supportedHighestVersion = null;
267         if (null != list && 0 != list.size()) {
268             for (Elements element : list) {
269                 List<Boolean> bitmap = element.getVersionBitmap();
270                 // check for version bitmap
271                 for (short bitPos : OFConstants.VERSION_ORDER) {
272                     // with all the version it should work.
273                     if (bitmap.get(bitPos % Integer.SIZE)) {
274                         supportedHighestVersion = bitPos;
275                         break;
276                     }
277                 }
278             }
279
280             if (null == supportedHighestVersion) {
281                 LOG.trace("versionBitmap: no common version found");
282                 throw new IllegalArgumentException("no common version found in versionBitmap");
283             }
284         }
285
286         return supportedHighestVersion;
287     }
288
289     /**
290      * find supported version based on remoteVersion.
291      *
292      * @param remoteVersion openflow version supported by remote entity
293      * @return openflow version
294      */
295     protected short proposeNextVersion(short remoteVersion) {
296         Short proposal = null;
297         for (short offer : versionOrder) {
298             if (offer <= remoteVersion) {
299                 proposal = offer;
300                 break;
301             }
302         }
303         if (proposal == null) {
304             throw new IllegalArgumentException(
305                     "no equal or lower version found, unsupported version: " + remoteVersion);
306         }
307         return proposal;
308     }
309
310     /**
311      * send hello reply without versionBitmap.
312      *
313      * @param helloVersion initial hello version for openflow connection negotiation
314      * @param helloXid     transaction id
315      */
316     private ListenableFuture<Void> sendHelloMessage(Short helloVersion, final Long helloXid) throws Exception {
317
318
319         HelloInput helloInput = MessageFactory.createHelloInput(helloVersion, helloXid, versionOrder);
320
321         final SettableFuture<Void> resultFtr = SettableFuture.create();
322
323         LOG.debug("sending hello message: version{}, xid={}, version bitmap={}", helloVersion, helloXid,
324                   MessageFactory.digVersions(helloInput.getElements()));
325
326         Future<RpcResult<Void>> helloResult = connectionAdapter.hello(helloInput);
327
328         ListenableFuture<RpcResult<Void>> rpcResultListenableFuture = JdkFutureAdapters.listenInPoolThread(helloResult);
329         Futures.addCallback(rpcResultListenableFuture, new FutureCallback<RpcResult<Void>>() {
330             @Override
331             public void onSuccess(RpcResult<Void> result) {
332                 if (result.isSuccessful()) {
333                     LOG.debug("hello successfully sent, xid={}, addr={}", helloXid,
334                               connectionAdapter.getRemoteAddress());
335                     resultFtr.set(null);
336                 } else {
337                     for (RpcError error : result.getErrors()) {
338                         LOG.debug("hello sending failed [{}]: i:{} s:{} m:{}, addr:{}", helloXid, error.getInfo(),
339                                   error.getSeverity(), error.getMessage(), connectionAdapter.getRemoteAddress());
340                         if (error.getCause() != null) {
341                             LOG.trace("DETAIL of sending hello failure", error.getCause());
342                         }
343                     }
344                     resultFtr.cancel(false);
345                     handshakeListener.onHandshakeFailure();
346                 }
347             }
348
349             @Override
350             public void onFailure(Throwable throwable) {
351                 LOG.warn("sending of hello failed seriously [{}, addr:{}]: {}", helloXid,
352                          connectionAdapter.getRemoteAddress(), throwable.getMessage());
353                 LOG.trace("DETAIL of sending of hello failure:", throwable);
354                 resultFtr.cancel(false);
355                 handshakeListener.onHandshakeFailure();
356             }
357         }, MoreExecutors.directExecutor());
358         LOG.trace("sending hello message [{}] - result hooked ..", helloXid);
359         return resultFtr;
360     }
361
362
363     /**
364      * after handshake set features, register to session.
365      *
366      * @param proposedVersion proposed openflow version
367      * @param xid             transaction id
368      */
369     protected void postHandshake(final Short proposedVersion, final Long xid) {
370         // set version
371         version = proposedVersion;
372
373         LOG.debug("version set: {}", proposedVersion);
374         // request features
375         GetFeaturesInputBuilder featuresBuilder = new GetFeaturesInputBuilder();
376         featuresBuilder.setVersion(version).setXid(xid);
377         LOG.debug("sending feature request for version={} and xid={}", version, xid);
378         Future<RpcResult<GetFeaturesOutput>> featuresFuture = connectionAdapter.getFeatures(featuresBuilder.build());
379
380         Futures.addCallback(JdkFutureAdapters.listenInPoolThread(featuresFuture),
381                 new FutureCallback<RpcResult<GetFeaturesOutput>>() {
382                     @Override
383                     public void onSuccess(RpcResult<GetFeaturesOutput> rpcFeatures) {
384                         LOG.trace("features are back");
385                         if (rpcFeatures.isSuccessful()) {
386                             GetFeaturesOutput featureOutput = rpcFeatures.getResult();
387
388                             LOG.debug("obtained features: datapathId={}", featureOutput.getDatapathId());
389                             LOG.debug("obtained features: auxiliaryId={}", featureOutput.getAuxiliaryId());
390                             LOG.trace("handshake SETTLED: version={}, datapathId={}, auxiliaryId={}",
391                                       version, featureOutput.getDatapathId(),
392                                       featureOutput.getAuxiliaryId());
393                             handshakeListener.onHandshakeSuccessful(featureOutput, proposedVersion);
394                         } else {
395                             // handshake failed
396                             LOG.warn("issuing disconnect during handshake [{}]",
397                                      connectionAdapter.getRemoteAddress());
398                             for (RpcError rpcError : rpcFeatures.getErrors()) {
399                                 LOG.debug("handshake - features failure [{}]: i:{} | m:{} | s:{}", xid,
400                                           rpcError.getInfo(), rpcError.getMessage(), rpcError.getSeverity(),
401                                           rpcError.getCause());
402                             }
403                             handshakeListener.onHandshakeFailure();
404                         }
405
406                         LOG.debug("postHandshake DONE");
407                     }
408
409                     @Override
410                     public void onFailure(Throwable throwable) {
411                         LOG.warn("getting feature failed seriously [{}, addr:{}]: {}", xid,
412                                  connectionAdapter.getRemoteAddress(), throwable.getMessage());
413                         LOG.trace("DETAIL of sending of hello failure:", throwable);
414                     }
415                 }, MoreExecutors.directExecutor());
416         LOG.debug("future features [{}] hooked ..", xid);
417     }
418
419     /**
420      * Method for unit testing, only.
421      * This method is not thread safe and can only safely be used from a test.
422      */
423     @VisibleForTesting
424     @SuppressFBWarnings("IS2_INCONSISTENT_SYNC") // because shake() is synchronized
425     void setUseVersionBitmap(boolean useVersionBitmap) {
426         this.useVersionBitmap = useVersionBitmap;
427     }
428
429 }