/**
* Copyright 2013, Big Switch Networks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
**/
package net.floodlightcontroller.core.web;
import java.io.IOException;
import java.lang.Thread.State;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.projectfloodlight.openflow.protocol.OFControllerRole;
import org.projectfloodlight.openflow.protocol.OFFactories;
import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole;
import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply;
import org.projectfloodlight.openflow.protocol.OFRoleReply;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.U64;
import org.restlet.resource.Post;
import org.restlet.resource.ServerResource;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.internal.IOFSwitchService;
import org.restlet.resource.Get;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.google.common.util.concurrent.ListenableFuture;
public class SwitchRoleResource extends ServerResource {
protected static Logger log = LoggerFactory.getLogger(SwitchRoleResource.class);
private static final String STR_ROLE_PREFIX = "ROLE_"; /* enum OFControllerRole is ROLE_XXX, so trim the ROLE_ when printing */
private static final String STR_ROLE_MASTER = "MASTER"; /* these are all assumed uppercase within this class */
private static final String STR_ROLE_SLAVE = "SLAVE";
private static final String STR_ROLE_EQUAL = "EQUAL";
private static final String STR_ROLE_OTHER = "OTHER";
@Get("json")
public Map<String, String> getRole() {
IOFSwitchService switchService =
(IOFSwitchService)getContext().getAttributes().
get(IOFSwitchService.class.getCanonicalName());
String switchId = (String) getRequestAttributes().get(CoreWebRoutable.STR_SWITCH_ID);
HashMap<String, String> model = new HashMap<String, String>();
if (switchId.equalsIgnoreCase(CoreWebRoutable.STR_ALL)) {
for (IOFSwitch sw: switchService.getAllSwitchMap().values()) {
switchId = sw.getId().toString();
model.put(switchId, sw.getControllerRole().toString().replaceFirst(STR_ROLE_PREFIX, ""));
}
return model;
} else {
try {
DatapathId dpid = DatapathId.of(switchId);
IOFSwitch sw = switchService.getSwitch(dpid);
if (sw == null) {
model.put("ERROR", "Switch Manager could not locate switch DPID " + dpid.toString());
return model;
} else {
model.put(dpid.toString(), sw.getControllerRole().toString().replaceFirst(STR_ROLE_PREFIX, ""));
return model;
}
} catch (Exception e) {
model.put("ERROR", "Could not parse switch DPID " + switchId);
return model;
}
}
}
/* for some reason @Post("json") isn't working here... */
@Post
public Map<String, String> setRole(String json) {
IOFSwitchService switchService =
(IOFSwitchService)getContext().getAttributes().
get(IOFSwitchService.class.getCanonicalName());
Map<String, String> retValue = new HashMap<String, String>();
String switchId = (String) getRequestAttributes().get(CoreWebRoutable.STR_SWITCH_ID);
MappingJsonFactory f = new MappingJsonFactory();
JsonParser jp = null;
String role = null;
try {
try {
jp = f.createParser(json);
} catch (IOException e) {
e.printStackTrace();
}
jp.nextToken();
if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
throw new IOException("Expected START_OBJECT");
}
while (jp.nextToken() != JsonToken.END_OBJECT) {
if (jp.getCurrentToken() != JsonToken.FIELD_NAME) {
throw new IOException("Expected FIELD_NAME");
}
String n = jp.getCurrentName().toLowerCase();
jp.nextToken();
switch (n) {
case CoreWebRoutable.STR_ROLE:
role = jp.getText();
if (switchId.equalsIgnoreCase(CoreWebRoutable.STR_ALL)) {
for (IOFSwitch sw: switchService.getAllSwitchMap().values()) {
List<SetConcurrentRoleThread> activeThreads = new ArrayList<SetConcurrentRoleThread>(switchService.getAllSwitchMap().size());
List<SetConcurrentRoleThread> pendingRemovalThreads = new ArrayList<SetConcurrentRoleThread>();
SetConcurrentRoleThread t;
t = new SetConcurrentRoleThread(sw, parseRole(role));
activeThreads.add(t);
t.start();
// Join all the threads after the timeout. Set a hard timeout
// of 12 seconds for the threads to finish. If the thread has not
// finished the switch has not replied yet and therefore we won't
// add the switch's stats to the reply.
for (int iSleepCycles = 0; iSleepCycles < 12; iSleepCycles++) {
for (SetConcurrentRoleThread curThread : activeThreads) {
if (curThread.getState() == State.TERMINATED) {
retValue.put(curThread.getSwitch().getId().toString(),
(curThread.getRoleReply() == null ?
"Error communicating with switch. Role not changed."
: curThread.getRoleReply().getRole().toString().replaceFirst(STR_ROLE_PREFIX, "")
)
);
pendingRemovalThreads.add(curThread);
}
}
// remove the threads that have completed the queries to the switches
for (SetConcurrentRoleThread curThread : pendingRemovalThreads) {
activeThreads.remove(curThread);
}
// clear the list so we don't try to double remove them
pendingRemovalThreads.clear();
// if we are done finish early so we don't always get the worst case
if (activeThreads.isEmpty()) {
break;
}
// sleep for 1 s here
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error("Interrupted while waiting for role replies", e);
retValue.put("ERROR", "Thread sleep interrupted while waiting for role replies.");
}
}
}
} else {
/* Must be a specific switch DPID then. */
try {
DatapathId dpid = DatapathId.of(switchId);
IOFSwitch sw = switchService.getSwitch(dpid);
if (sw == null) {
retValue.put("ERROR", "Switch Manager could not locate switch DPID " + dpid.toString());
} else {
OFRoleReply reply = setSwitchRole(sw, parseRole(role));
retValue.put(sw.getId().toString(),
(reply == null ?
"Error communicating with switch. Role not changed."
: reply.getRole().toString().replaceFirst(STR_ROLE_PREFIX, "")
)
);
}
} catch (Exception e) {
retValue.put("ERROR", "Could not parse switch DPID " + switchId);
}
}
break;
default:
retValue.put("ERROR", "Unrecognized JSON key.");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
retValue.put("ERROR", "Caught IOException while parsing JSON POST request in role request.");
}
return retValue;
}
protected class SetConcurrentRoleThread extends Thread {
private OFRoleReply switchReply;
private OFControllerRole role;
private IOFSwitch sw;
public SetConcurrentRoleThread(IOFSwitch sw, OFControllerRole role) {
this.sw = sw;
this.switchReply = null;
this.role = role;
}
public OFRoleReply getRoleReply() {
return switchReply;
}
public IOFSwitch getSwitch() {
return sw;
}
public OFControllerRole getRole() {
return role;
}
@Override
public void run() {
switchReply = setSwitchRole(sw, role);
}
}
private static OFRoleReply setSwitchRole(IOFSwitch sw, OFControllerRole role) {
try {
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_12) < 0) {
OFNiciraControllerRole nrole;
switch (role) {
case ROLE_EQUAL:
nrole = OFNiciraControllerRole.ROLE_OTHER;
log.warn("Assuming EQUAL as OTHER for Nicira role request.");
break;
case ROLE_MASTER:
nrole = OFNiciraControllerRole.ROLE_MASTER;
break;
case ROLE_SLAVE:
nrole = OFNiciraControllerRole.ROLE_SLAVE;
break;
case ROLE_NOCHANGE:
log.error("Nicira extension does not support NOCHANGE role. Thus, we won't change the role.");
return OFFactories.getFactory(OFVersion.OF_13).buildRoleReply().setRole(sw.getControllerRole()).setGenerationId(U64.ZERO).build();
default:
log.error("Impossible to have anything other than MASTER, OTHER, or SLAVE for Nicira role.");
return OFFactories.getFactory(OFVersion.OF_13).buildRoleReply().setRole(sw.getControllerRole()).setGenerationId(U64.ZERO).build();
}
ListenableFuture<OFNiciraControllerRoleReply> future = sw.writeRequest(sw.getOFFactory().buildNiciraControllerRoleRequest()
.setRole(nrole)
.build());
OFNiciraControllerRoleReply nreply = future.get(10, TimeUnit.SECONDS);
if (nreply != null) {
/* Turn the OFControllerRoleReply into a OFNiciraControllerRoleReply */
switch (nreply.getRole()) {
case ROLE_MASTER:
return OFFactories.getFactory(OFVersion.OF_13).buildRoleReply().setRole(OFControllerRole.ROLE_MASTER).setGenerationId(U64.ZERO).build();
case ROLE_OTHER:
return OFFactories.getFactory(OFVersion.OF_13).buildRoleReply().setRole(OFControllerRole.ROLE_EQUAL).setGenerationId(U64.ZERO).build();
case ROLE_SLAVE:
return OFFactories.getFactory(OFVersion.OF_13).buildRoleReply().setRole(OFControllerRole.ROLE_SLAVE).setGenerationId(U64.ZERO).build();
default:
log.error("Impossible to have anything other than MASTER, OTHER, or SLAVE for Nicira role: {}.", nreply.getRole().toString());
break;
}
} else {
log.error("Did not receive Nicira role reply for switch {}.", sw.getId().toString());
}
} else {
ListenableFuture<OFRoleReply> future = sw.writeRequest(sw.getOFFactory().buildRoleRequest()
.setGenerationId(U64.ZERO)
.setRole(role)
.build());
return future.get(10, TimeUnit.SECONDS);
}
} catch (Exception e) {
log.error("Failure setting switch {} role to {}.", sw.toString(), role.toString());
log.error(e.getMessage());
}
return null;
}
private static OFControllerRole parseRole(String role) {
if (role == null || role.isEmpty()) {
return OFControllerRole.ROLE_NOCHANGE;
}
role = role.toUpperCase();
if (role.contains(STR_ROLE_MASTER)) {
return OFControllerRole.ROLE_MASTER;
} else if (role.contains(STR_ROLE_SLAVE)) {
return OFControllerRole.ROLE_SLAVE;
} else if (role.contains(STR_ROLE_EQUAL) || role.contains(STR_ROLE_OTHER)) {
return OFControllerRole.ROLE_EQUAL;
} else {
return OFControllerRole.ROLE_NOCHANGE;
}
}
}