bd4875cf1e96ecf9c498c25a6744bb578ab0049f
[onosfw.git] /
1 /*
2  * Copyright 2014 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 package org.onosproject.openflow.controller.impl;
17
18 import com.google.common.cache.Cache;
19 import com.google.common.cache.CacheBuilder;
20 import org.onosproject.openflow.controller.RoleState;
21 import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
22 import org.onosproject.openflow.controller.driver.RoleHandler;
23 import org.onosproject.openflow.controller.driver.RoleRecvStatus;
24 import org.onosproject.openflow.controller.driver.RoleReplyInfo;
25 import org.onosproject.openflow.controller.driver.SwitchStateException;
26 import org.projectfloodlight.openflow.protocol.OFControllerRole;
27 import org.projectfloodlight.openflow.protocol.OFErrorMsg;
28 import org.projectfloodlight.openflow.protocol.OFErrorType;
29 import org.projectfloodlight.openflow.protocol.OFExperimenter;
30 import org.projectfloodlight.openflow.protocol.OFFactories;
31 import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole;
32 import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply;
33 import org.projectfloodlight.openflow.protocol.OFRoleReply;
34 import org.projectfloodlight.openflow.protocol.OFRoleRequest;
35 import org.projectfloodlight.openflow.protocol.OFVersion;
36 import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
37 import org.projectfloodlight.openflow.protocol.errormsg.OFRoleRequestFailedErrorMsg;
38 import org.projectfloodlight.openflow.types.U64;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import java.io.IOException;
43 import java.util.concurrent.TimeUnit;
44
45
46 /**
47  * A utility class to handle role requests and replies for this channel.
48  * After a role request is submitted the role changer keeps track of the
49  * pending request, collects the reply (if any) and times out the request
50  * if necessary.
51  */
52 class RoleManager implements RoleHandler {
53     protected static final long NICIRA_EXPERIMENTER = 0x2320;
54
55     private static Logger log = LoggerFactory.getLogger(RoleManager.class);
56
57     // The time until cached XID is evicted. Arbitrary for now.
58     private final int pendingXidTimeoutSeconds = 60;
59
60     // The cache for pending expected RoleReplies keyed on expected XID
61     private Cache<Integer, RoleState> pendingReplies =
62             CacheBuilder.newBuilder()
63                 .expireAfterWrite(pendingXidTimeoutSeconds, TimeUnit.SECONDS)
64                 .build();
65
66     // the expectation set by the caller for the returned role
67     private RoleRecvStatus expectation;
68     private final OpenFlowSwitchDriver sw;
69
70
71     public RoleManager(OpenFlowSwitchDriver sw) {
72         this.expectation = RoleRecvStatus.MATCHED_CURRENT_ROLE;
73         this.sw = sw;
74     }
75
76     /**
77      * Send NX role request message to the switch requesting the specified
78      * role.
79      *
80      * @param role role to request
81      */
82     private int sendNxRoleRequest(RoleState role) throws IOException {
83         // Convert the role enum to the appropriate role to send
84         OFNiciraControllerRole roleToSend = OFNiciraControllerRole.ROLE_OTHER;
85         switch (role) {
86         case MASTER:
87             roleToSend = OFNiciraControllerRole.ROLE_MASTER;
88             break;
89         case SLAVE:
90         case EQUAL:
91         default:
92             // ensuring that the only two roles sent to 1.0 switches with
93             // Nicira role support, are MASTER and SLAVE
94             roleToSend = OFNiciraControllerRole.ROLE_OTHER;
95             log.debug("Sending Nx Role.SLAVE to switch {}.", sw);
96         }
97         int xid = sw.getNextTransactionId();
98         OFExperimenter roleRequest = OFFactories.getFactory(OFVersion.OF_10)
99                 .buildNiciraControllerRoleRequest()
100                 .setXid(xid)
101                 .setRole(roleToSend)
102                 .build();
103         sw.sendRoleRequest(roleRequest);
104         return xid;
105     }
106
107     private int sendOF13RoleRequest(RoleState role) throws IOException {
108         // Convert the role enum to the appropriate role to send
109         OFControllerRole roleToSend = OFControllerRole.ROLE_NOCHANGE;
110         switch (role) {
111         case EQUAL:
112             roleToSend = OFControllerRole.ROLE_EQUAL;
113             break;
114         case MASTER:
115             roleToSend = OFControllerRole.ROLE_MASTER;
116             break;
117         case SLAVE:
118             roleToSend = OFControllerRole.ROLE_SLAVE;
119             break;
120         default:
121             log.warn("Sending default role.noChange to switch {}."
122                     + " Should only be used for queries.", sw);
123         }
124
125         int xid = sw.getNextTransactionId();
126         OFRoleRequest rrm = OFFactories.getFactory(OFVersion.OF_13)
127                 .buildRoleRequest()
128                 .setRole(roleToSend)
129                 .setXid(xid)
130                 //FIXME fix below when we actually use generation ids
131                 .setGenerationId(U64.ZERO)
132                 .build();
133
134         sw.sendRoleRequest(rrm);
135         return xid;
136     }
137
138     @Override
139     public synchronized boolean sendRoleRequest(RoleState role, RoleRecvStatus exp)
140             throws IOException {
141         this.expectation = exp;
142
143         if (sw.factory().getVersion() == OFVersion.OF_10) {
144             Boolean supportsNxRole = sw.supportNxRole();
145             if (!supportsNxRole) {
146                 log.debug("Switch driver indicates no support for Nicira "
147                         + "role request messages. Not sending ...");
148                 handleUnsentRoleMessage(role,
149                         expectation);
150                 return false;
151             }
152             // OF1.0 switch with support for NX_ROLE_REQUEST vendor extn.
153             // make Role.EQUAL become Role.SLAVE
154             RoleState roleToSend = (role == RoleState.EQUAL) ? RoleState.SLAVE : role;
155             pendingReplies.put(sendNxRoleRequest(roleToSend), role);
156         } else {
157             // OF1.3 switch, use OFPT_ROLE_REQUEST message
158             pendingReplies.put(sendOF13RoleRequest(role), role);
159         }
160         return true;
161     }
162
163     private void handleUnsentRoleMessage(RoleState role,
164             RoleRecvStatus exp) throws IOException {
165         // typically this is triggered for a switch where role messages
166         // are not supported - we confirm that the role being set is
167         // master
168         if (exp != RoleRecvStatus.MATCHED_SET_ROLE) {
169
170             log.error("Expected MASTER role from registry for switch "
171                     + "which has no support for role-messages."
172                     + "Received {}. It is possible that this switch "
173                     + "is connected to other controllers, in which "
174                     + "case it should support role messages - not "
175                     + "moving forward.", role);
176
177         }
178
179     }
180
181
182     @Override
183     public synchronized RoleRecvStatus deliverRoleReply(RoleReplyInfo rri)
184             throws SwitchStateException {
185         int xid = (int) rri.getXid();
186         RoleState receivedRole = rri.getRole();
187         RoleState expectedRole = pendingReplies.getIfPresent(xid);
188
189         if (expectedRole == null) {
190             RoleState currentRole = (sw != null) ? sw.getRole() : null;
191             if (currentRole != null) {
192                 if (currentRole == rri.getRole()) {
193                     // Don't disconnect if the role reply we received is
194                     // for the same role we are already in.
195                     // FIXME: but we do from the caller anyways.
196                     log.debug("Received unexpected RoleReply from "
197                             + "Switch: {}. "
198                             + "Role in reply is same as current role of this "
199                             + "controller for this sw. Ignoring ...",
200                             sw.getStringId());
201                     return RoleRecvStatus.OTHER_EXPECTATION;
202                 } else {
203                     String msg = String.format("Switch: [%s], "
204                             + "received unexpected RoleReply[%s]. "
205                             + "No roles are pending, and this controller's "
206                             + "current role:[%s] does not match reply. "
207                             + "Disconnecting switch ... ",
208                             sw.getStringId(),
209                             rri, currentRole);
210                     throw new SwitchStateException(msg);
211                 }
212             }
213             log.debug("Received unexpected RoleReply {} from "
214                     + "Switch: {}. "
215                     + "This controller has no current role for this sw. "
216                     + "Ignoring ...",
217                       rri,
218                       sw == null ? "(null)" : sw.getStringId());
219             return RoleRecvStatus.OTHER_EXPECTATION;
220         }
221
222         // XXX Should check generation id meaningfully and other cases of expectations
223         //if (pendingXid != xid) {
224         //    log.info("Received older role reply from " +
225         //            "switch {} ({}). Ignoring. " +
226         //            "Waiting for {}, xid={}",
227         //            new Object[] {sw.getStringId(), rri,
228         //            pendingRole, pendingXid });
229         //    return RoleRecvStatus.OLD_REPLY;
230         //}
231         sw.returnRoleReply(expectedRole, receivedRole);
232
233         if (expectedRole == receivedRole) {
234             log.debug("Received role reply message from {} that matched "
235                     + "expected role-reply {} with expectations {}",
236                     sw.getStringId(), receivedRole, expectation);
237
238             // Done with this RoleReply; Invalidate
239             pendingReplies.invalidate(xid);
240             if (expectation == RoleRecvStatus.MATCHED_CURRENT_ROLE ||
241                     expectation == RoleRecvStatus.MATCHED_SET_ROLE) {
242                 return expectation;
243             } else {
244                 return RoleRecvStatus.OTHER_EXPECTATION;
245             }
246         }
247
248         pendingReplies.invalidate(xid);
249         // if xids match but role's don't, perhaps its a query (OF1.3)
250         if (expectation == RoleRecvStatus.REPLY_QUERY) {
251             return expectation;
252         }
253
254         return RoleRecvStatus.OTHER_EXPECTATION;
255     }
256
257     /**
258      * Called if we receive an  error message. If the xid matches the
259      * pending request we handle it otherwise we ignore it.
260      *
261      * Note: since we only keep the last pending request we might get
262      * error messages for earlier role requests that we won't be able
263      * to handle
264      */
265     @Override
266     public synchronized RoleRecvStatus deliverError(OFErrorMsg error)
267             throws SwitchStateException {
268         RoleState errorRole = pendingReplies.getIfPresent(error.getXid());
269         if (errorRole == null) {
270             if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
271                 log.debug("Received an error msg from sw {} for a role request,"
272                         + " but not for pending request in role-changer; "
273                         + " ignoring error {} ...",
274                         sw.getStringId(), error);
275             } else {
276                 log.debug("Received an error msg from sw {}, but no pending "
277                         + "requests in role-changer; not handling ...",
278                         sw.getStringId());
279             }
280             return RoleRecvStatus.OTHER_EXPECTATION;
281         }
282         // it is an error related to a currently pending role request message
283         if (error.getErrType() == OFErrorType.BAD_REQUEST) {
284             log.error("Received a error msg {} from sw {} for "
285                     + "pending role request {}. Switch driver indicates "
286                     + "role-messaging is supported. Possible issues in "
287                     + "switch driver configuration?",
288                     ((OFBadRequestErrorMsg) error).toString(),
289                     sw.getStringId(),
290                     errorRole);
291             return RoleRecvStatus.UNSUPPORTED;
292         }
293
294         if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
295             OFRoleRequestFailedErrorMsg rrerr =
296                     (OFRoleRequestFailedErrorMsg) error;
297             switch (rrerr.getCode()) {
298             case BAD_ROLE:
299                 // switch says that current-role-req has bad role?
300                 // for now we disconnect
301                 // fall-thru
302             case STALE:
303                 // switch says that current-role-req has stale gen-id?
304                 // for now we disconnect
305                 // fall-thru
306             case UNSUP:
307                 // switch says that current-role-req has role that
308                 // cannot be supported? for now we disconnect
309                 String msgx = String.format("Switch: [%s], "
310                         + "received Error to for pending role request [%s]. "
311                         + "Error:[%s]. Disconnecting switch ... ",
312                         sw.getStringId(),
313                         errorRole, rrerr);
314                 throw new SwitchStateException(msgx);
315             default:
316                 break;
317             }
318         }
319
320         // This error message was for a role request message but we dont know
321         // how to handle errors for nicira role request messages
322         return RoleRecvStatus.OTHER_EXPECTATION;
323     }
324
325     /**
326      * Extract the role from an OFVendor message.
327      *
328      * Extract the role from an OFVendor message if the message is a
329      * Nicira role reply. Otherwise return null.
330      *
331      * @param experimenterMsg message
332      * @return The role in the message if the message is a Nicira role
333      * reply, null otherwise.
334      * @throws SwitchStateException If the message is a Nicira role reply
335      * but the numeric role value is unknown.
336      */
337     @Override
338     public RoleState extractNiciraRoleReply(OFExperimenter experimenterMsg)
339             throws SwitchStateException {
340         int vendor = (int) experimenterMsg.getExperimenter();
341         if (vendor != 0x2320) {
342             return null;
343         }
344         OFNiciraControllerRoleReply nrr =
345                 (OFNiciraControllerRoleReply) experimenterMsg;
346
347         RoleState role = null;
348         OFNiciraControllerRole ncr = nrr.getRole();
349         switch (ncr) {
350         case ROLE_MASTER:
351             role = RoleState.MASTER;
352             break;
353         case ROLE_OTHER:
354             role = RoleState.EQUAL;
355             break;
356         case ROLE_SLAVE:
357             role = RoleState.SLAVE;
358             break;
359         default: //handled below
360         }
361
362         if (role == null) {
363             String msg = String.format("Switch: [%s], "
364                     + "received NX_ROLE_REPLY with invalid role "
365                     + "value %s",
366                     sw.getStringId(),
367                     nrr.getRole());
368             throw new SwitchStateException(msg);
369         }
370         return role;
371     }
372
373     /**
374      * Extract the role information from an OF1.3 Role Reply Message.
375      *
376      * @param rrmsg the role message
377      * @return RoleReplyInfo object
378      * @throws SwitchStateException if the role information could not be extracted.
379      */
380     @Override
381     public RoleReplyInfo extractOFRoleReply(OFRoleReply rrmsg)
382             throws SwitchStateException {
383         OFControllerRole cr = rrmsg.getRole();
384         RoleState role = null;
385         switch (cr) {
386         case ROLE_EQUAL:
387             role = RoleState.EQUAL;
388             break;
389         case ROLE_MASTER:
390             role = RoleState.MASTER;
391             break;
392         case ROLE_SLAVE:
393             role = RoleState.SLAVE;
394             break;
395         case ROLE_NOCHANGE: // switch should send current role
396         default:
397             String msg = String.format("Unknown controller role %s "
398                     + "received from switch %s", cr, sw);
399             throw new SwitchStateException(msg);
400         }
401
402         return new RoleReplyInfo(role, rrmsg.getGenerationId(), rrmsg.getXid());
403     }
404
405 }
406