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