e7e5ffee362067f17fadffdb860aa6f406f183f6
[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 ...", new Object[] {rri,
217                             sw == null ? "(null)" : sw.getStringId(), });
218             return RoleRecvStatus.OTHER_EXPECTATION;
219         }
220
221         // XXX Should check generation id meaningfully and other cases of expectations
222         //if (pendingXid != xid) {
223         //    log.info("Received older role reply from " +
224         //            "switch {} ({}). Ignoring. " +
225         //            "Waiting for {}, xid={}",
226         //            new Object[] {sw.getStringId(), rri,
227         //            pendingRole, pendingXid });
228         //    return RoleRecvStatus.OLD_REPLY;
229         //}
230         sw.returnRoleReply(expectedRole, receivedRole);
231
232         if (expectedRole == receivedRole) {
233             log.debug("Received role reply message from {} that matched "
234                     + "expected role-reply {} with expectations {}",
235                     new Object[] {sw.getStringId(), receivedRole, expectation});
236
237             // Done with this RoleReply; Invalidate
238             pendingReplies.invalidate(xid);
239             if (expectation == RoleRecvStatus.MATCHED_CURRENT_ROLE ||
240                     expectation == RoleRecvStatus.MATCHED_SET_ROLE) {
241                 return expectation;
242             } else {
243                 return RoleRecvStatus.OTHER_EXPECTATION;
244             }
245         }
246
247         pendingReplies.invalidate(xid);
248         // if xids match but role's don't, perhaps its a query (OF1.3)
249         if (expectation == RoleRecvStatus.REPLY_QUERY) {
250             return expectation;
251         }
252
253         return RoleRecvStatus.OTHER_EXPECTATION;
254     }
255
256     /**
257      * Called if we receive an  error message. If the xid matches the
258      * pending request we handle it otherwise we ignore it.
259      *
260      * Note: since we only keep the last pending request we might get
261      * error messages for earlier role requests that we won't be able
262      * to handle
263      */
264     @Override
265     public synchronized RoleRecvStatus deliverError(OFErrorMsg error)
266             throws SwitchStateException {
267         RoleState errorRole = pendingReplies.getIfPresent(error.getXid());
268         if (errorRole == null) {
269             if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
270                 log.debug("Received an error msg from sw {} for a role request,"
271                         + " but not for pending request in role-changer; "
272                         + " ignoring error {} ...",
273                         sw.getStringId(), error);
274             } else {
275                 log.debug("Received an error msg from sw {}, but no pending "
276                         + "requests in role-changer; not handling ...",
277                         sw.getStringId());
278             }
279             return RoleRecvStatus.OTHER_EXPECTATION;
280         }
281         // it is an error related to a currently pending role request message
282         if (error.getErrType() == OFErrorType.BAD_REQUEST) {
283             log.error("Received a error msg {} from sw {} for "
284                     + "pending role request {}. Switch driver indicates "
285                     + "role-messaging is supported. Possible issues in "
286                     + "switch driver configuration?", new Object[] {
287                             ((OFBadRequestErrorMsg) error).toString(),
288                             sw.getStringId(), errorRole
289                     });
290             return RoleRecvStatus.UNSUPPORTED;
291         }
292
293         if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
294             OFRoleRequestFailedErrorMsg rrerr =
295                     (OFRoleRequestFailedErrorMsg) error;
296             switch (rrerr.getCode()) {
297             case BAD_ROLE:
298                 // switch says that current-role-req has bad role?
299                 // for now we disconnect
300                 // fall-thru
301             case STALE:
302                 // switch says that current-role-req has stale gen-id?
303                 // for now we disconnect
304                 // fall-thru
305             case UNSUP:
306                 // switch says that current-role-req has role that
307                 // cannot be supported? for now we disconnect
308                 String msgx = String.format("Switch: [%s], "
309                         + "received Error to for pending role request [%s]. "
310                         + "Error:[%s]. Disconnecting switch ... ",
311                         sw.getStringId(),
312                         errorRole, rrerr);
313                 throw new SwitchStateException(msgx);
314             default:
315                 break;
316             }
317         }
318
319         // This error message was for a role request message but we dont know
320         // how to handle errors for nicira role request messages
321         return RoleRecvStatus.OTHER_EXPECTATION;
322     }
323
324     /**
325      * Extract the role from an OFVendor message.
326      *
327      * Extract the role from an OFVendor message if the message is a
328      * Nicira role reply. Otherwise return null.
329      *
330      * @param experimenterMsg message
331      * @return The role in the message if the message is a Nicira role
332      * reply, null otherwise.
333      * @throws SwitchStateException If the message is a Nicira role reply
334      * but the numeric role value is unknown.
335      */
336     @Override
337     public RoleState extractNiciraRoleReply(OFExperimenter experimenterMsg)
338             throws SwitchStateException {
339         int vendor = (int) experimenterMsg.getExperimenter();
340         if (vendor != 0x2320) {
341             return null;
342         }
343         OFNiciraControllerRoleReply nrr =
344                 (OFNiciraControllerRoleReply) experimenterMsg;
345
346         RoleState role = null;
347         OFNiciraControllerRole ncr = nrr.getRole();
348         switch (ncr) {
349         case ROLE_MASTER:
350             role = RoleState.MASTER;
351             break;
352         case ROLE_OTHER:
353             role = RoleState.EQUAL;
354             break;
355         case ROLE_SLAVE:
356             role = RoleState.SLAVE;
357             break;
358         default: //handled below
359         }
360
361         if (role == null) {
362             String msg = String.format("Switch: [%s], "
363                     + "received NX_ROLE_REPLY with invalid role "
364                     + "value %s",
365                     sw.getStringId(),
366                     nrr.getRole());
367             throw new SwitchStateException(msg);
368         }
369         return role;
370     }
371
372     /**
373      * Extract the role information from an OF1.3 Role Reply Message.
374      *
375      * @param rrmsg the role message
376      * @return RoleReplyInfo object
377      * @throws SwitchStateException if the role information could not be extracted.
378      */
379     @Override
380     public RoleReplyInfo extractOFRoleReply(OFRoleReply rrmsg)
381             throws SwitchStateException {
382         OFControllerRole cr = rrmsg.getRole();
383         RoleState role = null;
384         switch (cr) {
385         case ROLE_EQUAL:
386             role = RoleState.EQUAL;
387             break;
388         case ROLE_MASTER:
389             role = RoleState.MASTER;
390             break;
391         case ROLE_SLAVE:
392             role = RoleState.SLAVE;
393             break;
394         case ROLE_NOCHANGE: // switch should send current role
395         default:
396             String msg = String.format("Unknown controller role %s "
397                     + "received from switch %s", cr, sw);
398             throw new SwitchStateException(msg);
399         }
400
401         return new RoleReplyInfo(role, rrmsg.getGenerationId(), rrmsg.getXid());
402     }
403
404 }
405