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