blob: 99e91732a44506029d95aaacb0595af4a1c4f11a [file] [log] [blame]
alshabib54ebd9c2014-08-27 18:38:41 -07001package org.onlab.onos.of.controller.impl.internal;
2
3import java.io.IOException;
4import java.util.Collections;
alshabib54ebd9c2014-08-27 18:38:41 -07005
6import org.onlab.onos.of.controller.RoleState;
7import org.projectfloodlight.openflow.protocol.OFControllerRole;
8import org.projectfloodlight.openflow.protocol.OFErrorMsg;
9import org.projectfloodlight.openflow.protocol.OFErrorType;
10import org.projectfloodlight.openflow.protocol.OFExperimenter;
11import org.projectfloodlight.openflow.protocol.OFFactories;
12import org.projectfloodlight.openflow.protocol.OFMessage;
13import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole;
14import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply;
15import org.projectfloodlight.openflow.protocol.OFRoleReply;
16import org.projectfloodlight.openflow.protocol.OFRoleRequest;
17import org.projectfloodlight.openflow.protocol.OFVersion;
18import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
19import org.projectfloodlight.openflow.protocol.errormsg.OFRoleRequestFailedErrorMsg;
20import org.projectfloodlight.openflow.types.U64;
21import org.slf4j.Logger;
22import org.slf4j.LoggerFactory;
23
24
25/**
26 * A utility class to handle role requests and replies for this channel.
27 * After a role request is submitted the role changer keeps track of the
28 * pending request, collects the reply (if any) and times out the request
29 * if necessary.
30 *
31 * To simplify role handling we only keep track of the /last/ pending
32 * role reply send to the switch. If multiple requests are pending and
33 * we receive replies for earlier requests we ignore them. However, this
34 * way of handling pending requests implies that we could wait forever if
35 * a new request is submitted before the timeout triggers. If necessary
36 * we could work around that though.
37 */
38class RoleManager {
39 protected static final long NICIRA_EXPERIMENTER = 0x2320;
40
41 private static Logger log = LoggerFactory.getLogger(RoleManager.class);
42 // indicates that a request is currently pending
43 // needs to be volatile to allow correct double-check idiom
44 private volatile boolean requestPending;
45 // the transaction Id of the pending request
46 private int pendingXid;
47 // the role that's pending
48 private RoleState pendingRole;
49
50 // the expectation set by the caller for the returned role
51 private RoleRecvStatus expectation;
alshabib54ebd9c2014-08-27 18:38:41 -070052 private AbstractOpenFlowSwitch sw;
53
54
55 public RoleManager(AbstractOpenFlowSwitch sw) {
56 this.requestPending = false;
57 this.pendingXid = -1;
58 this.pendingRole = null;
alshabib54ebd9c2014-08-27 18:38:41 -070059 this.expectation = RoleRecvStatus.MATCHED_CURRENT_ROLE;
60 this.sw = sw;
61 }
62
63 /**
64 * Send NX role request message to the switch requesting the specified
65 * role.
66 *
67 * @param sw switch to send the role request message to
68 * @param role role to request
69 */
70 private int sendNxRoleRequest(RoleState role) throws IOException {
71 // Convert the role enum to the appropriate role to send
72 OFNiciraControllerRole roleToSend = OFNiciraControllerRole.ROLE_OTHER;
73 switch (role) {
74 case MASTER:
75 roleToSend = OFNiciraControllerRole.ROLE_MASTER;
76 break;
77 case SLAVE:
78 case EQUAL:
79 default:
80 // ensuring that the only two roles sent to 1.0 switches with
81 // Nicira role support, are MASTER and SLAVE
82 roleToSend = OFNiciraControllerRole.ROLE_SLAVE;
83 log.warn("Sending Nx Role.SLAVE to switch {}.", sw);
84 }
alshabibd777b202014-08-28 17:52:55 -070085 int xid = sw.getNextTransactionId();
alshabib54ebd9c2014-08-27 18:38:41 -070086 OFExperimenter roleRequest = OFFactories.getFactory(OFVersion.OF_10)
87 .buildNiciraControllerRoleRequest()
88 .setXid(xid)
89 .setRole(roleToSend)
90 .build();
91 sw.write(Collections.<OFMessage>singletonList(roleRequest));
92 return xid;
93 }
94
95 private int sendOF13RoleRequest(RoleState role) throws IOException {
96 // Convert the role enum to the appropriate role to send
97 OFControllerRole roleToSend = OFControllerRole.ROLE_NOCHANGE;
98 switch (role) {
99 case EQUAL:
100 roleToSend = OFControllerRole.ROLE_EQUAL;
101 break;
102 case MASTER:
103 roleToSend = OFControllerRole.ROLE_MASTER;
104 break;
105 case SLAVE:
106 roleToSend = OFControllerRole.ROLE_SLAVE;
107 break;
108 default:
109 log.warn("Sending default role.noChange to switch {}."
110 + " Should only be used for queries.", sw);
111 }
112
alshabibd777b202014-08-28 17:52:55 -0700113 int xid = sw.getNextTransactionId();
alshabib54ebd9c2014-08-27 18:38:41 -0700114 OFRoleRequest rrm = OFFactories.getFactory(OFVersion.OF_13)
115 .buildRoleRequest()
116 .setRole(roleToSend)
117 .setXid(xid)
118 //FIXME fix below when we actually use generation ids
119 .setGenerationId(U64.ZERO)
120 .build();
alshabibd777b202014-08-28 17:52:55 -0700121 sw.sendMsg(rrm);
alshabib54ebd9c2014-08-27 18:38:41 -0700122 return xid;
123 }
124
125 /**
126 * Send a role request with the given role to the switch and update
127 * the pending request and timestamp.
128 * Sends an OFPT_ROLE_REQUEST to an OF1.3 switch, OR
129 * Sends an NX_ROLE_REQUEST to an OF1.0 switch if configured to support it
130 * in the IOFSwitch driver. If not supported, this method sends nothing
131 * and returns 'false'. The caller should take appropriate action.
132 *
133 * One other optimization we do here is that for OF1.0 switches with
134 * Nicira role message support, we force the Role.EQUAL to become
135 * Role.SLAVE, as there is no defined behavior for the Nicira role OTHER.
136 * We cannot expect it to behave like SLAVE. We don't have this problem with
137 * OF1.3 switches, because Role.EQUAL is well defined and we can simulate
138 * SLAVE behavior by using ASYNC messages.
139 *
140 * @param role
141 * @throws IOException
142 * @returns false if and only if the switch does not support role-request
143 * messages, according to the switch driver; true otherwise.
144 */
145 synchronized boolean sendRoleRequest(RoleState role, RoleRecvStatus exp)
146 throws IOException {
147 this.expectation = exp;
148
149 if (sw.factory().getVersion() == OFVersion.OF_10) {
150 Boolean supportsNxRole = (Boolean)
151 sw.supportNxRole();
152 if (!supportsNxRole) {
153 log.debug("Switch driver indicates no support for Nicira "
154 + "role request messages. Not sending ...");
155 handleUnsentRoleMessage(role,
156 expectation);
157 return false;
158 }
159 // OF1.0 switch with support for NX_ROLE_REQUEST vendor extn.
160 // make Role.EQUAL become Role.SLAVE
161 role = (role == RoleState.EQUAL) ? RoleState.SLAVE : role;
162 pendingXid = sendNxRoleRequest(role);
163 pendingRole = role;
164 requestPending = true;
165 } else {
166 // OF1.3 switch, use OFPT_ROLE_REQUEST message
167 pendingXid = sendOF13RoleRequest(role);
168 pendingRole = role;
169 requestPending = true;
170 }
171 return true;
172 }
173
174 private void handleUnsentRoleMessage(RoleState role,
175 RoleRecvStatus exp) throws IOException {
176 // typically this is triggered for a switch where role messages
177 // are not supported - we confirm that the role being set is
178 // master
179 if (exp != RoleRecvStatus.MATCHED_SET_ROLE) {
180
181 log.error("Expected MASTER role from registry for switch "
182 + "which has no support for role-messages."
183 + "Received {}. It is possible that this switch "
184 + "is connected to other controllers, in which "
185 + "case it should support role messages - not "
186 + "moving forward.", role);
187
188 }
189
190 }
191
192 /**
193 * Deliver a received role reply.
194 *
195 * Check if a request is pending and if the received reply matches the
196 * the expected pending reply (we check both role and xid) we set
197 * the role for the switch/channel.
198 *
199 * If a request is pending but doesn't match the reply we ignore it, and
200 * return
201 *
202 * If no request is pending we disconnect with a SwitchStateException
203 *
204 * @param RoleReplyInfo information about role-reply in format that
205 * controller can understand.
206 * @throws SwitchStateException if no request is pending
207 */
208 synchronized RoleRecvStatus deliverRoleReply(RoleReplyInfo rri)
209 throws SwitchStateException {
210 if (!requestPending) {
211 RoleState currentRole = (sw != null) ? sw.getRole() : null;
212 if (currentRole != null) {
213 if (currentRole == rri.getRole()) {
214 // Don't disconnect if the role reply we received is
215 // for the same role we are already in.
216 log.debug("Received unexpected RoleReply from "
217 + "Switch: {}. "
218 + "Role in reply is same as current role of this "
219 + "controller for this sw. Ignoring ...",
220 sw.getStringId());
221 return RoleRecvStatus.OTHER_EXPECTATION;
222 } else {
223 String msg = String.format("Switch: [%s], "
224 + "received unexpected RoleReply[%s]. "
225 + "No roles are pending, and this controller's "
226 + "current role:[%s] does not match reply. "
227 + "Disconnecting switch ... ",
228 sw.getStringId(),
229 rri, currentRole);
230 throw new SwitchStateException(msg);
231 }
232 }
233 log.debug("Received unexpected RoleReply {} from "
234 + "Switch: {}. "
235 + "This controller has no current role for this sw. "
236 + "Ignoring ...", new Object[] {rri,
237 sw.getStringId(), });
238 return RoleRecvStatus.OTHER_EXPECTATION;
239 }
240
241 int xid = (int) rri.getXid();
242 RoleState role = rri.getRole();
243 // XXX S should check generation id meaningfully and other cases of expectations
alshabib54ebd9c2014-08-27 18:38:41 -0700244
245 if (pendingXid != xid) {
246 log.debug("Received older role reply from " +
247 "switch {} ({}). Ignoring. " +
248 "Waiting for {}, xid={}",
249 new Object[] {sw.getStringId(), rri,
250 pendingRole, pendingXid });
251 return RoleRecvStatus.OLD_REPLY;
252 }
253
254 if (pendingRole == role) {
255 log.debug("Received role reply message from {} that matched "
256 + "expected role-reply {} with expectations {}",
257 new Object[] {sw.getStringId(), role, expectation});
258
alshabib54ebd9c2014-08-27 18:38:41 -0700259 if (expectation == RoleRecvStatus.MATCHED_CURRENT_ROLE ||
260 expectation == RoleRecvStatus.MATCHED_SET_ROLE) {
261 return expectation;
262 } else {
263 return RoleRecvStatus.OTHER_EXPECTATION;
264 }
265 }
266
267 // if xids match but role's don't, perhaps its a query (OF1.3)
268 if (expectation == RoleRecvStatus.REPLY_QUERY) {
269 return expectation;
270 }
271
272 return RoleRecvStatus.OTHER_EXPECTATION;
273 }
274
275 /**
276 * Called if we receive an error message. If the xid matches the
277 * pending request we handle it otherwise we ignore it.
278 *
279 * Note: since we only keep the last pending request we might get
280 * error messages for earlier role requests that we won't be able
281 * to handle
282 */
283 synchronized RoleRecvStatus deliverError(OFErrorMsg error)
284 throws SwitchStateException {
285 if (!requestPending) {
286 log.debug("Received an error msg from sw {}, but no pending "
287 + "requests in role-changer; not handling ...",
288 sw.getStringId());
289 return RoleRecvStatus.OTHER_EXPECTATION;
290 }
291 if (pendingXid != error.getXid()) {
292 if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
293 log.debug("Received an error msg from sw {} for a role request,"
294 + " but not for pending request in role-changer; "
295 + " ignoring error {} ...",
296 sw.getStringId(), error);
297 }
298 return RoleRecvStatus.OTHER_EXPECTATION;
299 }
300 // it is an error related to a currently pending role request message
301 if (error.getErrType() == OFErrorType.BAD_REQUEST) {
302 log.error("Received a error msg {} from sw {} for "
303 + "pending role request {}. Switch driver indicates "
304 + "role-messaging is supported. Possible issues in "
305 + "switch driver configuration?", new Object[] {
306 ((OFBadRequestErrorMsg) error).toString(),
307 sw.getStringId(), pendingRole
308 });
309 return RoleRecvStatus.UNSUPPORTED;
310 }
311
312 if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
313 OFRoleRequestFailedErrorMsg rrerr =
314 (OFRoleRequestFailedErrorMsg) error;
315 switch (rrerr.getCode()) {
316 case BAD_ROLE:
317 // switch says that current-role-req has bad role?
318 // for now we disconnect
319 // fall-thru
320 case STALE:
321 // switch says that current-role-req has stale gen-id?
322 // for now we disconnect
323 // fall-thru
324 case UNSUP:
325 // switch says that current-role-req has role that
326 // cannot be supported? for now we disconnect
327 String msgx = String.format("Switch: [%s], "
328 + "received Error to for pending role request [%s]. "
329 + "Error:[%s]. Disconnecting switch ... ",
330 sw.getStringId(),
331 pendingRole, rrerr);
332 throw new SwitchStateException(msgx);
333 default:
334 break;
335 }
336 }
337
338 // This error message was for a role request message but we dont know
339 // how to handle errors for nicira role request messages
340 return RoleRecvStatus.OTHER_EXPECTATION;
341 }
342
343 /**
344 * Extract the role from an OFVendor message.
345 *
346 * Extract the role from an OFVendor message if the message is a
347 * Nicira role reply. Otherwise return null.
348 *
349 * @param h The channel handler receiving the message
350 * @param vendorMessage The vendor message to parse.
351 * @return The role in the message if the message is a Nicira role
352 * reply, null otherwise.
353 * @throws SwitchStateException If the message is a Nicira role reply
354 * but the numeric role value is unknown.
355 */
356 protected RoleState extractNiciraRoleReply(OFExperimenter experimenterMsg)
357 throws SwitchStateException {
358 int vendor = (int) experimenterMsg.getExperimenter();
359 if (vendor != 0x2320) {
360 return null;
361 }
362 OFNiciraControllerRoleReply nrr =
363 (OFNiciraControllerRoleReply) experimenterMsg;
364
365 RoleState role = null;
366 OFNiciraControllerRole ncr = nrr.getRole();
367 switch(ncr) {
368 case ROLE_MASTER:
369 role = RoleState.MASTER;
370 break;
371 case ROLE_OTHER:
372 role = RoleState.EQUAL;
373 break;
374 case ROLE_SLAVE:
375 role = RoleState.SLAVE;
376 break;
377 default: //handled below
378 }
379
380 if (role == null) {
381 String msg = String.format("Switch: [%s], "
382 + "received NX_ROLE_REPLY with invalid role "
383 + "value %s",
384 sw.getStringId(),
385 nrr.getRole());
386 throw new SwitchStateException(msg);
387 }
388 return role;
389 }
390
391 /**
392 * When we remove a pending role request we use this enum to indicate how we
393 * arrived at the decision. When we send a role request to the switch, we
394 * also use this enum to indicate what we expect back from the switch, so the
395 * role changer can match the reply to our expectation.
396 */
397 public enum RoleRecvStatus {
398 /** The switch returned an error indicating that roles are not.
399 * supported*/
400 UNSUPPORTED,
401 /** The request timed out. */
402 NO_REPLY,
403 /** The reply was old, there is a newer request pending. */
404 OLD_REPLY,
405 /**
406 * The reply's role matched the role that this controller set in the
407 * request message - invoked either initially at startup or to reassert
408 * current role.
409 */
410 MATCHED_CURRENT_ROLE,
411 /**
412 * The reply's role matched the role that this controller set in the
413 * request message - this is the result of a callback from the
414 * global registry, followed by a role request sent to the switch.
415 */
416 MATCHED_SET_ROLE,
417 /**
418 * The reply's role was a response to the query made by this controller.
419 */
420 REPLY_QUERY,
421 /** We received a role reply message from the switch
422 * but the expectation was unclear, or there was no expectation.
423 */
424 OTHER_EXPECTATION,
425 }
426
427 /**
428 * Helper class returns role reply information in the format understood
429 * by the controller.
430 */
431 protected static class RoleReplyInfo {
432 private RoleState role;
433 private U64 genId;
434 private long xid;
435
436 RoleReplyInfo(RoleState role, U64 genId, long xid) {
437 this.role = role;
438 this.genId = genId;
439 this.xid = xid;
440 }
441 public RoleState getRole() { return role; }
442 public U64 getGenId() { return genId; }
443 public long getXid() { return xid; }
444 @Override
445 public String toString() {
446 return "[Role:" + role + " GenId:" + genId + " Xid:" + xid + "]";
447 }
448 }
449
450 /**
451 * Extract the role information from an OF1.3 Role Reply Message.
452 * @param h
453 * @param rrmsg
454 * @return RoleReplyInfo object
455 * @throws SwitchStateException
456 */
457 protected RoleReplyInfo extractOFRoleReply(OFRoleReply rrmsg)
458 throws SwitchStateException {
459 OFControllerRole cr = rrmsg.getRole();
460 RoleState role = null;
461 switch(cr) {
462 case ROLE_EQUAL:
463 role = RoleState.EQUAL;
464 break;
465 case ROLE_MASTER:
466 role = RoleState.MASTER;
467 break;
468 case ROLE_SLAVE:
469 role = RoleState.SLAVE;
470 break;
471 case ROLE_NOCHANGE: // switch should send current role
472 default:
473 String msg = String.format("Unknown controller role %s "
474 + "received from switch %s", cr, sw);
475 throw new SwitchStateException(msg);
476 }
477
478 return new RoleReplyInfo(role, rrmsg.getGenerationId(), rrmsg.getXid());
479 }
480
481}
482