Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Tip

This tutorial and the simpleft module were designed by Tulio Alberton Ribeiro

...

of the LaSIGE - Large-Scale Informatics Systems Laboratory

...

 

First thing that you need is generate the key used in challenge response authentication as follow:

Code Block
languagebash

. Thanks Tulio!

Table of Contents

Table of Contents
excludeTable of Contents

Using the Included SimpleFT Module

If you're interested in using the module as written and not about developing or understanding the code, you only need to complete this section.

Creating the Keystore

First thing that you need to do is generate the key used in challenge response authentication as follows:

Code Block
languagebash
# keytool -genkey -alias AliasChallengeResponse -keystore myKey.jceks -keypass "YourPassWord" -storepass "YourPassWord" -storetype JCEKS

Currently the alias option from keytool is hard coded and it is used in CryptoUtil class located at: floodlight/src/main/java/org/sdnplatform/sync/internal/util/CryptoUtil.java:

Code Block
languagejava
public static final String CHALLENGE_RESPONSE_SECRET = "AliasChallengeResponse";

Which which means that it is necessary to use alias option value as defined above. The value set in CHALLENGE_RESPONSE_SECRET var variable will be used to recover the key from the key storekeystore.

As you can see the alias option needs to be "AliasChallengeResponse", unless you change it in both places (keytool generation and CHALLENGE_RESPONSE_SECRET var).

Testing the Keystore

After key generation, you can test itthe accessibility of your keystore:

Code Block
languagebash
# keytool -list -alias AliasChallengeResponse -keystore myKey.jceks -storetype JCEKS
Enter keystore password: YourPassWord /* this should be your password as defined above */
AliasChallengeResponse, 24/Mar/2016, PrivateKeyEntry,
Certificate fingerprint (SHA1): A2:1B:49:1B:18:D8:DC:95:CC:9F:C3:33:94:04:39:EE:44:DD:CF:BE

 

Note that the controllerId and thisNodeId are the same. 

Defining Controllers

The Primary Controller

First, note that the net.floodlightcontroller.core.internal.FloodlightProvider.controllerId and org.sdnplatform.sync.internal.SyncManager.thisNodeId should be set to the same values. Note also that all switches defined in switchesInitialState are in net.floodlightcontroller.core.internal.OFSwitchManager.switchesInitialState should be set to MASTER in the primary controller and SLAVE at in the backup controller.

Code Block
languagejava
title(PRIMARY CONTROLLER) The floodlightdefault.properties file shall be defined as follow:
org.sdnplatform.sync.internal.SyncManager.authScheme=CHALLENGE_RESPONSE
org.sdnplatform.sync.internal.SyncManager.keyStorePath=/etc/floodlight/myKey.jceks
org.sdnplatform.sync.internal.SyncManager.dbPath=/var/lib/floodlight/
org.sdnplatform.sync.internal.SyncManager.keyStorePassword=YourPassWord
org.sdnplatform.sync.internal.SyncManager.port=6642
org.sdnplatform.sync.internal.SyncManager.thisNodeId=1
org.sdnplatform.sync.internal.SyncManager.persistenceEnabled=FALSE
org.sdnplatform.sync.internal.SyncManager.nodes=[\
{"nodeId": 1, "domainId": 1, "hostname": "192.168.1.100", "port": 6642},\
{"nodeId": 2, "domainId": 1, "hostname": "192.168.1.100", "port": 6643}\
]
 
net.floodlightcontroller.core.internal.FloodlightProvider.controllerId=1
net.floodlightcontroller.core.internal.OFSwitchManager.switchesInitialState={"00:00:00:00:00:00:00:01":"ROLE_MASTER","00:00:00:00:00:00:00:02":"ROLE_MASTER", "00:00:00:00:00:00:00:03":"ROLE_MASTER", "00:00:00:00:00:00:00:04":"ROLE_MASTER","00:00:00:00:00:00:00:05":"ROLE_MASTER"}

...

The Backup Controller

Note that the thisNodeId and controllerId and thisNodeId are the same. Note also that all switches defined in switchesInitialState are SLAVE in de secondary are set to 2 in this case (and must be different from 1 as defined above for the master). Also note that the switch roles defined in switchesIntialState are set to SLAVE, as this is the backup controller.

