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