Code Block
languagejava
title(BACKUP CONTROLLER) The floodlightNodeBackup.properties file shall be defined as follow:
org.sdnplatform.sync.internal.SyncManager.authScheme=CHALLENGE_RESPONSE
org.sdnplatform.sync.internal.SyncManager.keyStorePath=/etc/floodlight/key2.jceks
org.sdnplatform.sync.internal.SyncManager.dbPath=/var/lib/floodlight2/
org.sdnplatform.sync.internal.SyncManager.keyStorePassword=PassWord
org.sdnplatform.sync.internal.SyncManager.port=6643
org.sdnplatform.sync.internal.SyncManager.thisNodeId=2
org.sdnplatform.sync.internal.SyncManager.persistenceEnabled=FALSE
org.sdnplatform.sync.internal.SyncManager.nodes=[\
{"nodeId": 1, "domainId": 1, "hostname": "192.168.1.100", "port": 6642},\
{"nodeId": 2, "domainId": 1, "hostname": "192.168.1.100", "port": 6643}\
]
 
net.floodlightcontroller.core.internal.FloodlightProvider.controllerId=2
net.floodlightcontroller.core.internal.OFSwitchManager.switchesInitialState={"00:00:00:00:00:00:00:01":"ROLE_SLAVE","00:00:00:00:00:00:00:02":"ROLE_SLAVE", "00:00:00:00:00:00:00:03":"ROLE_SLAVE", "00:00:00:00:00:00:00:04":"ROLE_SLAVE","00:00:00:00:00:00:00:05":"ROLE_SLAVE"}

...

Running the Module

To use run the sync service, you need create two vars ISyncService and IStoreClient and initiate the syncService: 

Code Block
languagejava
private ISyncService syncService;
private IStoreClient<String, String> storeFT;
this.syncService = context.getServiceImpl(ISyncService.class);

 

And as well as start your store with global scope (which means syncing remote updates):SimpleFT module, make sure it's listed in the list of modules to load in floodlightdefault.properties, save the file, and run the controller. It's as simple as that.

Using the Sync Service – A Developer's Guide

This is not a step-by-step guide. It is expected the reader be comfortable with Java and writing Floodlight modules (tutorial here). One can follow along using the SimpleFT code here.

Initialize

To use the sync service, we need create two variables for ISyncService (the IFloodlightService we'll leverage) and IStoreClient (our module, as a user of the sync service): 

Code Block
languagejava
tryprivate {ISyncService 	this.syncService.registerStore("NameOfMyStore", Scope.GLOBAL);
	this.storeFT = this.syncService.getStoreClient("NameOfMyStore",
					String.class,
					String.class);
	this.storeFT.addStoreListener(this);
} catch (SyncException e) {
	throw new syncService;
private IStoreClient<String, String> storeFT;
this.syncService = context.getServiceImpl(ISyncService.class);

Next, we need to start our store with global scope, which allows us to sync our store with other controllers and receive remote updates:

Code Block
languagejava
try {
	this.syncService.registerStore("NameOfMyStore", Scope.GLOBAL);
	this.storeFT = this.syncService.getStoreClient("NameOfMyStore",
					String.class,
					String.class);
	this.storeFT.addStoreListener(this);
} catch (SyncException e) {
	throw new FloodlightModuleException("Error while setting up sync service", e);
}

...

Read/Write Data Operations

To add data in your to our store:

Code Block
languagejava
try {
	this.storeFT.put("Key Y", "Data X");
} catch (SyncException e) {
	e.printStackTrace();
}

To retrieve data from your our store:

Code Block
languagejava
try {
	this.storeFT.get("Key Y").getValue().toString();
} catch (SyncException e) {
	e.printStackTrace();
}

 

And finally, if you want we wish to monitor your our store, it is necessary implement interface IStoreListener<String> .
in our module or monitoring class. (In this case the store has the String type, but this can vary if you wish to store other types.)

Receiving Updates to the Store (from a Remote Controller)

In the example below we are showing the remote sync events.
But you can uncomment the , we show how our module can receive and process store updates from remote controllers using the keysModified() callback function. If you wish, you can uncomment debug logging code and see local and remote updates from your sync store.

Code Block
@Override
public void keysModified(Iterator<String> keys, org.sdnplatform.sync.IStoreListener.UpdateType type) {

	while(keys.hasNext()){
		String k = keys.next();
		try {
			/*
			logger.debug("keysModified: Key:{}, Value:{}, Type: {}", 
					new Object[] {
							k, 
							storeFT.get(k).getValue().toString(), 
							type.name()
						}
					);
			*/
			if(type.name().equals("REMOTE")){
				String info = storeFT.get(k).getValue();
				logger.debug("REMOTE: Key:{}, Value:{}", k, value);
			}
		} catch (SyncException e) {
			e.printStackTrace();
		}
	}
}

 

Follow the complete code. The code below is part of FT class from simpleft package.

...

Other Details

The FT class uses an RPCListener to monitor RPC connections among the cluster and inform all synced nodes about connected and disconnected events. The fault tolerance module defines a RPCListener and monitors its connections. 

In events from connected and switches the module insert in the store its switch list. And at disconected the event a controller boots up and connects, the SimpleFT module will insert a list of its switches and their roles to the store. And upon controller disconnection events, the module gets the disconnected node's switch list from store and set all the disconnected controller's switches as MASTER. 

Follow the link below for more details. 

https://groups.google.com/a/openflowhub.org/forum/#!topic/floodlight-dev/KJAuKAgz_9M

 

Code Block
languagejava
/**
 * Tulio Alberton Ribeiro
 * 
 * LaSIGE - Large-Scale Informatics Systems Laboratory
 * 
 *    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
 */

package net.floodlightcontroller.simpleft;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.IOFSwitchListener;
import net.floodlightcontroller.core.PortChangeType;
import net.floodlightcontroller.core.internal.FloodlightProvider;
import net.floodlightcontroller.core.internal.IOFSwitchService;
import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.module.IFloodlightModule;
import net.floodlightcontroller.core.module.IFloodlightService;
import net.floodlightcontroller.storage.IStorageSourceService;

import org.projectfloodlight.openflow.protocol.OFControllerRole;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.OFRoleReply;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.types.DatapathId;
import org.sdnplatform.sync.IStoreClient;
import org.sdnplatform.sync.IStoreListener;
import org.sdnplatform.sync.ISyncService;
import org.sdnplatform.sync.ISyncService.Scope;
import org.sdnplatform.sync.error.SyncException;
import org.sdnplatform.sync.internal.rpc.IRPCListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FT implements 
IOFMessageListener, 
IFloodlightModule,
IStoreListener<String>,
IOFSwitchListener,
IRPCListener
{

	private ISyncService syncService;
	private IStoreClient<String, String> storeFT;
	protected static Logger logger = LoggerFactory.getLogger(FT.class);
	protected static IOFSwitchService switchService;
	private String controllerId;

	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return FT.class.getSimpleName();
	}

	@Override
	public boolean isCallbackOrderingPrereq(OFType type, String name) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean isCallbackOrderingPostreq(OFType type, String name) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public Collection<Class<? extends IFloodlightService>> getModuleServices() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
		// TODO Auto-generated method stub
		Collection<Class<? extends IFloodlightService>> l =
				new ArrayList<Class<? extends IFloodlightService>>();
		l.add(IStorageSourceService.class);
		l.add(ISyncService.class);
		l.add(IOFSwitchService.class);
		return l;
	}

	@Override
	public void init(FloodlightModuleContext context)
			throws FloodlightModuleException {
		// TODO Auto-generated method stub
		
		this.syncService = context.getServiceImpl(ISyncService.class);
		switchService = context.getServiceImpl(IOFSwitchService.class);

		Map<String, String> configParams = context.getConfigParams(FloodlightProvider.class);
		controllerId = configParams.get("controllerId");
	}

	@Override
	public void startUp(FloodlightModuleContext context)
			throws FloodlightModuleException {
		
		switchService.addOFSwitchListener(this);
		syncService.addRPCListener(this);
		
		try {
			this.syncService.registerStore("FT_Switches", Scope.GLOBAL);
			this.storeFT = this.syncService
					.getStoreClient("FT_Switches",
							String.class,
							String.class);
			this.storeFT.addStoreListener(this);
		} catch (SyncException e) {
			throw new FloodlightModuleException("Error while setting up sync service", e);
		}
	}

	@Override
	public net.floodlightcontroller.core.IListener.Command receive(
			IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void keysModified(Iterator<String> keys,
			org.sdnplatform.sync.IStoreListener.UpdateType type) {
		// TODO Auto-generated method stub
		while(keys.hasNext()){
			String k = keys.next();
			try {
				/*logger.debug("keysModified: Key:{}, Value:{}, Type: {}", 
						new Object[] {
							k, 
							storeClient.get(k).getValue().toString(), 
							type.name()}
						);*/
				if(type.name().equals("REMOTE")){
					String swIds = storeFT.get(k).getValue();
					logger.debug("REMOTE: NodeId:{}, Switches:{}", k, swIds);
				}
			} catch (SyncException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
	}

	@Override
	public void switchAdded(DatapathId switchId) {
		// TODO Auto-generated method stub
	}

	@Override
	public void switchRemoved(DatapathId switchId) {
		// TODO Auto-generated method stub
		String activeSwitches = getActiveSwitchesAndUpdateSyncInfo();
		logger.debug("Switch REMOVED: {}, Syncing: {}", switchId, activeSwitches);
	}

	@Override
	public void switchActivated(DatapathId switchId) {
		// TODO Auto-generated method stub
		String activeSwitches = getActiveSwitchesAndUpdateSyncInfo();
		logger.debug("Switch ACTIVATED: {}, Syncing: {}", switchId, activeSwitches);
		
	}

	@Override
	public void switchPortChanged(DatapathId switchId, OFPortDesc port,
			PortChangeType type) {
		// TODO Auto-generated method stub
		logger.debug("Switch Port CHANGED: {}", switchId);
	}

	@Override
	public void switchChanged(DatapathId switchId) {
		// TODO Auto-generated method stub
		String activeSwitches = getActiveSwitchesAndUpdateSyncInfo();
		logger.debug("Switch CHANGED: {}, Syncing: {}", switchId, activeSwitches);
		
	}
	
	@Override
	public void switchDeactivated(DatapathId switchId) {
		// TODO Auto-generated method stub
		String activeSwitches = getActiveSwitchesAndUpdateSyncInfo();
		logger.debug("Switch DEACTIVATED: {}, Syncing: {}", switchId, activeSwitches);
	}


	public String getActiveSwitchesAndUpdateSyncInfo(){
		String activeSwitches = "";
		if(switchService == null)
			return "";
		
		Iterator<DatapathId> itDpid = switchService.getAllSwitchDpids().iterator();
		while (itDpid.hasNext()) {
			DatapathId dpid = itDpid.next();
			try{
				if(switchService.getActiveSwitch(dpid).isActive()){
					activeSwitches += dpid;
					if(itDpid.hasNext())
						activeSwitches += ",";	
				}
			}
			catch(NullPointerException npe){
				return "";
			}
		}
		
		if(activeSwitches.equals(""))
			return "";
		
		try {
			this.storeFT.put(controllerId, activeSwitches);
			return activeSwitches;
		} catch (SyncException e) {
			e.printStackTrace();
			return "";
		}
		
	}
	
	public void setSwitchRole(OFControllerRole role, String swId){
		IOFSwitch sw = switchService.getActiveSwitch(DatapathId.of(swId));
		OFRoleReply reply=null;
		UtilDurable utilDurable = new UtilDurable();
		reply = utilDurable.setSwitchRole(sw, role);
		
		if(reply!=null){
			logger.info("DEFINED {} as {}, reply.getRole:{}!", 
					new Object[]{
					sw.getId(), 
					role,
					reply.getRole()});
		}
		else
			logger.info("Reply NULL!");
	}

	@Override
	public void disconnectedNode(Short nodeId) {
		// TODO Auto-generated method stub
		String swIds=null; 
		try {
			swIds = storeFT.get(""+nodeId).getValue();
			logger.debug("Switches managed by nodeId:{}, Switches:{}", nodeId, swIds);							
		} catch (SyncException e) {
			e.printStackTrace();
		}
		
		if(swIds != null){
			String swId[] = swIds.split(",");
			for (int i = 0; i < swId.length; i++) {
				setSwitchRole(OFControllerRole.ROLE_MASTER, swId[i]);	
			}	
		}
	}

	@Override
	public void connectedNode(Short nodeId) {
		// TODO Auto-generated method stub
		String activeSwicthes = getActiveSwitchesAndUpdateSyncInfo();
		logger.debug("NodeID:{} connected, sending my Switches: {}", nodeId, activeSwicthes);
	}
	
}

 

 

More Information

One can follow SimpleFT Q&A threads (such as this one) in the floodlight-dev@openflowhub.org email list.

One might also find Tulio's initial dev thread useful.

 

Lastly, the source code is located on GitHub here.