Praseed Balakrishnan

Merge branch 'master' of ssh://gerrit.onlab.us:29418/onos-next

Conflicts:
	providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
Showing 253 changed files with 15265 additions and 3555 deletions
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-apps</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-calendar</artifactId>
<packaging>bundle</packaging>
<description>ONOS simple calendaring REST interface for intents</description>
<properties>
<web.context>/onos/calendar</web.context>
</properties>
<dependencies>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-rest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-servlet</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-core</artifactId>
<version>1.18.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-grizzly2</artifactId>
<version>1.18.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<_wab>src/main/webapp/</_wab>
<Bundle-SymbolicName>
${project.groupId}.${project.artifactId}
</Bundle-SymbolicName>
<Import-Package>
org.osgi.framework,
javax.ws.rs,javax.ws.rs.core,
com.sun.jersey.api.core,
com.sun.jersey.spi.container.servlet,
com.sun.jersey.server.impl.container.servlet,
org.onlab.packet.*,
org.onlab.rest.*,
org.onlab.onos.*
</Import-Package>
<Web-ContextPath>${web.context}</Web-ContextPath>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
package org.onlab.onos.calendar;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.intent.IntentService;
import org.onlab.rest.BaseResource;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import java.net.URI;
import static org.onlab.onos.net.PortNumber.portNumber;
/**
* Web resource for triggering calendared intents.
*/
@Path("intent")
public class BandwidthCalendarResource extends BaseResource {
@POST
@Path("{src}/{dst}/{srcPort}/{dstPort}/{bandwidth}")
public Response createIntent(@PathParam("src") String src,
@PathParam("dst") String dst,
@PathParam("srcPort") String srcPort,
@PathParam("dstPort") String dstPort,
@PathParam("bandwidth") String bandwidth) {
// TODO: implement calls to intent framework
IntentService service = get(IntentService.class);
ConnectPoint srcPoint = new ConnectPoint(deviceId(src), portNumber(srcPort));
ConnectPoint dstPoint = new ConnectPoint(deviceId(dst), portNumber(dstPort));
return Response.ok("Yo! We got src=" + srcPoint + "; dst=" + dstPoint +
"; bw=" + bandwidth + "; intent service " + service).build();
}
private DeviceId deviceId(String dpid) {
return DeviceId.deviceId(URI.create("of:" + dpid));
}
}
/**
* Application providing integration between OSCARS and ONOS intent
* framework via REST API.
*/
package org.onlab.onos.calendar;
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="ONOS" version="2.5">
<display-name>ONOS GUI</display-name>
<servlet>
<servlet-name>JAX-RS Service</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>org.onlab.onos.calendar</param-value>
</init-param>
<load-on-startup>10</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JAX-RS Service</servlet-name>
<url-pattern>/rs/*</url-pattern>
</servlet-mapping>
</web-app>
\ No newline at end of file
......@@ -16,4 +16,11 @@
<description>ONOS simple reactive forwarding app</description>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
</dependencies>
</project>
......
package org.onlab.onos.fwd;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.Set;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.onos.ApplicationId;
......@@ -29,8 +27,14 @@ import org.onlab.onos.net.packet.PacketProcessor;
import org.onlab.onos.net.packet.PacketService;
import org.onlab.onos.net.topology.TopologyService;
import org.onlab.packet.Ethernet;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import java.util.Dictionary;
import java.util.Set;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Sample reactive forwarding application.
*/
......@@ -61,6 +65,10 @@ public class ReactiveForwarding {
private ApplicationId appId;
@Property(name = "enabled", boolValue = true,
label = "Enable forwarding; default is true")
private boolean isEnabled = true;
@Activate
public void activate() {
appId = coreService.registerApplication("org.onlab.onos.fwd");
......@@ -76,6 +84,22 @@ public class ReactiveForwarding {
log.info("Stopped");
}
@Modified
public void modified(ComponentContext context) {
Dictionary properties = context.getProperties();
String flag = (String) properties.get("enabled");
if (flag != null) {
boolean enabled = flag.equals("true");
if (isEnabled != enabled) {
isEnabled = enabled;
if (!isEnabled) {
flowRuleService.removeFlowRulesById(appId);
}
log.info("Reconfigured. Forwarding is {}",
isEnabled ? "enabled" : "disabled");
}
}
}
/**
* Packet processor responsible for forwarding packets along their paths.
......@@ -86,7 +110,7 @@ public class ReactiveForwarding {
public void process(PacketContext context) {
// Stop processing if the packet has been handled, since we
// can't do any more to it.
if (context.isHandled()) {
if (!isEnabled || context.isHandled()) {
return;
}
......@@ -185,7 +209,6 @@ public class ReactiveForwarding {
builder.build(), treat.build(), PRIORITY, appId, TIMEOUT);
flowRuleService.applyFlowRules(f);
}
}
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-apps</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-app-optical</artifactId>
<packaging>bundle</packaging>
<description>ONOS application for packet/optical deployments</description>
<dependencies>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
package org.onlab.onos.optical.cfg;
import static org.onlab.onos.net.DeviceId.deviceId;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultAnnotations;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.MastershipRole;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.device.DefaultDeviceDescription;
import org.onlab.onos.net.device.DeviceDescription;
import org.onlab.onos.net.device.DeviceProvider;
import org.onlab.onos.net.device.DeviceProviderRegistry;
import org.onlab.onos.net.device.DeviceProviderService;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.link.LinkProvider;
import org.onlab.onos.net.link.LinkProviderRegistry;
import org.onlab.onos.net.link.LinkProviderService;
import org.onlab.onos.net.provider.AbstractProvider;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.packet.ChassisId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* OpticalConfigProvider emulates the SB network provider for optical switches,
* optical links and any other state that needs to be configured for correct network
* operations.
*
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@Component(immediate = true)
public class OpticalConfigProvider extends AbstractProvider implements DeviceProvider, LinkProvider {
protected static final Logger log = LoggerFactory
.getLogger(OpticalConfigProvider.class);
// TODO: fix hard coded file path later.
private static final String DEFAULT_CONFIG_FILE =
"/opt/onos/config/demo-3-roadm-2-ps.json";
private String configFileName = DEFAULT_CONFIG_FILE;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LinkProviderRegistry linkProviderRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceProviderRegistry deviceProviderRegistry;
private static final String OPTICAL_ANNOTATION = "optical.";
private LinkProviderService linkProviderService;
private DeviceProviderService deviceProviderService;
private static final List<Roadm> RAW_ROADMS = new ArrayList<>();
private static final List<WdmLink> RAW_WDMLINKS = new ArrayList<>();
private static final List<PktOptLink> RAW_PKTOPTLINKS = new ArrayList<>();
private static final String ROADM = "Roadm";
private static final String WDM_LINK = "wdmLink";
private static final String PKT_OPT_LINK = "pktOptLink";
protected OpticalNetworkConfig opticalNetworkConfig;
public OpticalConfigProvider() {
super(new ProviderId("of", "org.onlab.onos.provider.opticalConfig", true));
}
@Activate
protected void activate() {
linkProviderService = linkProviderRegistry.register(this);
deviceProviderService = deviceProviderRegistry.register(this);
log.info("Starting optical network configuration process...");
log.info("Optical config file set to {}", configFileName);
loadOpticalConfig();
parseOpticalConfig();
publishOpticalConfig();
}
@Deactivate
protected void deactivate() {
linkProviderRegistry.unregister(this);
linkProviderService = null;
deviceProviderRegistry.unregister(this);
deviceProviderService = null;
RAW_ROADMS.clear();
RAW_WDMLINKS.clear();
RAW_PKTOPTLINKS.clear();
log.info("Stopped");
}
private void loadOpticalConfig() {
ObjectMapper mapper = new ObjectMapper();
opticalNetworkConfig = new OpticalNetworkConfig();
try {
opticalNetworkConfig = mapper.readValue(new File(configFileName), OpticalNetworkConfig.class);
} catch (JsonParseException e) {
String err = String.format("JsonParseException while loading network "
+ "config from file: %s: %s", configFileName, e.getMessage());
log.error(err, e);
} catch (JsonMappingException e) {
String err = String.format(
"JsonMappingException while loading network config "
+ "from file: %s: %s", configFileName, e.getMessage());
log.error(err, e);
} catch (IOException e) {
String err = String.format("IOException while loading network config "
+ "from file: %s %s", configFileName, e.getMessage());
log.error(err, e);
}
}
private void parseOpticalConfig() {
List<OpticalSwitchDescription> swList = opticalNetworkConfig.getOpticalSwitches();
List<OpticalLinkDescription> lkList = opticalNetworkConfig.getOpticalLinks();
for (OpticalSwitchDescription sw : swList) {
String swtype = sw.getType();
boolean allow = sw.isAllowed();
if (swtype.equals(ROADM) && allow) {
int regNum = 0;
Set<Map.Entry<String, JsonNode>> m = sw.params.entrySet();
for (Map.Entry<String, JsonNode> e : m) {
String key = e.getKey();
JsonNode j = e.getValue();
if (key.equals("numRegen")) {
regNum = j.asInt();
}
}
Roadm newRoadm = new Roadm();
newRoadm.setName(sw.name);
newRoadm.setNodeId(sw.nodeDpid);
newRoadm.setLongtitude(sw.longitude);
newRoadm.setLatitude(sw.latitude);
newRoadm.setRegenNum(regNum);
RAW_ROADMS.add(newRoadm);
log.info(newRoadm.toString());
}
}
for (OpticalLinkDescription lk : lkList) {
String lktype = lk.getType();
switch (lktype) {
case WDM_LINK:
WdmLink newWdmLink = new WdmLink();
newWdmLink.setSrcNodeId(lk.getNodeDpid1());
newWdmLink.setSnkNodeId(lk.getNodeDpid2());
newWdmLink.setAdminWeight(1000); // default weight for each WDM link.
Set<Map.Entry<String, JsonNode>> m = lk.params.entrySet();
for (Map.Entry<String, JsonNode> e : m) {
String key = e.getKey();
JsonNode j = e.getValue();
if (key.equals("nodeName1")) {
newWdmLink.setSrcNodeName(j.asText());
} else if (key.equals("nodeName2")) {
newWdmLink.setSnkNodeName(j.asText());
} else if (key.equals("port1")) {
newWdmLink.setSrcPort(j.asInt());
} else if (key.equals("port2")) {
newWdmLink.setSnkPort(j.asInt());
} else if (key.equals("distKms")) {
newWdmLink.setDistance(j.asDouble());
} else if (key.equals("numWaves")) {
newWdmLink.setWavelengthNumber(j.asInt());
} else {
log.error("error found");
// TODO add exception processing;
}
}
RAW_WDMLINKS.add(newWdmLink);
log.info(newWdmLink.toString());
break;
case PKT_OPT_LINK:
PktOptLink newPktOptLink = new PktOptLink();
newPktOptLink.setSrcNodeId(lk.getNodeDpid1());
newPktOptLink.setSnkNodeId(lk.getNodeDpid2());
newPktOptLink.setAdminWeight(10); // default weight for each packet-optical link.
Set<Map.Entry<String, JsonNode>> ptm = lk.params.entrySet();
for (Map.Entry<String, JsonNode> e : ptm) {
String key = e.getKey();
JsonNode j = e.getValue();
if (key.equals("nodeName1")) {
newPktOptLink.setSrcNodeName(j.asText());
} else if (key.equals("nodeName2")) {
newPktOptLink.setSnkNodeName(j.asText());
} else if (key.equals("port1")) {
newPktOptLink.setSrcPort(j.asInt());
} else if (key.equals("port2")) {
newPktOptLink.setSnkPort(j.asInt());
} else if (key.equals("bandWidth")) {
newPktOptLink.setBandwdith(j.asDouble());
} else {
log.error("error found");
// TODO add exception processing;
}
}
RAW_PKTOPTLINKS.add(newPktOptLink);
log.info(newPktOptLink.toString());
break;
default:
}
}
}
private void publishOpticalConfig() {
if (deviceProviderService == null || linkProviderService == null) {
return;
}
// Discover the optical ROADM objects
Iterator<Roadm> iterWdmNode = RAW_ROADMS.iterator();
while (iterWdmNode.hasNext()) {
Roadm value = iterWdmNode.next();
DeviceId did = deviceId("of:" + value.getNodeId().replace(":", ""));
ChassisId cid = new ChassisId(value.getNodeId());
DefaultAnnotations extendedAttributes = DefaultAnnotations.builder()
.set(OPTICAL_ANNOTATION + "switchType", "ROADM")
.set(OPTICAL_ANNOTATION + "switchName", value.getName())
.set(OPTICAL_ANNOTATION + "latitude", Double.toString(value.getLatitude()))
.set(OPTICAL_ANNOTATION + "longtitude", Double.toString(value.getLongtitude()))
.set(OPTICAL_ANNOTATION + "regNum", Integer.toString(value.getRegenNum()))
.build();
DeviceDescription description =
new DefaultDeviceDescription(did.uri(),
Device.Type.SWITCH,
"",
"",
"",
"",
cid,
extendedAttributes);
deviceProviderService.deviceConnected(did, description);
}
// Discover the optical WDM link objects
Iterator<WdmLink> iterWdmlink = RAW_WDMLINKS.iterator();
while (iterWdmlink.hasNext()) {
WdmLink value = iterWdmlink.next();
DeviceId srcNodeId = deviceId("of:" + value.getSrcNodeId().replace(":", ""));
DeviceId snkNodeId = deviceId("of:" + value.getSnkNodeId().replace(":", ""));
PortNumber srcPort = PortNumber.portNumber(value.getSrcPort());
PortNumber snkPort = PortNumber.portNumber(value.getSnkPort());
ConnectPoint srcPoint = new ConnectPoint(srcNodeId, srcPort);
ConnectPoint snkPoint = new ConnectPoint(snkNodeId, snkPort);
DefaultAnnotations extendedAttributes = DefaultAnnotations.builder()
.set(OPTICAL_ANNOTATION + "linkType", "WDM")
.set(OPTICAL_ANNOTATION + "distance", Double.toString(value.getDistance()))
.set(OPTICAL_ANNOTATION + "cost", Double.toString(value.getDistance()))
.set(OPTICAL_ANNOTATION + "adminWeight", Double.toString(value.getAdminWeight()))
.set(OPTICAL_ANNOTATION + "wavelengthNum", Integer.toString(value.getWavelengthNumber()))
.build();
DefaultLinkDescription linkDescription =
new DefaultLinkDescription(srcPoint,
snkPoint,
Link.Type.DIRECT,
extendedAttributes);
linkProviderService.linkDetected(linkDescription);
log.info(String.format("WDM link: %s : %s",
linkDescription.src().toString(), linkDescription.dst().toString()));
}
// Discover the packet optical link objects
Iterator<PktOptLink> iterPktOptlink = RAW_PKTOPTLINKS.iterator();
while (iterPktOptlink.hasNext()) {
PktOptLink value = iterPktOptlink.next();
DeviceId srcNodeId = deviceId("of:" + value.getSrcNodeId().replace(":", ""));
DeviceId snkNodeId = deviceId("of:" + value.getSnkNodeId().replace(":", ""));
PortNumber srcPort = PortNumber.portNumber(value.getSrcPort());
PortNumber snkPort = PortNumber.portNumber(value.getSnkPort());
ConnectPoint srcPoint = new ConnectPoint(srcNodeId, srcPort);
ConnectPoint snkPoint = new ConnectPoint(snkNodeId, snkPort);
DefaultAnnotations extendedAttributes = DefaultAnnotations.builder()
.set(OPTICAL_ANNOTATION + "linkType", "PktOptLink")
.set(OPTICAL_ANNOTATION + "bandwidth", Double.toString(value.getBandwidth()))
.set(OPTICAL_ANNOTATION + "cost", Double.toString(value.getBandwidth()))
.set(OPTICAL_ANNOTATION + "adminWeight", Double.toString(value.getAdminWeight()))
.build();
DefaultLinkDescription linkDescription =
new DefaultLinkDescription(srcPoint,
snkPoint,
Link.Type.DIRECT,
extendedAttributes);
linkProviderService.linkDetected(linkDescription);
log.info(String.format("Packet-optical link: %s : %s",
linkDescription.src().toString(), linkDescription.dst().toString()));
}
}
@Override
public void triggerProbe(Device device) {
// TODO We may want to consider re-reading config files and publishing them based on this event.
}
@Override
public void roleChanged(Device device, MastershipRole newRole) {
// TODO Auto-generated method stub.
}
}
package org.onlab.onos.optical.cfg;
import java.util.Map;
import org.codehaus.jackson.JsonNode;
import org.onlab.util.HexString;
/**
* Public class corresponding to JSON described data model.
*/
public class OpticalLinkDescription {
protected String type;
protected Boolean allowed;
protected long dpid1;
protected long dpid2;
protected String nodeDpid1;
protected String nodeDpid2;
protected Map<String, JsonNode> params;
protected Map<String, String> publishAttributes;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Boolean isAllowed() {
return allowed;
}
public void setAllowed(Boolean allowed) {
this.allowed = allowed;
}
public String getNodeDpid1() {
return nodeDpid1;
}
public void setNodeDpid1(String nodeDpid1) {
this.nodeDpid1 = nodeDpid1;
this.dpid1 = HexString.toLong(nodeDpid1);
}
public String getNodeDpid2() {
return nodeDpid2;
}
public void setNodeDpid2(String nodeDpid2) {
this.nodeDpid2 = nodeDpid2;
this.dpid2 = HexString.toLong(nodeDpid2);
}
public long getDpid1() {
return dpid1;
}
public void setDpid1(long dpid1) {
this.dpid1 = dpid1;
this.nodeDpid1 = HexString.toHexString(dpid1);
}
public long getDpid2() {
return dpid2;
}
public void setDpid2(long dpid2) {
this.dpid2 = dpid2;
this.nodeDpid2 = HexString.toHexString(dpid2);
}
public Map<String, JsonNode> getParams() {
return params;
}
public void setParams(Map<String, JsonNode> params) {
this.params = params;
}
public Map<String, String> getPublishAttributes() {
return publishAttributes;
}
public void setPublishAttributes(Map<String, String> publishAttributes) {
this.publishAttributes = publishAttributes;
}
}
package org.onlab.onos.optical.cfg;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Public class corresponding to JSON described data model.
*/
public class OpticalNetworkConfig {
protected static final Logger log = LoggerFactory.getLogger(OpticalNetworkConfig.class);
private List<OpticalSwitchDescription> opticalSwitches;
private List<OpticalLinkDescription> opticalLinks;
public OpticalNetworkConfig() {
opticalSwitches = new ArrayList<OpticalSwitchDescription>();
opticalLinks = new ArrayList<OpticalLinkDescription>();
}
public List<OpticalSwitchDescription> getOpticalSwitches() {
return opticalSwitches;
}
public void setOpticalSwitches(List<OpticalSwitchDescription> switches) {
this.opticalSwitches = switches;
}
public List<OpticalLinkDescription> getOpticalLinks() {
return opticalLinks;
}
public void setOpticalLinks(List<OpticalLinkDescription> links) {
this.opticalLinks = links;
}
}
package org.onlab.onos.optical.cfg;
import java.util.Map;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.annotate.JsonProperty;
import org.onlab.util.HexString;
/**
* Public class corresponding to JSON described data model.
*/
public class OpticalSwitchDescription {
protected String name;
protected long dpid;
protected String nodeDpid;
protected String type;
protected double latitude;
protected double longitude;
protected boolean allowed;
protected Map<String, JsonNode> params;
protected Map<String, String> publishAttributes;
public String getName() {
return name;
}
@JsonProperty("name")
public void setName(String name) {
this.name = name;
}
public long getDpid() {
return dpid;
}
@JsonProperty("dpid")
public void setDpid(long dpid) {
this.dpid = dpid;
this.nodeDpid = HexString.toHexString(dpid);
}
public String getNodeDpid() {
return nodeDpid;
}
public String getHexDpid() {
return nodeDpid;
}
public void setNodeDpid(String nodeDpid) {
this.nodeDpid = nodeDpid;
this.dpid = HexString.toLong(nodeDpid);
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public boolean isAllowed() {
return allowed;
}
public void setAllowed(boolean allowed) {
this.allowed = allowed;
}
public Map<String, JsonNode> getParams() {
return params;
}
public void setParams(Map<String, JsonNode> params) {
this.params = params;
}
public Map<String, String> getPublishAttributes() {
return publishAttributes;
}
public void setPublishAttributes(Map<String, String> publishAttributes) {
this.publishAttributes = publishAttributes;
}
}
package org.onlab.onos.optical.cfg;
/**
* Packet-optical link Java data object.
*/
class PktOptLink {
private String srcNodeName;
private String snkNodeName;
private String srcNodeId;
private String snkNodeId;
private int srcPort;
private int snkPort;
private double bandwidth;
private double cost;
private long adminWeight;
public PktOptLink(String srcName, String snkName) {
this.srcNodeName = srcName;
this.snkNodeName = snkName;
}
public PktOptLink() {
// TODO Auto-generated constructor stub
}
public void setSrcNodeName(String name) {
this.srcNodeName = name;
}
public String getSrcNodeName() {
return this.srcNodeName;
}
public void setSnkNodeName(String name) {
this.snkNodeName = name;
}
public String getSnkNodeName() {
return this.snkNodeName;
}
public void setSrcNodeId(String nodeId) {
this.srcNodeId = nodeId;
}
public String getSrcNodeId() {
return this.srcNodeId;
}
public void setSnkNodeId(String nodeId) {
this.snkNodeId = nodeId;
}
public String getSnkNodeId() {
return this.snkNodeId;
}
public void setSrcPort(int port) {
this.srcPort = port;
}
public int getSrcPort() {
return this.srcPort;
}
public void setSnkPort(int port) {
this.snkPort = port;
}
public int getSnkPort() {
return this.snkPort;
}
public void setBandwdith(double x) {
this.bandwidth = x;
}
public double getBandwidth() {
return this.bandwidth;
}
public void setCost(double x) {
this.cost = x;
}
public double getCost() {
return this.cost;
}
public void setAdminWeight(long x) {
this.adminWeight = x;
}
public long getAdminWeight() {
return this.adminWeight;
}
@Override
public String toString() {
return new StringBuilder(" srcNodeName: ").append(this.srcNodeName)
.append(" snkNodeName: ").append(this.snkNodeName)
.append(" srcNodeId: ").append(this.srcNodeId)
.append(" snkNodeId: ").append(this.snkNodeId)
.append(" srcPort: ").append(this.srcPort)
.append(" snkPort: ").append(this.snkPort)
.append(" bandwidth: ").append(this.bandwidth)
.append(" cost: ").append(this.cost)
.append(" adminWeight: ").append(this.adminWeight).toString();
}
}
package org.onlab.onos.optical.cfg;
/**
* ROADM java data object converted from a JSON file.
*/
class Roadm {
private String name;
private String nodeID;
private double longtitude;
private double latitude;
private int regenNum;
//TODO use the following attributes when needed for configurations
private int tPort10G;
private int tPort40G;
private int tPort100G;
private int wPort;
public Roadm() {
}
public Roadm(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setNodeId(String nameId) {
this.nodeID = nameId;
}
public String getNodeId() {
return this.nodeID;
}
public void setLongtitude(double x) {
this.longtitude = x;
}
public double getLongtitude() {
return this.longtitude;
}
public void setLatitude(double y) {
this.latitude = y;
}
public double getLatitude() {
return this.latitude;
}
public void setRegenNum(int num) {
this.regenNum = num;
}
public int getRegenNum() {
return this.regenNum;
}
public void setTport10GNum(int num) {
this.tPort10G = num;
}
public int getTport10GNum() {
return this.tPort10G;
}
public void setTport40GNum(int num) {
this.tPort40G = num;
}
public int getTport40GNum() {
return this.tPort40G;
}
public void setTport100GNum(int num) {
this.tPort100G = num;
}
public int getTport100GNum() {
return this.tPort100G;
}
public void setWportNum(int num) {
this.wPort = num;
}
public int getWportNum() {
return this.wPort;
}
@Override
public String toString() {
return new StringBuilder(" ROADM Name: ").append(this.name)
.append(" nodeID: ").append(this.nodeID)
.append(" longtitude: ").append(this.longtitude)
.append(" latitude: ").append(this.latitude)
.append(" regenNum: ").append(this.regenNum)
.append(" 10GTportNum: ").append(this.tPort10G)
.append(" 40GTportNum: ").append(this.tPort40G)
.append(" 100GTportNum: ").append(this.tPort100G)
.append(" WportNum: ").append(this.wPort).toString();
}
}
package org.onlab.onos.optical.cfg;
/**
* WDM Link Java data object converted from a JSON file.
*/
class WdmLink {
private String srcNodeName;
private String snkNodeName;
private String srcNodeId;
private String snkNodeId;
private int srcPort;
private int snkPort;
private double distance;
private double cost;
private int wavelengthNumber;
private long adminWeight;
public WdmLink(String name1, String name2) {
this.srcNodeName = name1;
this.snkNodeName = name2;
}
public WdmLink() {
// TODO Auto-generated constructor stub
}
public void setSrcNodeName(String name) {
this.srcNodeName = name;
}
public String getSrcNodeName() {
return this.srcNodeName;
}
public void setSnkNodeName(String name) {
this.snkNodeName = name;
}
public String getSnkNodeName() {
return this.snkNodeName;
}
public void setSrcNodeId(String nodeId) {
this.srcNodeId = nodeId;
}
public String getSrcNodeId() {
return this.srcNodeId;
}
public void setSnkNodeId(String nodeId) {
this.snkNodeId = nodeId;
}
public String getSnkNodeId() {
return this.snkNodeId;
}
public void setSrcPort(int port) {
this.srcPort = port;
}
public int getSrcPort() {
return this.srcPort;
}
public void setSnkPort(int port) {
this.snkPort = port;
}
public int getSnkPort() {
return this.snkPort;
}
public void setDistance(double x) {
this.distance = x;
}
public double getDistance() {
return this.distance;
}
public void setCost(double x) {
this.cost = x;
}
public double getCost() {
return this.cost;
}
public void setWavelengthNumber(int x) {
this.wavelengthNumber = x;
}
public int getWavelengthNumber() {
return this.wavelengthNumber;
}
public void setAdminWeight(long x) {
this.adminWeight = x;
}
public long getAdminWeight() {
return this.adminWeight;
}
@Override
public String toString() {
return new StringBuilder(" srcNodeName: ").append(this.srcNodeName)
.append(" snkNodeName: ").append(this.snkNodeName)
.append(" srcNodeId: ").append(this.srcNodeId)
.append(" snkNodeId: ").append(this.snkNodeId)
.append(" srcPort: ").append(this.srcPort)
.append(" snkPort: ").append(this.snkPort)
.append(" distance: ").append(this.distance)
.append(" cost: ").append(this.cost)
.append(" wavelengthNumber: ").append(this.wavelengthNumber)
.append(" adminWeight: ").append(this.adminWeight).toString();
}
}
{
"opticalSwitches": [
{
"allowed": true,
"latitude": 37.6,
"longitude": 122.3,
"name": "SFO-W10",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:01",
"params": {
"numRegen": 0
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 37.3,
"longitude": 121.9,
"name": "SJC-W10",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:02",
"params": {
"numRegen": 0
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 33.9,
"longitude": 118.4
"name": "LAX-W10",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:03",
"params": {
"numRegen": 0
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 32.8,
"longitude": 117.1,
"name": "SDG-W10",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:04",
"params": {
"numRegen": 3
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 44.8,
"longitude": 93.1,
"name": "MSP-M10",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:05",
"params": {
"numRegen": 3
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 32.8,
"longitude": 97.1,
"name": "DFW-M10",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:06",
"params": {
"numRegen": 3
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 41.8,
"longitude": 120.1,
"name": "CHG-N10",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:07",
"params": {
"numRegen": 3
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 38.8,
"longitude": 77.1,
"name": "IAD-M10",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:08",
"params": {
"numRegen": 3
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 40.8,
"longitude": 73.1,
"name": "JFK-E10",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:09",
"params": {
"numRegen": 0
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 33.8,
"longitude": 84.1,
"name": "ATL-S10",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:0A",
"params": {
"numRegen": 0
},
"type": "Roadm"
}
],
"opticalLinks": [
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:01",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:02",
"params": {
"distKms": 1000,
"nodeName1": "SFO-W10",
"nodeName2": "SJC-W10",
"numWaves": 80,
"port1": 10,
"port2": 10
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:02",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:03",
"params": {
"distKms": 1000,
"nodeName1": "SJC-W10",
"nodeName2": "LAX-W10",
"numWaves": 80,
"port1": 20,
"port2": 10
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:03",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:04",
"params": {
"distKms": 1000,
"nodeName1": "LAX-W10",
"nodeName2": "SDG-W10",
"numWaves": 80,
"port1": 30,
"port2": 10
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:02",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:05",
"params": {
"distKms": 4000,
"nodeName1": "SJC-W10",
"nodeName2": "MSP-M10",
"numWaves": 80,
"port1": 20,
"port2": 10
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:03",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:06",
"params": {
"distKms": 5000,
"nodeName1": "LAX-W10",
"nodeName2": "DFW-M10",
"numWaves": 80,
"port1": 20,
"port2": 10
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:05",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:06",
"params": {
"distKms": 3000,
"nodeName1": "MSP-M10",
"nodeName2": "DFW-M10",
"numWaves": 80,
"port1": 30,
"port2": 20
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:05",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:07",
"params": {
"distKms": 3000,
"nodeName1": "MSP-M10",
"nodeName2": "CHG-N10",
"numWaves": 80,
"port1": 20,
"port2": 21
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:06",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:08",
"params": {
"distKms": 4000,
"nodeName1": "DFW-M10",
"nodeName2": "IAD-M10",
"numWaves": 80,
"port1": 30,
"port2": 10
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:07",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:08",
"params": {
"distKms": 4000,
"nodeName1": "CHG-M10",
"nodeName2": "IAD-M10",
"numWaves": 80,
"port1": 30,
"port2": 20
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:07",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:09",
"params": {
"distKms": 5000,
"nodeName1": "CHG-M10",
"nodeName2": "JFK-E10",
"numWaves": 80,
"port1": 20,
"port2": 10
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:08",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:0A",
"params": {
"distKms": 3000,
"nodeName1": "IAD-M10",
"nodeName2": "ATL-S10",
"numWaves": 80,
"port1": 30,
"port2": 10
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:09",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:0A",
"params": {
"distKms": 4000,
"nodeName1": "JFK-E10",
"nodeName2": "ATL-S10",
"numWaves": 80,
"port1": 20,
"port2": 20
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:00:01",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:01",
"params": {
"nodeName1": "SFO-R10",
"nodeName2": "SFO-W10",
"port1": 10,
"port2": 1
},
"type": "pktOptLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:00:03",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:03",
"params": {
"nodeName1": "LAX-R10",
"nodeName2": "LAX-W10",
"port1": 10,
"port2": 1
},
"type": "pktOptLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:00:04",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:04",
"params": {
"nodeName1": "SDG-R10",
"nodeName2": "SDG-W10",
"port1": 10,
"port2": 1
},
"type": "pktOptLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:00:07",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:07",
"params": {
"nodeName1": "CHG-R10",
"nodeName2": "CHG-W10",
"port1": 10,
"port2": 1
},
"type": "pktOptLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:00:09",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:09",
"params": {
"nodeName1": "JFK-R10",
"nodeName2": "JFK-W10",
"port1": 10,
"port2": 1
},
"type": "pktOptLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:00:0A",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:0A",
"params": {
"nodeName1": "ATL-R10",
"nodeName2": "ATL-W10",
"port1": 10,
"port2": 1
},
"type": "pktOptLink"
},
]
}
{
"opticalSwitches": [
{
"allowed": true,
"latitude": 37.6,
"longitude": 122.3,
"name": "ROADM1",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:01",
"params": {
"numRegen": 0
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 37.3,
"longitude": 121.9,
"name": "ROADM2",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:02",
"params": {
"numRegen": 0
},
"type": "Roadm"
},
{
"allowed": true,
"latitude": 33.9,
"longitude": 118.4,
"name": "ROADM3",
"nodeDpid": "00:00:ff:ff:ff:ff:ff:03",
"params": {
"numRegen": 2
},
"type": "Roadm"
}
],
"opticalLinks": [
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:01",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:03",
"params": {
"distKms": 1000,
"nodeName1": "ROADM1",
"nodeName2": "ROADM3",
"numWaves": 80,
"port1": 10,
"port2": 30
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:03",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:02",
"params": {
"distKms": 2000,
"nodeName1": "ROADM3",
"nodeName2": "ROADM2",
"numWaves": 80,
"port1": 31,
"port2": 20
},
"type": "wdmLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:00:01",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:01",
"params": {
"nodeName1": "ROUTER1",
"nodeName2": "ROADM1",
"bandWidth": 100000,
"port1": 10,
"port2": 11
},
"type": "pktOptLink"
},
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:00:02",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:02",
"params": {
"nodeName1": "ROUTER2",
"nodeName2": "ROADM2",
"bandWidth": 100000,
"port1": 10,
"port2": 21
},
"type": "pktOptLink"
}
]
}
......@@ -25,6 +25,8 @@
<module>proxyarp</module>
<module>config</module>
<module>sdnip</module>
<module>calendar</module>
<module>optical</module>
</modules>
<properties>
......
......@@ -36,6 +36,36 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-thirdparty</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-misc</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
......
......@@ -18,11 +18,11 @@ import com.google.common.collect.Sets;
/**
* Provides IntefaceService using PortAddresses data from the HostService.
*/
public class HostServiceBasedInterfaceService implements InterfaceService {
public class HostToInterfaceAdaptor implements InterfaceService {
private final HostService hostService;
public HostServiceBasedInterfaceService(HostService hostService) {
public HostToInterfaceAdaptor(HostService hostService) {
this.hostService = checkNotNull(hostService);
}
......
......@@ -25,10 +25,10 @@ import org.slf4j.LoggerFactory;
/**
* Manages the connectivity requirements between peers.
*/
public class PeerConnectivity {
public class PeerConnectivityManager {
private static final Logger log = LoggerFactory.getLogger(
PeerConnectivity.class);
PeerConnectivityManager.class);
// TODO these shouldn't be defined here
private static final short BGP_PORT = 179;
......@@ -41,7 +41,7 @@ public class PeerConnectivity {
// TODO this sucks.
private int intentId = 0;
public PeerConnectivity(SdnIpConfigService configInfoService,
public PeerConnectivityManager(SdnIpConfigService configInfoService,
InterfaceService interfaceService, IntentService intentService) {
this.configInfoService = configInfoService;
this.interfaceService = interfaceService;
......@@ -126,8 +126,8 @@ public class PeerConnectivity {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_TCP)
.matchIPSrc(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPSrc(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
.matchTcpDst(BGP_PORT)
.build();
......@@ -147,8 +147,8 @@ public class PeerConnectivity {
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_TCP)
.matchIPSrc(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPSrc(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
.matchTcpSrc(BGP_PORT)
.build();
......@@ -165,8 +165,8 @@ public class PeerConnectivity {
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_TCP)
.matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
.matchTcpDst(BGP_PORT)
.build();
......@@ -183,8 +183,8 @@ public class PeerConnectivity {
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_TCP)
.matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
.matchTcpSrc(BGP_PORT)
.build();
......@@ -251,8 +251,8 @@ public class PeerConnectivity {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_ICMP)
.matchIPSrc(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPSrc(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
......@@ -269,8 +269,8 @@ public class PeerConnectivity {
selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_ICMP)
.matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdAddress.toRealInt(), IPV4_BIT_LENGTH))
.matchIPSrc(IpPrefix.valueOf(bgpdPeerAddress.toInt(), IPV4_BIT_LENGTH))
.matchIPDst(IpPrefix.valueOf(bgpdAddress.toInt(), IPV4_BIT_LENGTH))
.build();
PointToPointIntent reversedIntent = new PointToPointIntent(
......
package org.onlab.onos.sdnip;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Objects;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import com.google.common.base.MoreObjects;
/**
* Represents a route entry for an IP prefix.
*/
public class RouteEntry {
private final IpPrefix prefix; // The IP prefix
private final IpAddress nextHop; // Next-hop IP address
/**
* Class constructor.
*
* @param prefix the IP prefix of the route
* @param nextHop the next hop IP address for the route
*/
public RouteEntry(IpPrefix prefix, IpAddress nextHop) {
this.prefix = checkNotNull(prefix);
this.nextHop = checkNotNull(nextHop);
}
/**
* Returns the IP prefix of the route.
*
* @return the IP prefix of the route
*/
public IpPrefix prefix() {
return prefix;
}
/**
* Returns the next hop IP address for the route.
*
* @return the next hop IP address for the route
*/
public IpAddress nextHop() {
return nextHop;
}
/**
* Creates the binary string representation of an IPv4 prefix.
* The string length is equal to the prefix length.
*
* @param ip4Prefix the IPv4 prefix to use
* @return the binary string representation
*/
static String createBinaryString(IpPrefix ip4Prefix) {
if (ip4Prefix.prefixLength() == 0) {
return "";
}
StringBuilder result = new StringBuilder(ip4Prefix.prefixLength());
long value = ip4Prefix.toInt();
for (int i = 0; i < ip4Prefix.prefixLength(); i++) {
long mask = 1 << (IpAddress.MAX_INET_MASK - 1 - i);
result.append(((value & mask) == 0) ? "0" : "1");
}
return result.toString();
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
//
// NOTE: Subclasses are considered as change of identity, hence
// equals() will return false if the class type doesn't match.
//
if (other == null || getClass() != other.getClass()) {
return false;
}
RouteEntry otherRoute = (RouteEntry) other;
return Objects.equals(this.prefix, otherRoute.prefix) &&
Objects.equals(this.nextHop, otherRoute.nextHop);
}
@Override
public int hashCode() {
return Objects.hash(prefix, nextHop);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("prefix", prefix)
.add("nextHop", nextHop)
.toString();
}
}
package org.onlab.onos.sdnip;
/**
* An interface to receive route updates from route providers.
*/
public interface RouteListener {
/**
* Receives a route update from a route provider.
*
* @param routeUpdate the updated route information
*/
public void update(RouteUpdate routeUpdate);
}
package org.onlab.onos.sdnip;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Objects;
import com.google.common.base.MoreObjects;
/**
* Represents a change in routing information.
*/
public class RouteUpdate {
private final Type type; // The route update type
private final RouteEntry routeEntry; // The updated route entry
/**
* Specifies the type of a route update.
* <p/>
* Route updates can either provide updated information for a route, or
* withdraw a previously updated route.
*/
public enum Type {
/**
* The update contains updated route information for a route.
*/
UPDATE,
/**
* The update withdraws the route, meaning any previous information is
* no longer valid.
*/
DELETE
}
/**
* Class constructor.
*
* @param type the type of the route update
* @param routeEntry the route entry with the update
*/
public RouteUpdate(Type type, RouteEntry routeEntry) {
this.type = type;
this.routeEntry = checkNotNull(routeEntry);
}
/**
* Returns the type of the route update.
*
* @return the type of the update
*/
public Type type() {
return type;
}
/**
* Returns the route entry the route update is for.
*
* @return the route entry the route update is for
*/
public RouteEntry routeEntry() {
return routeEntry;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof RouteUpdate)) {
return false;
}
RouteUpdate otherUpdate = (RouteUpdate) other;
return Objects.equals(this.type, otherUpdate.type) &&
Objects.equals(this.routeEntry, otherUpdate.routeEntry);
}
@Override
public int hashCode() {
return Objects.hash(type, routeEntry);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("type", type)
.add("routeEntry", routeEntry)
.toString();
}
}
package org.onlab.onos.sdnip;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;
import org.apache.commons.lang3.tuple.Pair;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Host;
import org.onlab.onos.net.flow.DefaultTrafficSelector;
import org.onlab.onos.net.flow.DefaultTrafficTreatment;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
import org.onlab.onos.net.flow.criteria.Criterion;
import org.onlab.onos.net.flow.criteria.Criterion.Type;
import org.onlab.onos.net.host.HostEvent;
import org.onlab.onos.net.host.HostListener;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.intent.Intent;
import org.onlab.onos.net.intent.IntentId;
import org.onlab.onos.net.intent.IntentService;
import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
import org.onlab.onos.sdnip.config.BgpPeer;
import org.onlab.onos.sdnip.config.Interface;
import org.onlab.onos.sdnip.config.SdnIpConfigService;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.googlecode.concurrenttrees.common.KeyValuePair;
import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
/**
* This class processes BGP route update, translates each update into a intent
* and submits the intent.
*
* TODO: Make it thread-safe.
*/
public class Router implements RouteListener {
private static final Logger log = LoggerFactory.getLogger(Router.class);
// Store all route updates in a radix tree.
// The key in this tree is the binary string of prefix of the route.
private InvertedRadixTree<RouteEntry> bgpRoutes;
// Stores all incoming route updates in a queue.
private BlockingQueue<RouteUpdate> routeUpdates;
// The Ip4Address is the next hop address of each route update.
private SetMultimap<IpAddress, RouteEntry> routesWaitingOnArp;
private ConcurrentHashMap<IpPrefix, MultiPointToSinglePointIntent> pushedRouteIntents;
private IntentService intentService;
//private IProxyArpService proxyArp;
private HostService hostService;
private SdnIpConfigService configInfoService;
private InterfaceService interfaceService;
private ExecutorService bgpUpdatesExecutor;
private ExecutorService bgpIntentsSynchronizerExecutor;
// TODO temporary
private int intentId = Integer.MAX_VALUE / 2;
//
// State to deal with SDN-IP Leader election and pushing Intents
//
private Semaphore intentsSynchronizerSemaphore = new Semaphore(0);
private volatile boolean isElectedLeader = false;
private volatile boolean isActivatedLeader = false;
// For routes announced by local BGP daemon in SDN network,
// the next hop will be 0.0.0.0.
public static final IpAddress LOCAL_NEXT_HOP = IpAddress.valueOf("0.0.0.0");
/**
* Class constructor.
*
* @param intentService the intent service
* @param hostService the host service
* @param configInfoService the configuration service
* @param interfaceService the interface service
*/
public Router(IntentService intentService, HostService hostService,
SdnIpConfigService configInfoService, InterfaceService interfaceService) {
this.intentService = intentService;
this.hostService = hostService;
this.configInfoService = configInfoService;
this.interfaceService = interfaceService;
bgpRoutes = new ConcurrentInvertedRadixTree<>(
new DefaultByteArrayNodeFactory());
routeUpdates = new LinkedBlockingQueue<>();
routesWaitingOnArp = Multimaps.synchronizedSetMultimap(
HashMultimap.<IpAddress, RouteEntry>create());
pushedRouteIntents = new ConcurrentHashMap<>();
bgpUpdatesExecutor = Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder().setNameFormat("bgp-updates-%d").build());
bgpIntentsSynchronizerExecutor = Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder()
.setNameFormat("bgp-intents-synchronizer-%d").build());
this.hostService.addListener(new InternalHostListener());
}
/**
* Starts the Router.
*/
public void start() {
// TODO hack to enable SDN-IP now for testing
isElectedLeader = true;
isActivatedLeader = true;
bgpUpdatesExecutor.execute(new Runnable() {
@Override
public void run() {
doUpdatesThread();
}
});
bgpIntentsSynchronizerExecutor.execute(new Runnable() {
@Override
public void run() {
doIntentSynchronizationThread();
}
});
}
//@Override TODO hook this up to something
public void leaderChanged(boolean isLeader) {
log.debug("Leader changed: {}", isLeader);
if (!isLeader) {
this.isElectedLeader = false;
this.isActivatedLeader = false;
return; // Nothing to do
}
this.isActivatedLeader = false;
this.isElectedLeader = true;
//
// Tell the Intents Synchronizer thread to start the synchronization
//
intentsSynchronizerSemaphore.release();
}
@Override
public void update(RouteUpdate routeUpdate) {
log.debug("Received new route Update: {}", routeUpdate);
try {
routeUpdates.put(routeUpdate);
} catch (InterruptedException e) {
log.debug("Interrupted while putting on routeUpdates queue", e);
Thread.currentThread().interrupt();
}
}
/**
* Thread for Intent Synchronization.
*/
private void doIntentSynchronizationThread() {
boolean interrupted = false;
try {
while (!interrupted) {
try {
intentsSynchronizerSemaphore.acquire();
//
// Drain all permits, because a single synchronization is
// sufficient.
//
intentsSynchronizerSemaphore.drainPermits();
} catch (InterruptedException e) {
log.debug("Interrupted while waiting to become " +
"Intent Synchronization leader");
interrupted = true;
break;
}
syncIntents();
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
/**
* Thread for handling route updates.
*/
private void doUpdatesThread() {
boolean interrupted = false;
try {
while (!interrupted) {
try {
RouteUpdate update = routeUpdates.take();
switch (update.type()) {
case UPDATE:
processRouteAdd(update.routeEntry());
break;
case DELETE:
processRouteDelete(update.routeEntry());
break;
default:
log.error("Unknown update Type: {}", update.type());
break;
}
} catch (InterruptedException e) {
log.debug("Interrupted while taking from updates queue", e);
interrupted = true;
} catch (Exception e) {
log.debug("exception", e);
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
/**
* Performs Intents Synchronization between the internally stored Route
* Intents and the installed Route Intents.
*/
private void syncIntents() {
synchronized (this) {
if (!isElectedLeader) {
return; // Nothing to do: not the leader anymore
}
log.debug("Syncing SDN-IP Route Intents...");
Map<IpPrefix, MultiPointToSinglePointIntent> fetchedIntents =
new HashMap<>();
//
// Fetch all intents, and classify the Multi-Point-to-Point Intents
// based on the matching prefix.
//
for (Intent intent : intentService.getIntents()) {
//
// TODO: Ignore all intents that are not installed by
// the SDN-IP application.
//
if (!(intent instanceof MultiPointToSinglePointIntent)) {
continue;
}
MultiPointToSinglePointIntent mp2pIntent =
(MultiPointToSinglePointIntent) intent;
/*Match match = mp2pIntent.getMatch();
if (!(match instanceof PacketMatch)) {
continue;
}
PacketMatch packetMatch = (PacketMatch) match;
Ip4Prefix prefix = packetMatch.getDstIpAddress();
if (prefix == null) {
continue;
}
fetchedIntents.put(prefix, mp2pIntent);*/
for (Criterion criterion : mp2pIntent.selector().criteria()) {
if (criterion.type() == Type.IPV4_DST) {
IPCriterion ipCriterion = (IPCriterion) criterion;
fetchedIntents.put(ipCriterion.ip(), mp2pIntent);
}
}
}
//
// Compare for each prefix the local IN-MEMORY Intents with the
// FETCHED Intents:
// - If the IN-MEMORY Intent is same as the FETCHED Intent, store
// the FETCHED Intent in the local memory (i.e., override the
// IN-MEMORY Intent) to preserve the original Intent ID
// - if the IN-MEMORY Intent is not same as the FETCHED Intent,
// delete the FETCHED Intent, and push/install the IN-MEMORY
// Intent.
// - If there is an IN-MEMORY Intent for a prefix, but no FETCHED
// Intent for same prefix, then push/install the IN-MEMORY
// Intent.
// - If there is a FETCHED Intent for a prefix, but no IN-MEMORY
// Intent for same prefix, then delete/withdraw the FETCHED
// Intent.
//
Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
storeInMemoryIntents = new LinkedList<>();
Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
addIntents = new LinkedList<>();
Collection<Pair<IpPrefix, MultiPointToSinglePointIntent>>
deleteIntents = new LinkedList<>();
for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
pushedRouteIntents.entrySet()) {
IpPrefix prefix = entry.getKey();
MultiPointToSinglePointIntent inMemoryIntent =
entry.getValue();
MultiPointToSinglePointIntent fetchedIntent =
fetchedIntents.get(prefix);
if (fetchedIntent == null) {
//
// No FETCHED Intent for same prefix: push the IN-MEMORY
// Intent.
//
addIntents.add(Pair.of(prefix, inMemoryIntent));
continue;
}
//
// If IN-MEMORY Intent is same as the FETCHED Intent,
// store the FETCHED Intent in the local memory.
//
if (compareMultiPointToSinglePointIntents(inMemoryIntent,
fetchedIntent)) {
storeInMemoryIntents.add(Pair.of(prefix, fetchedIntent));
} else {
//
// The IN-MEMORY Intent is not same as the FETCHED Intent,
// hence delete the FETCHED Intent, and install the
// IN-MEMORY Intent.
//
deleteIntents.add(Pair.of(prefix, fetchedIntent));
addIntents.add(Pair.of(prefix, inMemoryIntent));
}
fetchedIntents.remove(prefix);
}
//
// Any remaining FETCHED Intents have to be deleted/withdrawn
//
for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
fetchedIntents.entrySet()) {
IpPrefix prefix = entry.getKey();
MultiPointToSinglePointIntent fetchedIntent = entry.getValue();
deleteIntents.add(Pair.of(prefix, fetchedIntent));
}
//
// Perform the actions:
// 1. Store in memory fetched intents that are same. Can be done
// even if we are not the leader anymore
// 2. Delete intents: check if the leader before each operation
// 3. Add intents: check if the leader before each operation
//
for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
storeInMemoryIntents) {
IpPrefix prefix = pair.getLeft();
MultiPointToSinglePointIntent intent = pair.getRight();
log.debug("Intent synchronization: updating in-memory " +
"Intent for prefix: {}", prefix);
pushedRouteIntents.put(prefix, intent);
}
//
isActivatedLeader = true; // Allow push of Intents
for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
deleteIntents) {
IpPrefix prefix = pair.getLeft();
MultiPointToSinglePointIntent intent = pair.getRight();
if (!isElectedLeader) {
isActivatedLeader = false;
return;
}
log.debug("Intent synchronization: deleting Intent for " +
"prefix: {}", prefix);
intentService.withdraw(intent);
}
//
for (Pair<IpPrefix, MultiPointToSinglePointIntent> pair :
addIntents) {
IpPrefix prefix = pair.getLeft();
MultiPointToSinglePointIntent intent = pair.getRight();
if (!isElectedLeader) {
isActivatedLeader = false;
return;
}
log.debug("Intent synchronization: adding Intent for " +
"prefix: {}", prefix);
intentService.submit(intent);
}
if (!isElectedLeader) {
isActivatedLeader = false;
}
log.debug("Syncing SDN-IP routes completed.");
}
}
/**
* Compares two Multi-point to Single Point Intents whether they represent
* same logical intention.
*
* @param intent1 the first Intent to compare
* @param intent2 the second Intent to compare
* @return true if both Intents represent same logical intention, otherwise
* false
*/
private boolean compareMultiPointToSinglePointIntents(
MultiPointToSinglePointIntent intent1,
MultiPointToSinglePointIntent intent2) {
/*Match match1 = intent1.getMatch();
Match match2 = intent2.getMatch();
Action action1 = intent1.getAction();
Action action2 = intent2.getAction();
Set<SwitchPort> ingressPorts1 = intent1.getIngressPorts();
Set<SwitchPort> ingressPorts2 = intent2.getIngressPorts();
SwitchPort egressPort1 = intent1.getEgressPort();
SwitchPort egressPort2 = intent2.getEgressPort();
return Objects.equal(match1, match2) &&
Objects.equal(action1, action2) &&
Objects.equal(egressPort1, egressPort2) &&
Objects.equal(ingressPorts1, ingressPorts2);*/
return Objects.equal(intent1.selector(), intent2.selector()) &&
Objects.equal(intent1.treatment(), intent2.treatment()) &&
Objects.equal(intent1.ingressPoints(), intent2.ingressPoints()) &&
Objects.equal(intent1.egressPoint(), intent2.egressPoint());
}
/**
* Processes adding a route entry.
* <p/>
* Put new route entry into the radix tree. If there was an existing
* next hop for this prefix, but the next hop was different, then execute
* deleting old route entry. If the next hop is the SDN domain, we do not
* handle it at the moment. Otherwise, execute adding a route.
*
* @param routeEntry the route entry to add
*/
protected void processRouteAdd(RouteEntry routeEntry) {
synchronized (this) {
log.debug("Processing route add: {}", routeEntry);
IpPrefix prefix = routeEntry.prefix();
IpAddress nextHop = null;
RouteEntry foundRouteEntry =
bgpRoutes.put(RouteEntry.createBinaryString(prefix),
routeEntry);
if (foundRouteEntry != null) {
nextHop = foundRouteEntry.nextHop();
}
if (nextHop != null && !nextHop.equals(routeEntry.nextHop())) {
// There was an existing nexthop for this prefix. This update
// supersedes that, so we need to remove the old flows for this
// prefix from the switches
executeRouteDelete(routeEntry);
}
if (nextHop != null && nextHop.equals(routeEntry.nextHop())) {
return;
}
if (routeEntry.nextHop().equals(LOCAL_NEXT_HOP)) {
// Route originated by SDN domain
// We don't handle these at the moment
log.debug("Own route {} to {}",
routeEntry.prefix(), routeEntry.nextHop());
return;
}
executeRouteAdd(routeEntry);
}
}
/**
* Executes adding a route entry.
* <p/>
* Find out the egress Interface and MAC address of next hop router for
* this route entry. If the MAC address can not be found in ARP cache,
* then this prefix will be put in routesWaitingOnArp queue. Otherwise,
* new route intent will be created and installed.
*
* @param routeEntry the route entry to add
*/
private void executeRouteAdd(RouteEntry routeEntry) {
log.debug("Executing route add: {}", routeEntry);
// See if we know the MAC address of the next hop
//MacAddress nextHopMacAddress =
//proxyArp.getMacAddress(routeEntry.getNextHop());
MacAddress nextHopMacAddress = null;
Set<Host> hosts = hostService.getHostsByIp(
routeEntry.nextHop().toPrefix());
if (!hosts.isEmpty()) {
// TODO how to handle if multiple hosts are returned?
nextHopMacAddress = hosts.iterator().next().mac();
}
if (nextHopMacAddress == null) {
routesWaitingOnArp.put(routeEntry.nextHop(), routeEntry);
//proxyArp.sendArpRequest(routeEntry.getNextHop(), this, true);
// TODO maybe just do this for every prefix anyway
hostService.startMonitoringIp(routeEntry.nextHop());
return;
}
addRouteIntentToNextHop(routeEntry.prefix(),
routeEntry.nextHop(),
nextHopMacAddress);
}
/**
* Adds a route intent given a prefix and a next hop IP address. This
* method will find the egress interface for the intent.
*
* @param prefix IP prefix of the route to add
* @param nextHopIpAddress IP address of the next hop
* @param nextHopMacAddress MAC address of the next hop
*/
private void addRouteIntentToNextHop(IpPrefix prefix,
IpAddress nextHopIpAddress,
MacAddress nextHopMacAddress) {
// Find the attachment point (egress interface) of the next hop
Interface egressInterface;
if (configInfoService.getBgpPeers().containsKey(nextHopIpAddress)) {
// Route to a peer
log.debug("Route to peer {}", nextHopIpAddress);
BgpPeer peer =
configInfoService.getBgpPeers().get(nextHopIpAddress);
egressInterface =
interfaceService.getInterface(peer.connectPoint());
} else {
// Route to non-peer
log.debug("Route to non-peer {}", nextHopIpAddress);
egressInterface =
interfaceService.getMatchingInterface(nextHopIpAddress);
if (egressInterface == null) {
log.warn("No outgoing interface found for {}",
nextHopIpAddress);
return;
}
}
doAddRouteIntent(prefix, egressInterface, nextHopMacAddress);
}
/**
* Installs a route intent for a prefix.
* <p/>
* Intent will match dst IP prefix and rewrite dst MAC address at all other
* border switches, then forward packets according to dst MAC address.
*
* @param prefix IP prefix from route
* @param egressInterface egress Interface connected to next hop router
* @param nextHopMacAddress MAC address of next hop router
*/
private void doAddRouteIntent(IpPrefix prefix, Interface egressInterface,
MacAddress nextHopMacAddress) {
log.debug("Adding intent for prefix {}, next hop mac {}",
prefix, nextHopMacAddress);
MultiPointToSinglePointIntent pushedIntent =
pushedRouteIntents.get(prefix);
// Just for testing.
if (pushedIntent != null) {
log.error("There should not be a pushed intent: {}", pushedIntent);
}
ConnectPoint egressPort = egressInterface.connectPoint();
Set<ConnectPoint> ingressPorts = new HashSet<>();
for (Interface intf : interfaceService.getInterfaces()) {
if (!intf.equals(egressInterface)) {
ConnectPoint srcPort = intf.connectPoint();
ingressPorts.add(srcPort);
}
}
// Match the destination IP prefix at the first hop
//PacketMatchBuilder builder = new PacketMatchBuilder();
//builder.setEtherType(Ethernet.TYPE_IPV4).setDstIpNet(prefix);
//PacketMatch packetMatch = builder.build();
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(prefix)
.build();
// Rewrite the destination MAC address
//ModifyDstMacAction modifyDstMacAction =
//new ModifyDstMacAction(nextHopMacAddress);
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setEthDst(nextHopMacAddress)
.build();
MultiPointToSinglePointIntent intent =
new MultiPointToSinglePointIntent(nextIntentId(),
selector, treatment, ingressPorts, egressPort);
if (isElectedLeader && isActivatedLeader) {
log.debug("Intent installation: adding Intent for prefix: {}",
prefix);
intentService.submit(intent);
}
// Maintain the Intent
pushedRouteIntents.put(prefix, intent);
}
/**
* Executes deleting a route entry.
* <p/>
* Removes prefix from radix tree, and if successful, then try to delete
* the related intent.
*
* @param routeEntry the route entry to delete
*/
protected void processRouteDelete(RouteEntry routeEntry) {
synchronized (this) {
log.debug("Processing route delete: {}", routeEntry);
IpPrefix prefix = routeEntry.prefix();
// TODO check the change of logic here - remove doesn't check that
// the route entry was what we expected (and we can't do this
// concurrently)
if (bgpRoutes.remove(RouteEntry.createBinaryString(prefix))) {
//
// Only delete flows if an entry was actually removed from the
// tree. If no entry was removed, the <prefix, nexthop> wasn't
// there so it's probably already been removed and we don't
// need to do anything.
//
executeRouteDelete(routeEntry);
}
routesWaitingOnArp.remove(routeEntry.nextHop(), routeEntry);
// TODO cancel the request in the ARP manager as well
}
}
/**
* Executed deleting a route entry.
*
* @param routeEntry the route entry to delete
*/
private void executeRouteDelete(RouteEntry routeEntry) {
log.debug("Executing route delete: {}", routeEntry);
IpPrefix prefix = routeEntry.prefix();
MultiPointToSinglePointIntent intent =
pushedRouteIntents.remove(prefix);
if (intent == null) {
log.debug("There is no intent in pushedRouteIntents to delete " +
"for prefix: {}", prefix);
} else {
if (isElectedLeader && isActivatedLeader) {
log.debug("Intent installation: deleting Intent for prefix: {}",
prefix);
intentService.withdraw(intent);
}
}
}
/**
* This method handles the prefixes which are waiting for ARP replies for
* MAC addresses of next hops.
*
* @param ipAddress next hop router IP address, for which we sent ARP
* request out
* @param macAddress MAC address which is relative to the ipAddress
*/
//@Override
// TODO change name
public void arpResponse(IpAddress ipAddress, MacAddress macAddress) {
log.debug("Received ARP response: {} => {}", ipAddress, macAddress);
// We synchronize on this to prevent changes to the radix tree
// while we're pushing intents. If the tree changes, the
// tree and intents could get out of sync.
synchronized (this) {
Set<RouteEntry> routesToPush =
routesWaitingOnArp.removeAll(ipAddress);
for (RouteEntry routeEntry : routesToPush) {
// These will always be adds
IpPrefix prefix = routeEntry.prefix();
String binaryString = RouteEntry.createBinaryString(prefix);
RouteEntry foundRouteEntry =
bgpRoutes.getValueForExactKey(binaryString);
if (foundRouteEntry != null &&
foundRouteEntry.nextHop().equals(routeEntry.nextHop())) {
log.debug("Pushing prefix {} next hop {}",
routeEntry.prefix(), routeEntry.nextHop());
// We only push prefix flows if the prefix is still in the
// radix tree and the next hop is the same as our
// update.
// The prefix could have been removed while we were waiting
// for the ARP, or the next hop could have changed.
addRouteIntentToNextHop(prefix, ipAddress, macAddress);
} else {
log.debug("Received ARP response, but {}/{} is no longer in"
+ " the radix tree", routeEntry.prefix(),
routeEntry.nextHop());
}
}
}
}
/**
* Gets the SDN-IP routes.
*
* @return the SDN-IP routes
*/
public Collection<RouteEntry> getRoutes() {
Iterator<KeyValuePair<RouteEntry>> it =
bgpRoutes.getKeyValuePairsForKeysStartingWith("").iterator();
List<RouteEntry> routes = new LinkedList<>();
while (it.hasNext()) {
KeyValuePair<RouteEntry> entry = it.next();
routes.add(entry.getValue());
}
return routes;
}
/**
* Generates a new unique intent ID.
*
* @return the new intent ID.
*/
private IntentId nextIntentId() {
return new IntentId(intentId++);
}
/**
* Listener for host events.
*/
class InternalHostListener implements HostListener {
@Override
public void event(HostEvent event) {
if (event.type() == HostEvent.Type.HOST_ADDED ||
event.type() == HostEvent.Type.HOST_UPDATED) {
Host host = event.subject();
for (IpPrefix ip : host.ipAddresses()) {
arpResponse(ip.toIpAddress(), host.mac());
}
}
}
}
}
......@@ -2,21 +2,30 @@ package org.onlab.onos.sdnip;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.Collection;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.intent.IntentService;
import org.onlab.onos.sdnip.RouteUpdate.Type;
import org.onlab.onos.sdnip.bgp.BgpRouteEntry;
import org.onlab.onos.sdnip.bgp.BgpSessionManager;
import org.onlab.onos.sdnip.config.SdnIpConfigReader;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.slf4j.Logger;
/**
* Placeholder SDN-IP component.
* Component for the SDN-IP peering application.
*/
@Component(immediate = true)
public class SdnIp {
@Service
public class SdnIp implements SdnIpService {
private final Logger log = getLogger(getClass());
......@@ -27,7 +36,9 @@ public class SdnIp {
protected HostService hostService;
private SdnIpConfigReader config;
private PeerConnectivity peerConnectivity;
private PeerConnectivityManager peerConnectivity;
private Router router;
private BgpSessionManager bgpSessionManager;
@Activate
protected void activate() {
......@@ -36,15 +47,40 @@ public class SdnIp {
config = new SdnIpConfigReader();
config.init();
InterfaceService interfaceService = new HostServiceBasedInterfaceService(hostService);
InterfaceService interfaceService = new HostToInterfaceAdaptor(hostService);
peerConnectivity = new PeerConnectivity(config, interfaceService, intentService);
peerConnectivity = new PeerConnectivityManager(config, interfaceService, intentService);
peerConnectivity.start();
router = new Router(intentService, hostService, config, interfaceService);
router.start();
bgpSessionManager = new BgpSessionManager(router);
bgpSessionManager.startUp(2000); // TODO
// TODO need to disable link discovery on external ports
router.update(new RouteUpdate(Type.UPDATE, new RouteEntry(
IpPrefix.valueOf("172.16.20.0/24"),
IpAddress.valueOf("192.168.10.1"))));
}
@Deactivate
protected void deactivate() {
log.info("Stopped");
}
@Override
public Collection<BgpRouteEntry> getBgpRoutes() {
return bgpSessionManager.getBgpRoutes();
}
@Override
public Collection<RouteEntry> getRoutes() {
return router.getRoutes();
}
static String dpidToUri(String dpid) {
return "of:" + dpid.replace(":", "");
}
}
......
package org.onlab.onos.sdnip;
import java.util.Collection;
import org.onlab.onos.sdnip.bgp.BgpRouteEntry;
/**
* Service interface exported by SDN-IP.
*/
public interface SdnIpService {
/**
* Gets the BGP routes.
*
* @return the BGP routes
*/
public Collection<BgpRouteEntry> getBgpRoutes();
/**
* Gets all the routes known to SDN-IP.
*
* @return the SDN-IP routes
*/
public Collection<RouteEntry> getRoutes();
}
package org.onlab.onos.sdnip.bgp;
/**
* BGP related constants.
*/
public final class BgpConstants {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private BgpConstants() {
}
/** BGP port number (RFC 4271). */
public static final int BGP_PORT = 179;
/** BGP version. */
public static final int BGP_VERSION = 4;
/** BGP OPEN message type. */
public static final int BGP_TYPE_OPEN = 1;
/** BGP UPDATE message type. */
public static final int BGP_TYPE_UPDATE = 2;
/** BGP NOTIFICATION message type. */
public static final int BGP_TYPE_NOTIFICATION = 3;
/** BGP KEEPALIVE message type. */
public static final int BGP_TYPE_KEEPALIVE = 4;
/** BGP Header Marker field length. */
public static final int BGP_HEADER_MARKER_LENGTH = 16;
/** BGP Header length. */
public static final int BGP_HEADER_LENGTH = 19;
/** BGP message maximum length. */
public static final int BGP_MESSAGE_MAX_LENGTH = 4096;
/** BGP OPEN message minimum length (BGP Header included). */
public static final int BGP_OPEN_MIN_LENGTH = 29;
/** BGP UPDATE message minimum length (BGP Header included). */
public static final int BGP_UPDATE_MIN_LENGTH = 23;
/** BGP NOTIFICATION message minimum length (BGP Header included). */
public static final int BGP_NOTIFICATION_MIN_LENGTH = 21;
/** BGP KEEPALIVE message expected length (BGP Header included). */
public static final int BGP_KEEPALIVE_EXPECTED_LENGTH = 19;
/** BGP KEEPALIVE messages transmitted per Hold interval. */
public static final int BGP_KEEPALIVE_PER_HOLD_INTERVAL = 3;
/** BGP KEEPALIVE messages minimum Holdtime (in seconds). */
public static final int BGP_KEEPALIVE_MIN_HOLDTIME = 3;
/** BGP KEEPALIVE messages minimum transmission interval (in seconds). */
public static final int BGP_KEEPALIVE_MIN_INTERVAL = 1;
/** BGP AS 0 (zero) value. See draft-ietf-idr-as0-06.txt Internet Draft. */
public static final long BGP_AS_0 = 0;
/**
* BGP UPDATE related constants.
*/
public static final class Update {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private Update() {
}
/**
* BGP UPDATE: ORIGIN related constants.
*/
public static final class Origin {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private Origin() {
}
/** BGP UPDATE Attributes Type Code ORIGIN. */
public static final int TYPE = 1;
/** BGP UPDATE Attributes Type Code ORIGIN length. */
public static final int LENGTH = 1;
/** BGP UPDATE ORIGIN: IGP. */
public static final int IGP = 0;
/** BGP UPDATE ORIGIN: EGP. */
public static final int EGP = 1;
/** BGP UPDATE ORIGIN: INCOMPLETE. */
public static final int INCOMPLETE = 2;
}
/**
* BGP UPDATE: AS_PATH related constants.
*/
public static final class AsPath {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private AsPath() {
}
/** BGP UPDATE Attributes Type Code AS_PATH. */
public static final int TYPE = 2;
/** BGP UPDATE AS_PATH Type: AS_SET. */
public static final int AS_SET = 1;
/** BGP UPDATE AS_PATH Type: AS_SEQUENCE. */
public static final int AS_SEQUENCE = 2;
}
/**
* BGP UPDATE: NEXT_HOP related constants.
*/
public static final class NextHop {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private NextHop() {
}
/** BGP UPDATE Attributes Type Code NEXT_HOP. */
public static final int TYPE = 3;
/** BGP UPDATE Attributes Type Code NEXT_HOP length. */
public static final int LENGTH = 4;
}
/**
* BGP UPDATE: MULTI_EXIT_DISC related constants.
*/
public static final class MultiExitDisc {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private MultiExitDisc() {
}
/** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC. */
public static final int TYPE = 4;
/** BGP UPDATE Attributes Type Code MULTI_EXIT_DISC length. */
public static final int LENGTH = 4;
/** BGP UPDATE Attributes lowest MULTI_EXIT_DISC value. */
public static final int LOWEST_MULTI_EXIT_DISC = 0;
}
/**
* BGP UPDATE: LOCAL_PREF related constants.
*/
public static final class LocalPref {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private LocalPref() {
}
/** BGP UPDATE Attributes Type Code LOCAL_PREF. */
public static final int TYPE = 5;
/** BGP UPDATE Attributes Type Code LOCAL_PREF length. */
public static final int LENGTH = 4;
}
/**
* BGP UPDATE: ATOMIC_AGGREGATE related constants.
*/
public static final class AtomicAggregate {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private AtomicAggregate() {
}
/** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE. */
public static final int TYPE = 6;
/** BGP UPDATE Attributes Type Code ATOMIC_AGGREGATE length. */
public static final int LENGTH = 0;
}
/**
* BGP UPDATE: AGGREGATOR related constants.
*/
public static final class Aggregator {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private Aggregator() {
}
/** BGP UPDATE Attributes Type Code AGGREGATOR. */
public static final int TYPE = 7;
/** BGP UPDATE Attributes Type Code AGGREGATOR length. */
public static final int LENGTH = 6;
}
}
/**
* BGP NOTIFICATION related constants.
*/
public static final class Notifications {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private Notifications() {
}
/**
* BGP NOTIFICATION: Message Header Error constants.
*/
public static final class MessageHeaderError {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private MessageHeaderError() {
}
/** Message Header Error code. */
public static final int ERROR_CODE = 1;
/** Message Header Error subcode: Connection Not Synchronized. */
public static final int CONNECTION_NOT_SYNCHRONIZED = 1;
/** Message Header Error subcode: Bad Message Length. */
public static final int BAD_MESSAGE_LENGTH = 2;
/** Message Header Error subcode: Bad Message Type. */
public static final int BAD_MESSAGE_TYPE = 3;
}
/**
* BGP NOTIFICATION: OPEN Message Error constants.
*/
public static final class OpenMessageError {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private OpenMessageError() {
}
/** OPEN Message Error code. */
public static final int ERROR_CODE = 2;
/** OPEN Message Error subcode: Unsupported Version Number. */
public static final int UNSUPPORTED_VERSION_NUMBER = 1;
/** OPEN Message Error subcode: Bad PEER AS. */
public static final int BAD_PEER_AS = 2;
/** OPEN Message Error subcode: Unacceptable Hold Time. */
public static final int UNACCEPTABLE_HOLD_TIME = 6;
}
/**
* BGP NOTIFICATION: UPDATE Message Error constants.
*/
public static final class UpdateMessageError {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private UpdateMessageError() {
}
/** UPDATE Message Error code. */
public static final int ERROR_CODE = 3;
/** UPDATE Message Error subcode: Malformed Attribute List. */
public static final int MALFORMED_ATTRIBUTE_LIST = 1;
/** UPDATE Message Error subcode: Unrecognized Well-known Attribute. */
public static final int UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE = 2;
/** UPDATE Message Error subcode: Missing Well-known Attribute. */
public static final int MISSING_WELL_KNOWN_ATTRIBUTE = 3;
/** UPDATE Message Error subcode: Attribute Flags Error. */
public static final int ATTRIBUTE_FLAGS_ERROR = 4;
/** UPDATE Message Error subcode: Attribute Length Error. */
public static final int ATTRIBUTE_LENGTH_ERROR = 5;
/** UPDATE Message Error subcode: Invalid ORIGIN Attribute. */
public static final int INVALID_ORIGIN_ATTRIBUTE = 6;
/** UPDATE Message Error subcode: Invalid NEXT_HOP Attribute. */
public static final int INVALID_NEXT_HOP_ATTRIBUTE = 8;
/** UPDATE Message Error subcode: Optional Attribute Error. Unused. */
public static final int OPTIONAL_ATTRIBUTE_ERROR = 9;
/** UPDATE Message Error subcode: Invalid Network Field. */
public static final int INVALID_NETWORK_FIELD = 10;
/** UPDATE Message Error subcode: Malformed AS_PATH. */
public static final int MALFORMED_AS_PATH = 11;
}
/**
* BGP NOTIFICATION: Hold Timer Expired constants.
*/
public static final class HoldTimerExpired {
/**
* Default constructor.
* <p>
* The constructor is private to prevent creating an instance of
* this utility class.
*/
private HoldTimerExpired() {
}
/** Hold Timer Expired code. */
public static final int ERROR_CODE = 4;
}
/** BGP NOTIFICATION message Error subcode: Unspecific. */
public static final int ERROR_SUBCODE_UNSPECIFIC = 0;
}
}
package org.onlab.onos.sdnip.bgp;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class for handling the decoding of the BGP messages.
*/
class BgpFrameDecoder extends FrameDecoder {
private static final Logger log =
LoggerFactory.getLogger(BgpFrameDecoder.class);
private final BgpSession bgpSession;
/**
* Constructor for a given BGP Session.
*
* @param bgpSession the BGP session state to use.
*/
BgpFrameDecoder(BgpSession bgpSession) {
this.bgpSession = bgpSession;
}
@Override
protected Object decode(ChannelHandlerContext ctx,
Channel channel,
ChannelBuffer buf) throws Exception {
//
// NOTE: If we close the channel during the decoding, we might still
// see some incoming messages while the channel closing is completed.
//
if (bgpSession.isClosed()) {
return null;
}
log.trace("BGP Peer: decode(): remoteAddr = {} localAddr = {} " +
"messageSize = {}",
ctx.getChannel().getRemoteAddress(),
ctx.getChannel().getLocalAddress(),
buf.readableBytes());
// Test for minimum length of the BGP message
if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) {
// No enough data received
return null;
}
//
// Mark the current buffer position in case we haven't received
// the whole message.
//
buf.markReaderIndex();
//
// Read and check the BGP message Marker field: it must be all ones
// (See RFC 4271, Section 4.1)
//
byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH];
buf.readBytes(marker);
for (int i = 0; i < marker.length; i++) {
if (marker[i] != (byte) 0xff) {
log.debug("BGP RX Error: invalid marker {} at position {}",
marker[i], i);
//
// ERROR: Connection Not Synchronized
//
// Send NOTIFICATION and close the connection
int errorCode = MessageHeaderError.ERROR_CODE;
int errorSubcode =
MessageHeaderError.CONNECTION_NOT_SYNCHRONIZED;
ChannelBuffer txMessage =
bgpSession.prepareBgpNotification(errorCode, errorSubcode,
null);
ctx.getChannel().write(txMessage);
bgpSession.closeChannel(ctx);
return null;
}
}
//
// Read and check the BGP message Length field
//
int length = buf.readUnsignedShort();
if ((length < BgpConstants.BGP_HEADER_LENGTH) ||
(length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) {
log.debug("BGP RX Error: invalid Length field {}. " +
"Must be between {} and {}",
length,
BgpConstants.BGP_HEADER_LENGTH,
BgpConstants.BGP_MESSAGE_MAX_LENGTH);
//
// ERROR: Bad Message Length
//
// Send NOTIFICATION and close the connection
ChannelBuffer txMessage =
bgpSession.prepareBgpNotificationBadMessageLength(length);
ctx.getChannel().write(txMessage);
bgpSession.closeChannel(ctx);
return null;
}
//
// Test whether the rest of the message is received:
// So far we have read the Marker (16 octets) and the
// Length (2 octets) fields.
//
int remainingMessageLen =
length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2;
if (buf.readableBytes() < remainingMessageLen) {
// No enough data received
buf.resetReaderIndex();
return null;
}
//
// Read the BGP message Type field, and process based on that type
//
int type = buf.readUnsignedByte();
remainingMessageLen--; // Adjust after reading the type
ChannelBuffer message = buf.readBytes(remainingMessageLen);
//
// Process the remaining of the message based on the message type
//
switch (type) {
case BgpConstants.BGP_TYPE_OPEN:
bgpSession.processBgpOpen(ctx, message);
break;
case BgpConstants.BGP_TYPE_UPDATE:
bgpSession.processBgpUpdate(ctx, message);
break;
case BgpConstants.BGP_TYPE_NOTIFICATION:
bgpSession.processBgpNotification(ctx, message);
break;
case BgpConstants.BGP_TYPE_KEEPALIVE:
bgpSession.processBgpKeepalive(ctx, message);
break;
default:
//
// ERROR: Bad Message Type
//
// Send NOTIFICATION and close the connection
int errorCode = MessageHeaderError.ERROR_CODE;
int errorSubcode = MessageHeaderError.BAD_MESSAGE_TYPE;
ChannelBuffer data = ChannelBuffers.buffer(1);
data.writeByte(type);
ChannelBuffer txMessage =
bgpSession.prepareBgpNotification(errorCode, errorSubcode,
data);
ctx.getChannel().write(txMessage);
bgpSession.closeChannel(ctx);
return null;
}
return null;
}
}
package org.onlab.onos.sdnip.bgp;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.ArrayList;
import java.util.Objects;
import org.onlab.onos.sdnip.RouteEntry;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import com.google.common.base.MoreObjects;
/**
* Represents a route in BGP.
*/
public class BgpRouteEntry extends RouteEntry {
private final BgpSession bgpSession; // The BGP Session the route was
// received on
private final byte origin; // Route ORIGIN: IGP, EGP, INCOMPLETE
private final AsPath asPath; // The AS Path
private final long localPref; // The local preference for the route
private long multiExitDisc =
BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
/**
* Class constructor.
*
* @param bgpSession the BGP Session the route was received on
* @param prefix the prefix of the route
* @param nextHop the next hop of the route
* @param origin the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
* @param asPath the AS path
* @param localPref the route local preference
*/
public BgpRouteEntry(BgpSession bgpSession, IpPrefix prefix,
IpAddress nextHop, byte origin,
BgpRouteEntry.AsPath asPath, long localPref) {
super(prefix, nextHop);
this.bgpSession = checkNotNull(bgpSession);
this.origin = origin;
this.asPath = checkNotNull(asPath);
this.localPref = localPref;
}
/**
* Gets the BGP Session the route was received on.
*
* @return the BGP Session the route was received on
*/
public BgpSession getBgpSession() {
return bgpSession;
}
/**
* Gets the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE.
*
* @return the route origin: 0=IGP, 1=EGP, 2=INCOMPLETE
*/
public byte getOrigin() {
return origin;
}
/**
* Gets the route AS path.
*
* @return the route AS path
*/
public BgpRouteEntry.AsPath getAsPath() {
return asPath;
}
/**
* Gets the route local preference.
*
* @return the route local preference
*/
public long getLocalPref() {
return localPref;
}
/**
* Gets the route MED (Multi-Exit Discriminator).
*
* @return the route MED (Multi-Exit Discriminator)
*/
public long getMultiExitDisc() {
return multiExitDisc;
}
/**
* Sets the route MED (Multi-Exit Discriminator).
*
* @param multiExitDisc the route MED (Multi-Exit Discriminator) to set
*/
void setMultiExitDisc(long multiExitDisc) {
this.multiExitDisc = multiExitDisc;
}
/**
* Tests whether the route is originated from the local AS.
* <p/>
* The route is considered originated from the local AS if the AS Path
* is empty or if it begins with an AS_SET.
*
* @return true if the route is originated from the local AS, otherwise
* false
*/
boolean isLocalRoute() {
if (asPath.getPathSegments().isEmpty()) {
return true;
}
PathSegment firstPathSegment = asPath.getPathSegments().get(0);
if (firstPathSegment.getType() == BgpConstants.Update.AsPath.AS_SET) {
return true;
}
return false;
}
/**
* Gets the BGP Neighbor AS number the route was received from.
* <p/>
* If the router is originated from the local AS, the return value is
* zero (BGP_AS_0).
*
* @return the BGP Neighbor AS number the route was received from.
*/
long getNeighborAs() {
if (isLocalRoute()) {
return BgpConstants.BGP_AS_0;
}
PathSegment firstPathSegment = asPath.getPathSegments().get(0);
if (firstPathSegment.getSegmentAsNumbers().isEmpty()) {
// TODO: Shouldn't happen. Should check during the parsing.
return BgpConstants.BGP_AS_0;
}
return firstPathSegment.getSegmentAsNumbers().get(0);
}
/**
* Tests whether the AS Path contains a loop.
* <p/>
* The test is done by comparing whether the AS Path contains the
* local AS number.
*
* @param localAsNumber the local AS number to compare against
* @return true if the AS Path contains a loop, otherwise false
*/
boolean hasAsPathLoop(long localAsNumber) {
for (PathSegment pathSegment : asPath.getPathSegments()) {
for (Long asNumber : pathSegment.getSegmentAsNumbers()) {
if (asNumber.equals(localAsNumber)) {
return true;
}
}
}
return false;
}
/**
* Compares this BGP route against another BGP route by using the
* BGP Decision Process.
* <p/>
* NOTE: The comparison needs to be performed only on routes that have
* same IP Prefix.
*
* @param other the BGP route to compare against
* @return true if this BGP route is better than the other BGP route
* or same, otherwise false
*/
boolean isBetterThan(BgpRouteEntry other) {
if (this == other) {
return true; // Return true if same route
}
// Compare the LOCAL_PREF values: larger is better
if (getLocalPref() != other.getLocalPref()) {
return (getLocalPref() > other.getLocalPref());
}
// Compare the AS number in the path: smaller is better
if (getAsPath().getAsPathLength() !=
other.getAsPath().getAsPathLength()) {
return getAsPath().getAsPathLength() <
other.getAsPath().getAsPathLength();
}
// Compare the Origin number: lower is better
if (getOrigin() != other.getOrigin()) {
return (getOrigin() < other.getOrigin());
}
// Compare the MED if the neighbor AS is same: larger is better
medLabel: {
boolean thisIsLocalRoute = isLocalRoute();
if (thisIsLocalRoute != other.isLocalRoute()) {
break medLabel; // AS number is different
}
if (!thisIsLocalRoute) {
long thisNeighborAs = getNeighborAs();
if (thisNeighborAs != other.getNeighborAs()) {
break medLabel; // AS number is different
}
if (thisNeighborAs == BgpConstants.BGP_AS_0) {
break medLabel; // Invalid AS number
}
}
// Compare the MED
if (getMultiExitDisc() != other.getMultiExitDisc()) {
return (getMultiExitDisc() > other.getMultiExitDisc());
}
}
// Compare the peer BGP ID: lower is better
IpAddress peerBgpId = getBgpSession().getRemoteBgpId();
IpAddress otherPeerBgpId = other.getBgpSession().getRemoteBgpId();
if (!peerBgpId.equals(otherPeerBgpId)) {
return (peerBgpId.compareTo(otherPeerBgpId) < 0);
}
// Compare the peer BGP address: lower is better
IpAddress peerAddress = getBgpSession().getRemoteIp4Address();
IpAddress otherPeerAddress =
other.getBgpSession().getRemoteIp4Address();
if (!peerAddress.equals(otherPeerAddress)) {
return (peerAddress.compareTo(otherPeerAddress) < 0);
}
return true; // Routes are same. Shouldn't happen?
}
/**
* A class to represent AS Path Segment.
*/
public static class PathSegment {
private final byte type; // Segment type: AS_SET, AS_SEQUENCE
private final ArrayList<Long> segmentAsNumbers; // Segment AS numbers
/**
* Constructor.
*
* @param type the Path Segment Type: 1=AS_SET, 2=AS_SEQUENCE
* @param segmentAsNumbers the Segment AS numbers
*/
PathSegment(byte type, ArrayList<Long> segmentAsNumbers) {
this.type = type;
this.segmentAsNumbers = checkNotNull(segmentAsNumbers);
}
/**
* Gets the Path Segment Type: AS_SET, AS_SEQUENCE.
*
* @return the Path Segment Type: AS_SET, AS_SEQUENCE
*/
public byte getType() {
return type;
}
/**
* Gets the Path Segment AS Numbers.
*
* @return the Path Segment AS Numbers
*/
public ArrayList<Long> getSegmentAsNumbers() {
return segmentAsNumbers;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof PathSegment)) {
return false;
}
PathSegment otherPathSegment = (PathSegment) other;
return Objects.equals(this.type, otherPathSegment.type) &&
Objects.equals(this.segmentAsNumbers,
otherPathSegment.segmentAsNumbers);
}
@Override
public int hashCode() {
return Objects.hash(type, segmentAsNumbers);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("type", this.type)
.add("segmentAsNumbers", this.segmentAsNumbers)
.toString();
}
}
/**
* A class to represent AS Path.
*/
public static class AsPath {
private final ArrayList<PathSegment> pathSegments;
private final int asPathLength; // Precomputed AS Path Length
/**
* Constructor.
*
* @param pathSegments the Path Segments of the Path
*/
AsPath(ArrayList<PathSegment> pathSegments) {
this.pathSegments = checkNotNull(pathSegments);
//
// Precompute the AS Path Length:
// - AS_SET counts as 1
//
int pl = 0;
for (PathSegment pathSegment : pathSegments) {
if (pathSegment.getType() ==
BgpConstants.Update.AsPath.AS_SET) {
pl++;
continue;
}
pl += pathSegment.getSegmentAsNumbers().size();
}
asPathLength = pl;
}
/**
* Gets the AS Path Segments.
*
* @return the AS Path Segments
*/
public ArrayList<PathSegment> getPathSegments() {
return pathSegments;
}
/**
* Gets the AS Path Length as considered by the BGP Decision Process.
*
* @return the AS Path Length as considered by the BGP Decision Process
*/
int getAsPathLength() {
return asPathLength;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AsPath)) {
return false;
}
AsPath otherAsPath = (AsPath) other;
return Objects.equals(this.pathSegments, otherAsPath.pathSegments);
}
@Override
public int hashCode() {
return Objects.hash(pathSegments);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("pathSegments", this.pathSegments)
.toString();
}
}
/**
* Compares whether two objects are equal.
* <p/>
* NOTE: The bgpSession field is excluded from the comparison.
*
* @return true if the two objects are equal, otherwise false.
*/
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
//
// NOTE: Subclasses are considered as change of identity, hence
// equals() will return false if the class type doesn't match.
//
if (other == null || getClass() != other.getClass()) {
return false;
}
if (!super.equals(other)) {
return false;
}
// NOTE: The bgpSession field is excluded from the comparison
BgpRouteEntry otherRoute = (BgpRouteEntry) other;
return (this.origin == otherRoute.origin) &&
Objects.equals(this.asPath, otherRoute.asPath) &&
(this.localPref == otherRoute.localPref) &&
(this.multiExitDisc == otherRoute.multiExitDisc);
}
/**
* Computes the hash code.
* <p/>
* NOTE: We return the base class hash code to avoid expensive computation
*
* @return the object hash code
*/
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("prefix", prefix())
.add("nextHop", nextHop())
.add("bgpId", bgpSession.getRemoteBgpId())
.add("origin", origin)
.add("asPath", asPath)
.add("localPref", localPref)
.add("multiExitDisc", multiExitDisc)
.toString();
}
}
package org.onlab.onos.sdnip.bgp;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.tuple.Pair;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.util.HashedWheelTimer;
import org.jboss.netty.util.Timeout;
import org.jboss.netty.util.Timer;
import org.jboss.netty.util.TimerTask;
import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications;
import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.HoldTimerExpired;
import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.MessageHeaderError;
import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.OpenMessageError;
import org.onlab.onos.sdnip.bgp.BgpConstants.Notifications.UpdateMessageError;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class for handling the BGP peer sessions.
* There is one instance per each BGP peer session.
*/
public class BgpSession extends SimpleChannelHandler {
private static final Logger log =
LoggerFactory.getLogger(BgpSession.class);
private final BgpSessionManager bgpSessionManager;
// Local flag to indicate the session is closed.
// It is used to avoid the Netty's asynchronous closing of a channel.
private boolean isClosed = false;
private SocketAddress remoteAddress; // Peer IP addr/port
private IpAddress remoteIp4Address; // Peer IPv4 address
private int remoteBgpVersion; // 1 octet
private long remoteAs; // 2 octets
private long remoteHoldtime; // 2 octets
private IpAddress remoteBgpId; // 4 octets -> IPv4 address
//
private SocketAddress localAddress; // Local IP addr/port
private IpAddress localIp4Address; // Local IPv4 address
private int localBgpVersion; // 1 octet
private long localAs; // 2 octets
private long localHoldtime; // 2 octets
private IpAddress localBgpId; // 4 octets -> IPv4 address
//
private long localKeepaliveInterval; // Keepalive interval
// Timers state
private Timer timer = new HashedWheelTimer();
private volatile Timeout keepaliveTimeout; // Periodic KEEPALIVE
private volatile Timeout sessionTimeout; // Session timeout
// BGP RIB-IN routing entries from this peer
private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRibIn =
new ConcurrentHashMap<>();
/**
* Constructor for a given BGP Session Manager.
*
* @param bgpSessionManager the BGP Session Manager to use
*/
BgpSession(BgpSessionManager bgpSessionManager) {
this.bgpSessionManager = bgpSessionManager;
}
/**
* Gets the BGP RIB-IN routing entries.
*
* @return the BGP RIB-IN routing entries
*/
public Collection<BgpRouteEntry> getBgpRibIn() {
return bgpRibIn.values();
}
/**
* Finds a BGP routing entry in the BGP RIB-IN.
*
* @param prefix the prefix of the route to search for
* @return the BGP routing entry if found, otherwise null
*/
public BgpRouteEntry findBgpRouteEntry(IpPrefix prefix) {
return bgpRibIn.get(prefix);
}
/**
* Gets the BGP session remote address.
*
* @return the BGP session remote address
*/
public SocketAddress getRemoteAddress() {
return remoteAddress;
}
/**
* Gets the BGP session remote IPv4 address.
*
* @return the BGP session remote IPv4 address
*/
public IpAddress getRemoteIp4Address() {
return remoteIp4Address;
}
/**
* Gets the BGP session remote BGP version.
*
* @return the BGP session remote BGP version
*/
public int getRemoteBgpVersion() {
return remoteBgpVersion;
}
/**
* Gets the BGP session remote AS number.
*
* @return the BGP session remote AS number
*/
public long getRemoteAs() {
return remoteAs;
}
/**
* Gets the BGP session remote Holdtime.
*
* @return the BGP session remote Holdtime
*/
public long getRemoteHoldtime() {
return remoteHoldtime;
}
/**
* Gets the BGP session remote BGP Identifier as an IPv4 address.
*
* @return the BGP session remote BGP Identifier as an IPv4 address
*/
public IpAddress getRemoteBgpId() {
return remoteBgpId;
}
/**
* Gets the BGP session local address.
*
* @return the BGP session local address
*/
public SocketAddress getLocalAddress() {
return localAddress;
}
/**
* Gets the BGP session local BGP version.
*
* @return the BGP session local BGP version
*/
public int getLocalBgpVersion() {
return localBgpVersion;
}
/**
* Gets the BGP session local AS number.
*
* @return the BGP session local AS number
*/
public long getLocalAs() {
return localAs;
}
/**
* Gets the BGP session local Holdtime.
*
* @return the BGP session local Holdtime
*/
public long getLocalHoldtime() {
return localHoldtime;
}
/**
* Gets the BGP session local BGP Identifier as an IPv4 address.
*
* @return the BGP session local BGP Identifier as an IPv4 address
*/
public IpAddress getLocalBgpId() {
return localBgpId;
}
/**
* Tests whether the session is closed.
* <p/>
* NOTE: We use this method to avoid the Netty's asynchronous closing
* of a channel.
*
* @param return true if the session is closed
*/
boolean isClosed() {
return isClosed;
}
/**
* Closes the channel.
*
* @param ctx the Channel Handler Context
*/
void closeChannel(ChannelHandlerContext ctx) {
isClosed = true;
timer.stop();
ctx.getChannel().close();
}
@Override
public void channelConnected(ChannelHandlerContext ctx,
ChannelStateEvent channelEvent) {
localAddress = ctx.getChannel().getLocalAddress();
remoteAddress = ctx.getChannel().getRemoteAddress();
// Assign the local and remote IPv4 addresses
InetAddress inetAddr;
if (localAddress instanceof InetSocketAddress) {
inetAddr = ((InetSocketAddress) localAddress).getAddress();
localIp4Address = IpAddress.valueOf(inetAddr.getAddress());
}
if (remoteAddress instanceof InetSocketAddress) {
inetAddr = ((InetSocketAddress) remoteAddress).getAddress();
remoteIp4Address = IpAddress.valueOf(inetAddr.getAddress());
}
log.debug("BGP Session Connected from {} on {}",
remoteAddress, localAddress);
if (!bgpSessionManager.peerConnected(this)) {
log.debug("Cannot setup BGP Session Connection from {}. Closing...",
remoteAddress);
ctx.getChannel().close();
}
}
@Override
public void channelDisconnected(ChannelHandlerContext ctx,
ChannelStateEvent channelEvent) {
log.debug("BGP Session Disconnected from {} on {}",
ctx.getChannel().getRemoteAddress(),
ctx.getChannel().getLocalAddress());
//
// Withdraw the routes advertised by this BGP peer
//
// NOTE: We must initialize the RIB-IN before propagating the withdraws
// for further processing. Otherwise, the BGP Decision Process
// will use those routes again.
//
Collection<BgpRouteEntry> deletedRoutes = bgpRibIn.values();
bgpRibIn = new ConcurrentHashMap<>();
// Push the updates to the BGP Merged RIB
BgpSessionManager.BgpRouteSelector bgpRouteSelector =
bgpSessionManager.getBgpRouteSelector();
Collection<BgpRouteEntry> addedRoutes = Collections.emptyList();
bgpRouteSelector.routeUpdates(this, addedRoutes, deletedRoutes);
bgpSessionManager.peerDisconnected(this);
}
/**
* Processes BGP OPEN message.
*
* @param ctx the Channel Handler Context
* @param message the message to process
*/
void processBgpOpen(ChannelHandlerContext ctx, ChannelBuffer message) {
int minLength =
BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
if (message.readableBytes() < minLength) {
log.debug("BGP RX OPEN Error from {}: " +
"Message length {} too short. Must be at least {}",
remoteAddress, message.readableBytes(), minLength);
//
// ERROR: Bad Message Length
//
// Send NOTIFICATION and close the connection
ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
return;
}
//
// Parse the OPEN message
//
// Remote BGP version
remoteBgpVersion = message.readUnsignedByte();
if (remoteBgpVersion != BgpConstants.BGP_VERSION) {
log.debug("BGP RX OPEN Error from {}: " +
"Unsupported BGP version {}. Should be {}",
remoteAddress, remoteBgpVersion,
BgpConstants.BGP_VERSION);
//
// ERROR: Unsupported Version Number
//
// Send NOTIFICATION and close the connection
int errorCode = OpenMessageError.ERROR_CODE;
int errorSubcode = OpenMessageError.UNSUPPORTED_VERSION_NUMBER;
ChannelBuffer data = ChannelBuffers.buffer(2);
data.writeShort(BgpConstants.BGP_VERSION);
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, data);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
return;
}
// Remote AS number
remoteAs = message.readUnsignedShort();
//
// Verify that the AS number is same for all other BGP Sessions
// NOTE: This check applies only for our use-case where all BGP
// sessions are iBGP.
//
for (BgpSession bgpSession : bgpSessionManager.getBgpSessions()) {
if (remoteAs != bgpSession.getRemoteAs()) {
log.debug("BGP RX OPEN Error from {}: Bad Peer AS {}. " +
"Expected {}",
remoteAddress, remoteAs, bgpSession.getRemoteAs());
//
// ERROR: Bad Peer AS
//
// Send NOTIFICATION and close the connection
int errorCode = OpenMessageError.ERROR_CODE;
int errorSubcode = OpenMessageError.BAD_PEER_AS;
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, null);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
return;
}
}
// Remote Hold Time
remoteHoldtime = message.readUnsignedShort();
if ((remoteHoldtime != 0) &&
(remoteHoldtime < BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME)) {
log.debug("BGP RX OPEN Error from {}: " +
"Unacceptable Hold Time field {}. " +
"Should be 0 or at least {}",
remoteAddress, remoteHoldtime,
BgpConstants.BGP_KEEPALIVE_MIN_HOLDTIME);
//
// ERROR: Unacceptable Hold Time
//
// Send NOTIFICATION and close the connection
int errorCode = OpenMessageError.ERROR_CODE;
int errorSubcode = OpenMessageError.UNACCEPTABLE_HOLD_TIME;
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, null);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
return;
}
// Remote BGP Identifier
remoteBgpId = IpAddress.valueOf((int) message.readUnsignedInt());
// Optional Parameters
int optParamLen = message.readUnsignedByte();
if (message.readableBytes() < optParamLen) {
log.debug("BGP RX OPEN Error from {}: " +
"Invalid Optional Parameter Length field {}. " +
"Remaining Optional Parameters {}",
remoteAddress, optParamLen, message.readableBytes());
//
// ERROR: Invalid Optional Parameter Length field: Unspecific
//
// Send NOTIFICATION and close the connection
int errorCode = OpenMessageError.ERROR_CODE;
int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC;
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, null);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
return;
}
// TODO: Parse the optional parameters (if needed)
message.readBytes(optParamLen); // NOTE: data ignored
//
// Copy some of the remote peer's state/setup to the local setup:
// - BGP version
// - AS number (NOTE: the peer setup is always iBGP)
// - Holdtime
// Also, assign the local BGP ID based on the local setup
//
localBgpVersion = remoteBgpVersion;
localAs = remoteAs;
localHoldtime = remoteHoldtime;
localBgpId = bgpSessionManager.getMyBgpId();
// Set the Keepalive interval
if (localHoldtime == 0) {
localKeepaliveInterval = 0;
} else {
localKeepaliveInterval = Math.max(localHoldtime /
BgpConstants.BGP_KEEPALIVE_PER_HOLD_INTERVAL,
BgpConstants.BGP_KEEPALIVE_MIN_INTERVAL);
}
log.debug("BGP RX OPEN message from {}: " +
"BGPv{} AS {} BGP-ID {} Holdtime {}",
remoteAddress, remoteBgpVersion, remoteAs,
remoteBgpId, remoteHoldtime);
// Send my OPEN followed by KEEPALIVE
ChannelBuffer txMessage = prepareBgpOpen();
ctx.getChannel().write(txMessage);
//
txMessage = prepareBgpKeepalive();
ctx.getChannel().write(txMessage);
// Start the KEEPALIVE timer
restartKeepaliveTimer(ctx);
// Start the Session Timeout timer
restartSessionTimeoutTimer(ctx);
}
/**
* Processes BGP UPDATE message.
*
* @param ctx the Channel Handler Context
* @param message the message to process
*/
void processBgpUpdate(ChannelHandlerContext ctx, ChannelBuffer message) {
Collection<BgpRouteEntry> addedRoutes = null;
Map<IpPrefix, BgpRouteEntry> deletedRoutes = new HashMap<>();
int minLength =
BgpConstants.BGP_UPDATE_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
if (message.readableBytes() < minLength) {
log.debug("BGP RX UPDATE Error from {}: " +
"Message length {} too short. Must be at least {}",
remoteAddress, message.readableBytes(), minLength);
//
// ERROR: Bad Message Length
//
// Send NOTIFICATION and close the connection
ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
return;
}
log.debug("BGP RX UPDATE message from {}", remoteAddress);
//
// Parse the UPDATE message
//
//
// Parse the Withdrawn Routes
//
int withdrawnRoutesLength = message.readUnsignedShort();
if (withdrawnRoutesLength > message.readableBytes()) {
// ERROR: Malformed Attribute List
actionsBgpUpdateMalformedAttributeList(ctx);
return;
}
Collection<IpPrefix> withdrawnPrefixes = null;
try {
withdrawnPrefixes = parsePackedPrefixes(withdrawnRoutesLength,
message);
} catch (BgpParseException e) {
// ERROR: Invalid Network Field
log.debug("Exception parsing Withdrawn Prefixes from BGP peer {}: ",
remoteBgpId, e);
actionsBgpUpdateInvalidNetworkField(ctx);
return;
}
for (IpPrefix prefix : withdrawnPrefixes) {
log.debug("BGP RX UPDATE message WITHDRAWN from {}: {}",
remoteAddress, prefix);
BgpRouteEntry bgpRouteEntry = bgpRibIn.get(prefix);
if (bgpRouteEntry != null) {
deletedRoutes.put(prefix, bgpRouteEntry);
}
}
//
// Parse the Path Attributes
//
try {
addedRoutes = parsePathAttributes(ctx, message);
} catch (BgpParseException e) {
log.debug("Exception parsing Path Attributes from BGP peer {}: ",
remoteBgpId, e);
// NOTE: The session was already closed, so nothing else to do
return;
}
// Ignore WITHDRAWN routes that are ADDED
for (BgpRouteEntry bgpRouteEntry : addedRoutes) {
deletedRoutes.remove(bgpRouteEntry.prefix());
}
// Update the BGP RIB-IN
for (BgpRouteEntry bgpRouteEntry : deletedRoutes.values()) {
bgpRibIn.remove(bgpRouteEntry.prefix());
}
for (BgpRouteEntry bgpRouteEntry : addedRoutes) {
bgpRibIn.put(bgpRouteEntry.prefix(), bgpRouteEntry);
}
// Push the updates to the BGP Merged RIB
BgpSessionManager.BgpRouteSelector bgpRouteSelector =
bgpSessionManager.getBgpRouteSelector();
bgpRouteSelector.routeUpdates(this, addedRoutes,
deletedRoutes.values());
// Start the Session Timeout timer
restartSessionTimeoutTimer(ctx);
}
/**
* Parse BGP Path Attributes from the BGP UPDATE message.
*
* @param ctx the Channel Handler Context
* @param message the message to parse
* @return a collection of the result BGP Route Entries
* @throws BgpParseException
*/
private Collection<BgpRouteEntry> parsePathAttributes(
ChannelHandlerContext ctx,
ChannelBuffer message)
throws BgpParseException {
Map<IpPrefix, BgpRouteEntry> addedRoutes = new HashMap<>();
//
// Parsed values
//
Short origin = -1; // Mandatory
BgpRouteEntry.AsPath asPath = null; // Mandatory
IpAddress nextHop = null; // Mandatory
long multiExitDisc = // Optional
BgpConstants.Update.MultiExitDisc.LOWEST_MULTI_EXIT_DISC;
Long localPref = null; // Mandatory
Long aggregatorAsNumber = null; // Optional: unused
IpAddress aggregatorIpAddress = null; // Optional: unused
//
// Get and verify the Path Attributes Length
//
int pathAttributeLength = message.readUnsignedShort();
if (pathAttributeLength > message.readableBytes()) {
// ERROR: Malformed Attribute List
actionsBgpUpdateMalformedAttributeList(ctx);
String errorMsg = "Malformed Attribute List";
throw new BgpParseException(errorMsg);
}
if (pathAttributeLength == 0) {
return addedRoutes.values();
}
//
// Parse the Path Attributes
//
int pathAttributeEnd = message.readerIndex() + pathAttributeLength;
while (message.readerIndex() < pathAttributeEnd) {
int attrFlags = message.readUnsignedByte();
if (message.readerIndex() >= pathAttributeEnd) {
// ERROR: Malformed Attribute List
actionsBgpUpdateMalformedAttributeList(ctx);
String errorMsg = "Malformed Attribute List";
throw new BgpParseException(errorMsg);
}
int attrTypeCode = message.readUnsignedByte();
// The Attribute Flags
boolean optionalBit = ((0x80 & attrFlags) != 0);
boolean transitiveBit = ((0x40 & attrFlags) != 0);
boolean partialBit = ((0x20 & attrFlags) != 0);
boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
// The Attribute Length
int attrLen = 0;
int attrLenOctets = 1;
if (extendedLengthBit) {
attrLenOctets = 2;
}
if (message.readerIndex() + attrLenOctets > pathAttributeEnd) {
// ERROR: Malformed Attribute List
actionsBgpUpdateMalformedAttributeList(ctx);
String errorMsg = "Malformed Attribute List";
throw new BgpParseException(errorMsg);
}
if (extendedLengthBit) {
attrLen = message.readUnsignedShort();
} else {
attrLen = message.readUnsignedByte();
}
if (message.readerIndex() + attrLen > pathAttributeEnd) {
// ERROR: Malformed Attribute List
actionsBgpUpdateMalformedAttributeList(ctx);
String errorMsg = "Malformed Attribute List";
throw new BgpParseException(errorMsg);
}
//
// Verify the Attribute Flags
//
verifyBgpUpdateAttributeFlags(ctx, attrTypeCode, attrLen,
attrFlags, message);
//
// Extract the Attribute Value based on the Attribute Type Code
//
switch (attrTypeCode) {
case BgpConstants.Update.Origin.TYPE:
// Attribute Type Code ORIGIN
origin = parseAttributeTypeOrigin(ctx, attrTypeCode, attrLen,
attrFlags, message);
break;
case BgpConstants.Update.AsPath.TYPE:
// Attribute Type Code AS_PATH
asPath = parseAttributeTypeAsPath(ctx, attrTypeCode, attrLen,
attrFlags, message);
break;
case BgpConstants.Update.NextHop.TYPE:
// Attribute Type Code NEXT_HOP
nextHop = parseAttributeTypeNextHop(ctx, attrTypeCode, attrLen,
attrFlags, message);
break;
case BgpConstants.Update.MultiExitDisc.TYPE:
// Attribute Type Code MULTI_EXIT_DISC
multiExitDisc =
parseAttributeTypeMultiExitDisc(ctx, attrTypeCode, attrLen,
attrFlags, message);
break;
case BgpConstants.Update.LocalPref.TYPE:
// Attribute Type Code LOCAL_PREF
localPref =
parseAttributeTypeLocalPref(ctx, attrTypeCode, attrLen,
attrFlags, message);
break;
case BgpConstants.Update.AtomicAggregate.TYPE:
// Attribute Type Code ATOMIC_AGGREGATE
parseAttributeTypeAtomicAggregate(ctx, attrTypeCode, attrLen,
attrFlags, message);
// Nothing to do: this attribute is primarily informational
break;
case BgpConstants.Update.Aggregator.TYPE:
// Attribute Type Code AGGREGATOR
Pair<Long, IpAddress> aggregator =
parseAttributeTypeAggregator(ctx, attrTypeCode, attrLen,
attrFlags, message);
aggregatorAsNumber = aggregator.getLeft();
aggregatorIpAddress = aggregator.getRight();
break;
default:
// TODO: Parse any new Attribute Types if needed
if (!optionalBit) {
// ERROR: Unrecognized Well-known Attribute
actionsBgpUpdateUnrecognizedWellKnownAttribute(
ctx, attrTypeCode, attrLen, attrFlags, message);
String errorMsg = "Unrecognized Well-known Attribute: " +
attrTypeCode;
throw new BgpParseException(errorMsg);
}
// Skip the data from the unrecognized attribute
log.debug("BGP RX UPDATE message from {}: " +
"Unrecognized Attribute Type {}",
remoteAddress, attrTypeCode);
message.skipBytes(attrLen);
break;
}
}
//
// Verify the Well-known Attributes
//
verifyBgpUpdateWellKnownAttributes(ctx, origin, asPath, nextHop,
localPref);
//
// Parse the NLRI (Network Layer Reachability Information)
//
Collection<IpPrefix> addedPrefixes = null;
int nlriLength = message.readableBytes();
try {
addedPrefixes = parsePackedPrefixes(nlriLength, message);
} catch (BgpParseException e) {
// ERROR: Invalid Network Field
log.debug("Exception parsing NLRI from BGP peer {}: ",
remoteBgpId, e);
actionsBgpUpdateInvalidNetworkField(ctx);
// Rethrow the exception
throw e;
}
// Generate the added routes
for (IpPrefix prefix : addedPrefixes) {
BgpRouteEntry bgpRouteEntry =
new BgpRouteEntry(this, prefix, nextHop,
origin.byteValue(), asPath, localPref);
bgpRouteEntry.setMultiExitDisc(multiExitDisc);
if (bgpRouteEntry.hasAsPathLoop(localAs)) {
log.debug("BGP RX UPDATE message IGNORED from {}: {} " +
"nextHop {}: contains AS Path loop",
remoteAddress, prefix, nextHop);
continue;
} else {
log.debug("BGP RX UPDATE message ADDED from {}: {} nextHop {}",
remoteAddress, prefix, nextHop);
}
addedRoutes.put(prefix, bgpRouteEntry);
}
return addedRoutes.values();
}
/**
* Verifies BGP UPDATE Well-known Attributes.
*
* @param ctx the Channel Handler Context
* @param origin the ORIGIN well-known mandatory attribute
* @param asPath the AS_PATH well-known mandatory attribute
* @param nextHop the NEXT_HOP well-known mandatory attribute
* @param localPref the LOCAL_PREF required attribute
* @throws BgpParseException
*/
private void verifyBgpUpdateWellKnownAttributes(
ChannelHandlerContext ctx,
Short origin,
BgpRouteEntry.AsPath asPath,
IpAddress nextHop,
Long localPref)
throws BgpParseException {
//
// Check for Missing Well-known Attributes
//
if ((origin == null) || (origin == -1)) {
// Missing Attribute Type Code ORIGIN
int type = BgpConstants.Update.Origin.TYPE;
actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
String errorMsg = "Missing Well-known Attribute: ORIGIN";
throw new BgpParseException(errorMsg);
}
if (asPath == null) {
// Missing Attribute Type Code AS_PATH
int type = BgpConstants.Update.AsPath.TYPE;
actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
String errorMsg = "Missing Well-known Attribute: AS_PATH";
throw new BgpParseException(errorMsg);
}
if (nextHop == null) {
// Missing Attribute Type Code NEXT_HOP
int type = BgpConstants.Update.NextHop.TYPE;
actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
String errorMsg = "Missing Well-known Attribute: NEXT_HOP";
throw new BgpParseException(errorMsg);
}
if (localPref == null) {
// Missing Attribute Type Code LOCAL_PREF
// NOTE: Required for iBGP
int type = BgpConstants.Update.LocalPref.TYPE;
actionsBgpUpdateMissingWellKnownAttribute(ctx, type);
String errorMsg = "Missing Well-known Attribute: LOCAL_PREF";
throw new BgpParseException(errorMsg);
}
}
/**
* Verifies the BGP UPDATE Attribute Flags.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message to parse
* @throws BgpParseException
*/
private void verifyBgpUpdateAttributeFlags(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message)
throws BgpParseException {
//
// Assign the Attribute Type Name and the Well-known flag
//
String typeName = "UNKNOWN";
boolean isWellKnown = false;
switch (attrTypeCode) {
case BgpConstants.Update.Origin.TYPE:
isWellKnown = true;
typeName = "ORIGIN";
break;
case BgpConstants.Update.AsPath.TYPE:
isWellKnown = true;
typeName = "AS_PATH";
break;
case BgpConstants.Update.NextHop.TYPE:
isWellKnown = true;
typeName = "NEXT_HOP";
break;
case BgpConstants.Update.MultiExitDisc.TYPE:
isWellKnown = false;
typeName = "MULTI_EXIT_DISC";
break;
case BgpConstants.Update.LocalPref.TYPE:
isWellKnown = true;
typeName = "LOCAL_PREF";
break;
case BgpConstants.Update.AtomicAggregate.TYPE:
isWellKnown = true;
typeName = "ATOMIC_AGGREGATE";
break;
case BgpConstants.Update.Aggregator.TYPE:
isWellKnown = false;
typeName = "AGGREGATOR";
break;
default:
isWellKnown = false;
typeName = "UNKNOWN(" + attrTypeCode + ")";
break;
}
//
// Verify the Attribute Flags
//
boolean optionalBit = ((0x80 & attrFlags) != 0);
boolean transitiveBit = ((0x40 & attrFlags) != 0);
boolean partialBit = ((0x20 & attrFlags) != 0);
if ((isWellKnown && optionalBit) ||
(isWellKnown && (!transitiveBit)) ||
(isWellKnown && partialBit) ||
(optionalBit && (!transitiveBit) && partialBit)) {
//
// ERROR: The Optional bit cannot be set for Well-known attributes
// ERROR: The Transtive bit MUST be 1 for well-known attributes
// ERROR: The Partial bit MUST be 0 for well-known attributes
// ERROR: The Partial bit MUST be 0 for optional non-transitive
// attributes
//
actionsBgpUpdateAttributeFlagsError(
ctx, attrTypeCode, attrLen, attrFlags, message);
String errorMsg = "Attribute Flags Error for " + typeName + ": " +
attrFlags;
throw new BgpParseException(errorMsg);
}
}
/**
* Parses BGP UPDATE Attribute Type ORIGIN.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message to parse
* @return the parsed ORIGIN value
* @throws BgpParseException
*/
private short parseAttributeTypeOrigin(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message)
throws BgpParseException {
// Check the Attribute Length
if (attrLen != BgpConstants.Update.Origin.LENGTH) {
// ERROR: Attribute Length Error
actionsBgpUpdateAttributeLengthError(
ctx, attrTypeCode, attrLen, attrFlags, message);
String errorMsg = "Attribute Length Error";
throw new BgpParseException(errorMsg);
}
message.markReaderIndex();
short origin = message.readUnsignedByte();
switch (origin) {
case BgpConstants.Update.Origin.IGP:
// FALLTHROUGH
case BgpConstants.Update.Origin.EGP:
// FALLTHROUGH
case BgpConstants.Update.Origin.INCOMPLETE:
break;
default:
// ERROR: Invalid ORIGIN Attribute
message.resetReaderIndex();
actionsBgpUpdateInvalidOriginAttribute(
ctx, attrTypeCode, attrLen, attrFlags, message, origin);
String errorMsg = "Invalid ORIGIN Attribute: " + origin;
throw new BgpParseException(errorMsg);
}
return origin;
}
/**
* Parses BGP UPDATE Attribute AS Path.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message to parse
* @return the parsed AS Path
* @throws BgpParseException
*/
private BgpRouteEntry.AsPath parseAttributeTypeAsPath(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message)
throws BgpParseException {
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
//
// Parse the message
//
while (attrLen > 0) {
if (attrLen < 2) {
// ERROR: Malformed AS_PATH
actionsBgpUpdateMalformedAsPath(ctx);
String errorMsg = "Malformed AS Path";
throw new BgpParseException(errorMsg);
}
// Get the Path Segment Type and Length (in number of ASes)
short pathSegmentType = message.readUnsignedByte();
short pathSegmentLength = message.readUnsignedByte();
attrLen -= 2;
// Verify the Path Segment Type
switch (pathSegmentType) {
case BgpConstants.Update.AsPath.AS_SET:
// FALLTHROUGH
case BgpConstants.Update.AsPath.AS_SEQUENCE:
break;
default:
// ERROR: Invalid Path Segment Type
//
// NOTE: The BGP Spec (RFC 4271) doesn't contain Error Subcode
// for "Invalid Path Segment Type", hence we return
// the error as "Malformed AS_PATH".
//
actionsBgpUpdateMalformedAsPath(ctx);
String errorMsg =
"Invalid AS Path Segment Type: " + pathSegmentType;
throw new BgpParseException(errorMsg);
}
// Parse the AS numbers
if (2 * pathSegmentLength > attrLen) {
// ERROR: Malformed AS_PATH
actionsBgpUpdateMalformedAsPath(ctx);
String errorMsg = "Malformed AS Path";
throw new BgpParseException(errorMsg);
}
attrLen -= (2 * pathSegmentLength);
ArrayList<Long> segmentAsNumbers = new ArrayList<>();
while (pathSegmentLength-- > 0) {
long asNumber = message.readUnsignedShort();
segmentAsNumbers.add(asNumber);
}
BgpRouteEntry.PathSegment pathSegment =
new BgpRouteEntry.PathSegment((byte) pathSegmentType,
segmentAsNumbers);
pathSegments.add(pathSegment);
}
return new BgpRouteEntry.AsPath(pathSegments);
}
/**
* Parses BGP UPDATE Attribute Type NEXT_HOP.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message to parse
* @return the parsed NEXT_HOP value
* @throws BgpParseException
*/
private IpAddress parseAttributeTypeNextHop(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message)
throws BgpParseException {
// Check the Attribute Length
if (attrLen != BgpConstants.Update.NextHop.LENGTH) {
// ERROR: Attribute Length Error
actionsBgpUpdateAttributeLengthError(
ctx, attrTypeCode, attrLen, attrFlags, message);
String errorMsg = "Attribute Length Error";
throw new BgpParseException(errorMsg);
}
message.markReaderIndex();
long address = message.readUnsignedInt();
IpAddress nextHopAddress = IpAddress.valueOf((int) address);
//
// Check whether the NEXT_HOP IP address is semantically correct.
// As per RFC 4271, Section 6.3:
//
// a) It MUST NOT be the IP address of the receiving speaker
// b) In the case of an EBGP ....
//
// Here we check only (a), because (b) doesn't apply for us: all our
// peers are iBGP.
//
if (nextHopAddress.equals(localIp4Address)) {
// ERROR: Invalid NEXT_HOP Attribute
message.resetReaderIndex();
actionsBgpUpdateInvalidNextHopAttribute(
ctx, attrTypeCode, attrLen, attrFlags, message,
nextHopAddress);
String errorMsg = "Invalid NEXT_HOP Attribute: " + nextHopAddress;
throw new BgpParseException(errorMsg);
}
return nextHopAddress;
}
/**
* Parses BGP UPDATE Attribute Type MULTI_EXIT_DISC.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message to parse
* @return the parsed MULTI_EXIT_DISC value
* @throws BgpParseException
*/
private long parseAttributeTypeMultiExitDisc(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message)
throws BgpParseException {
// Check the Attribute Length
if (attrLen != BgpConstants.Update.MultiExitDisc.LENGTH) {
// ERROR: Attribute Length Error
actionsBgpUpdateAttributeLengthError(
ctx, attrTypeCode, attrLen, attrFlags, message);
String errorMsg = "Attribute Length Error";
throw new BgpParseException(errorMsg);
}
long multiExitDisc = message.readUnsignedInt();
return multiExitDisc;
}
/**
* Parses BGP UPDATE Attribute Type LOCAL_PREF.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message to parse
* @return the parsed LOCAL_PREF value
* @throws BgpParseException
*/
private long parseAttributeTypeLocalPref(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message)
throws BgpParseException {
// Check the Attribute Length
if (attrLen != BgpConstants.Update.LocalPref.LENGTH) {
// ERROR: Attribute Length Error
actionsBgpUpdateAttributeLengthError(
ctx, attrTypeCode, attrLen, attrFlags, message);
String errorMsg = "Attribute Length Error";
throw new BgpParseException(errorMsg);
}
long localPref = message.readUnsignedInt();
return localPref;
}
/**
* Parses BGP UPDATE Attribute Type ATOMIC_AGGREGATE.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message to parse
* @throws BgpParseException
*/
private void parseAttributeTypeAtomicAggregate(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message)
throws BgpParseException {
// Check the Attribute Length
if (attrLen != BgpConstants.Update.AtomicAggregate.LENGTH) {
// ERROR: Attribute Length Error
actionsBgpUpdateAttributeLengthError(
ctx, attrTypeCode, attrLen, attrFlags, message);
String errorMsg = "Attribute Length Error";
throw new BgpParseException(errorMsg);
}
// Nothing to do: this attribute is primarily informational
}
/**
* Parses BGP UPDATE Attribute Type AGGREGATOR.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message to parse
* @return the parsed AGGREGATOR value: a tuple of <AS-Number, IP-Address>
* @throws BgpParseException
*/
private Pair<Long, IpAddress> parseAttributeTypeAggregator(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message)
throws BgpParseException {
// Check the Attribute Length
if (attrLen != BgpConstants.Update.Aggregator.LENGTH) {
// ERROR: Attribute Length Error
actionsBgpUpdateAttributeLengthError(
ctx, attrTypeCode, attrLen, attrFlags, message);
String errorMsg = "Attribute Length Error";
throw new BgpParseException(errorMsg);
}
// The AGGREGATOR AS number
long aggregatorAsNumber = message.readUnsignedShort();
// The AGGREGATOR IP address
long aggregatorAddress = message.readUnsignedInt();
IpAddress aggregatorIpAddress =
IpAddress.valueOf((int) aggregatorAddress);
Pair<Long, IpAddress> aggregator = Pair.of(aggregatorAsNumber,
aggregatorIpAddress);
return aggregator;
}
/**
* Parses a message that contains encoded IPv4 network prefixes.
* <p>
* The IPv4 prefixes are encoded in the form:
* <Length, Prefix> where Length is the length in bits of the IPv4 prefix,
* and Prefix is the IPv4 prefix (padded with trailing bits to the end
* of an octet).
*
* @param totalLength the total length of the data to parse
* @param message the message with data to parse
* @return a collection of parsed IPv4 network prefixes
* @throws BgpParseException
*/
private Collection<IpPrefix> parsePackedPrefixes(int totalLength,
ChannelBuffer message)
throws BgpParseException {
Collection<IpPrefix> result = new ArrayList<>();
if (totalLength == 0) {
return result;
}
// Parse the data
int dataEnd = message.readerIndex() + totalLength;
while (message.readerIndex() < dataEnd) {
int prefixBitlen = message.readUnsignedByte();
int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up
if (message.readerIndex() + prefixBytelen > dataEnd) {
String errorMsg = "Malformed Network Prefixes";
throw new BgpParseException(errorMsg);
}
long address = 0;
long extraShift = (4 - prefixBytelen) * 8;
while (prefixBytelen > 0) {
address <<= 8;
address |= message.readUnsignedByte();
prefixBytelen--;
}
address <<= extraShift;
IpPrefix prefix =
IpPrefix.valueOf(IpAddress.valueOf((int) address).toInt(),
(short) prefixBitlen);
result.add(prefix);
}
return result;
}
/**
* Applies the appropriate actions after detecting BGP UPDATE
* Invalid Network Field Error: send NOTIFICATION and close the channel.
*
* @param ctx the Channel Handler Context
*/
private void actionsBgpUpdateInvalidNetworkField(
ChannelHandlerContext ctx) {
log.debug("BGP RX UPDATE Error from {}: Invalid Network Field",
remoteAddress);
//
// ERROR: Invalid Network Field
//
// Send NOTIFICATION and close the connection
int errorCode = UpdateMessageError.ERROR_CODE;
int errorSubcode = UpdateMessageError.INVALID_NETWORK_FIELD;
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, null);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
}
/**
* Applies the appropriate actions after detecting BGP UPDATE
* Malformed Attribute List Error: send NOTIFICATION and close the channel.
*
* @param ctx the Channel Handler Context
*/
private void actionsBgpUpdateMalformedAttributeList(
ChannelHandlerContext ctx) {
log.debug("BGP RX UPDATE Error from {}: Malformed Attribute List",
remoteAddress);
//
// ERROR: Malformed Attribute List
//
// Send NOTIFICATION and close the connection
int errorCode = UpdateMessageError.ERROR_CODE;
int errorSubcode = UpdateMessageError.MALFORMED_ATTRIBUTE_LIST;
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, null);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
}
/**
* Applies the appropriate actions after detecting BGP UPDATE
* Missing Well-known Attribute Error: send NOTIFICATION and close the
* channel.
*
* @param ctx the Channel Handler Context
* @param missingAttrTypeCode the missing attribute type code
*/
private void actionsBgpUpdateMissingWellKnownAttribute(
ChannelHandlerContext ctx,
int missingAttrTypeCode) {
log.debug("BGP RX UPDATE Error from {}: Missing Well-known Attribute: {}",
remoteAddress, missingAttrTypeCode);
//
// ERROR: Missing Well-known Attribute
//
// Send NOTIFICATION and close the connection
int errorCode = UpdateMessageError.ERROR_CODE;
int errorSubcode = UpdateMessageError.MISSING_WELL_KNOWN_ATTRIBUTE;
ChannelBuffer data = ChannelBuffers.buffer(1);
data.writeByte(missingAttrTypeCode);
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, data);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
}
/**
* Applies the appropriate actions after detecting BGP UPDATE
* Invalid ORIGIN Attribute Error: send NOTIFICATION and close the channel.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message with the data
* @param origin the ORIGIN attribute value
*/
private void actionsBgpUpdateInvalidOriginAttribute(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message,
short origin) {
log.debug("BGP RX UPDATE Error from {}: Invalid ORIGIN Attribute",
remoteAddress);
//
// ERROR: Invalid ORIGIN Attribute
//
// Send NOTIFICATION and close the connection
int errorCode = UpdateMessageError.ERROR_CODE;
int errorSubcode = UpdateMessageError.INVALID_ORIGIN_ATTRIBUTE;
ChannelBuffer data =
prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
attrFlags, message);
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, data);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
}
/**
* Applies the appropriate actions after detecting BGP UPDATE
* Attribute Flags Error: send NOTIFICATION and close the channel.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message with the data
*/
private void actionsBgpUpdateAttributeFlagsError(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message) {
log.debug("BGP RX UPDATE Error from {}: Attribute Flags Error",
remoteAddress);
//
// ERROR: Attribute Flags Error
//
// Send NOTIFICATION and close the connection
int errorCode = UpdateMessageError.ERROR_CODE;
int errorSubcode = UpdateMessageError.ATTRIBUTE_FLAGS_ERROR;
ChannelBuffer data =
prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
attrFlags, message);
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, data);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
}
/**
* Applies the appropriate actions after detecting BGP UPDATE
* Invalid NEXT_HOP Attribute Error: send NOTIFICATION and close the
* channel.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message with the data
* @param nextHop the NEXT_HOP attribute value
*/
private void actionsBgpUpdateInvalidNextHopAttribute(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message,
IpAddress nextHop) {
log.debug("BGP RX UPDATE Error from {}: Invalid NEXT_HOP Attribute {}",
remoteAddress, nextHop);
//
// ERROR: Invalid ORIGIN Attribute
//
// Send NOTIFICATION and close the connection
int errorCode = UpdateMessageError.ERROR_CODE;
int errorSubcode = UpdateMessageError.INVALID_NEXT_HOP_ATTRIBUTE;
ChannelBuffer data =
prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
attrFlags, message);
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, data);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
}
/**
* Applies the appropriate actions after detecting BGP UPDATE
* Unrecognized Well-known Attribute Error: send NOTIFICATION and close
* the channel.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message with the data
*/
private void actionsBgpUpdateUnrecognizedWellKnownAttribute(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message) {
log.debug("BGP RX UPDATE Error from {}: " +
"Unrecognized Well-known Attribute Error: {}",
remoteAddress, attrTypeCode);
//
// ERROR: Unrecognized Well-known Attribute
//
// Send NOTIFICATION and close the connection
int errorCode = UpdateMessageError.ERROR_CODE;
int errorSubcode =
UpdateMessageError.UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE;
ChannelBuffer data =
prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
attrFlags, message);
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, data);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
}
/**
* Applies the appropriate actions after detecting BGP UPDATE
* Attribute Length Error: send NOTIFICATION and close the channel.
*
* @param ctx the Channel Handler Context
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message with the data
*/
private void actionsBgpUpdateAttributeLengthError(
ChannelHandlerContext ctx,
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message) {
log.debug("BGP RX UPDATE Error from {}: Attribute Length Error",
remoteAddress);
//
// ERROR: Attribute Length Error
//
// Send NOTIFICATION and close the connection
int errorCode = UpdateMessageError.ERROR_CODE;
int errorSubcode = UpdateMessageError.ATTRIBUTE_LENGTH_ERROR;
ChannelBuffer data =
prepareBgpUpdateNotificationDataPayload(attrTypeCode, attrLen,
attrFlags, message);
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, data);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
}
/**
* Applies the appropriate actions after detecting BGP UPDATE
* Malformed AS_PATH Error: send NOTIFICATION and close the channel.
*
* @param ctx the Channel Handler Context
*/
private void actionsBgpUpdateMalformedAsPath(
ChannelHandlerContext ctx) {
log.debug("BGP RX UPDATE Error from {}: Malformed AS Path",
remoteAddress);
//
// ERROR: Malformed AS_PATH
//
// Send NOTIFICATION and close the connection
int errorCode = UpdateMessageError.ERROR_CODE;
int errorSubcode = UpdateMessageError.MALFORMED_AS_PATH;
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, null);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
}
/**
* Processes BGP NOTIFICATION message.
*
* @param ctx the Channel Handler Context
* @param message the message to process
*/
void processBgpNotification(ChannelHandlerContext ctx,
ChannelBuffer message) {
int minLength =
BgpConstants.BGP_NOTIFICATION_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
if (message.readableBytes() < minLength) {
log.debug("BGP RX NOTIFICATION Error from {}: " +
"Message length {} too short. Must be at least {}",
remoteAddress, message.readableBytes(), minLength);
//
// ERROR: Bad Message Length
//
// NOTE: We do NOT send NOTIFICATION in response to a notification
return;
}
//
// Parse the NOTIFICATION message
//
int errorCode = message.readUnsignedByte();
int errorSubcode = message.readUnsignedByte();
int dataLength = message.readableBytes();
log.debug("BGP RX NOTIFICATION message from {}: Error Code {} " +
"Error Subcode {} Data Length {}",
remoteAddress, errorCode, errorSubcode, dataLength);
//
// NOTE: If the peer sent a NOTIFICATION, we leave it to the peer to
// close the connection.
//
// Start the Session Timeout timer
restartSessionTimeoutTimer(ctx);
}
/**
* Processes BGP KEEPALIVE message.
*
* @param ctx the Channel Handler Context
* @param message the message to process
*/
void processBgpKeepalive(ChannelHandlerContext ctx,
ChannelBuffer message) {
if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH !=
BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) {
log.debug("BGP RX KEEPALIVE Error from {}: " +
"Invalid total message length {}. Expected {}",
remoteAddress,
message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH,
BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH);
//
// ERROR: Bad Message Length
//
// Send NOTIFICATION and close the connection
ChannelBuffer txMessage = prepareBgpNotificationBadMessageLength(
message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
return;
}
//
// Parse the KEEPALIVE message: nothing to do
//
log.debug("BGP RX KEEPALIVE message from {}", remoteAddress);
// Start the Session Timeout timer
restartSessionTimeoutTimer(ctx);
}
/**
* Prepares BGP OPEN message.
*
* @return the message to transmit (BGP header included)
*/
private ChannelBuffer prepareBgpOpen() {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
//
// Prepare the OPEN message payload
//
message.writeByte(localBgpVersion);
message.writeShort((int) localAs);
message.writeShort((int) localHoldtime);
message.writeInt(bgpSessionManager.getMyBgpId().toInt());
message.writeByte(0); // No Optional Parameters
return prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN, message);
}
/**
* Prepares BGP KEEPALIVE message.
*
* @return the message to transmit (BGP header included)
*/
private ChannelBuffer prepareBgpKeepalive() {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
//
// Prepare the KEEPALIVE message payload: nothing to do
//
return prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE, message);
}
/**
* Prepares BGP NOTIFICATION message.
*
* @param errorCode the BGP NOTIFICATION Error Code
* @param errorSubcode the BGP NOTIFICATION Error Subcode if applicable,
* otherwise BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC
* @param payload the BGP NOTIFICATION Data if applicable, otherwise null
* @return the message to transmit (BGP header included)
*/
ChannelBuffer prepareBgpNotification(int errorCode, int errorSubcode,
ChannelBuffer data) {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
//
// Prepare the NOTIFICATION message payload
//
message.writeByte(errorCode);
message.writeByte(errorSubcode);
if (data != null) {
message.writeBytes(data);
}
return prepareBgpMessage(BgpConstants.BGP_TYPE_NOTIFICATION, message);
}
/**
* Prepares BGP NOTIFICATION message: Bad Message Length.
*
* @param length the erroneous Length field
* @return the message to transmit (BGP header included)
*/
ChannelBuffer prepareBgpNotificationBadMessageLength(int length) {
int errorCode = MessageHeaderError.ERROR_CODE;
int errorSubcode = MessageHeaderError.BAD_MESSAGE_LENGTH;
ChannelBuffer data = ChannelBuffers.buffer(2);
data.writeShort(length);
return prepareBgpNotification(errorCode, errorSubcode, data);
}
/**
* Prepares BGP UPDATE Notification data payload.
*
* @param attrTypeCode the attribute type code
* @param attrLen the attribute length (in octets)
* @param attrFlags the attribute flags
* @param message the message with the data
* @return the buffer with the data payload for the BGP UPDATE Notification
*/
private ChannelBuffer prepareBgpUpdateNotificationDataPayload(
int attrTypeCode,
int attrLen,
int attrFlags,
ChannelBuffer message) {
// Compute the attribute length field octets
boolean extendedLengthBit = ((0x10 & attrFlags) != 0);
int attrLenOctets = 1;
if (extendedLengthBit) {
attrLenOctets = 2;
}
ChannelBuffer data =
ChannelBuffers.buffer(attrLen + attrLenOctets + 1);
data.writeByte(attrTypeCode);
if (extendedLengthBit) {
data.writeShort(attrLen);
} else {
data.writeByte(attrLen);
}
data.writeBytes(message, attrLen);
return data;
}
/**
* Prepares BGP message.
*
* @param type the BGP message type
* @param payload the message payload to transmit (BGP header excluded)
* @return the message to transmit (BGP header included)
*/
private ChannelBuffer prepareBgpMessage(int type, ChannelBuffer payload) {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_HEADER_LENGTH +
payload.readableBytes());
// Write the marker
for (int i = 0; i < BgpConstants.BGP_HEADER_MARKER_LENGTH; i++) {
message.writeByte(0xff);
}
// Write the rest of the BGP header
message.writeShort(BgpConstants.BGP_HEADER_LENGTH +
payload.readableBytes());
message.writeByte(type);
// Write the payload
message.writeBytes(payload);
return message;
}
/**
* Restarts the BGP KeepaliveTimer.
*/
private void restartKeepaliveTimer(ChannelHandlerContext ctx) {
if (localKeepaliveInterval == 0) {
return; // Nothing to do
}
keepaliveTimeout = timer.newTimeout(new TransmitKeepaliveTask(ctx),
localKeepaliveInterval,
TimeUnit.SECONDS);
}
/**
* Task class for transmitting KEEPALIVE messages.
*/
private final class TransmitKeepaliveTask implements TimerTask {
private final ChannelHandlerContext ctx;
/**
* Constructor for given Channel Handler Context.
*
* @param ctx the Channel Handler Context to use
*/
TransmitKeepaliveTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void run(Timeout timeout) throws Exception {
if (timeout.isCancelled()) {
return;
}
if (!ctx.getChannel().isOpen()) {
return;
}
// Transmit the KEEPALIVE
ChannelBuffer txMessage = prepareBgpKeepalive();
ctx.getChannel().write(txMessage);
// Restart the KEEPALIVE timer
restartKeepaliveTimer(ctx);
}
}
/**
* Restarts the BGP Session Timeout Timer.
*/
private void restartSessionTimeoutTimer(ChannelHandlerContext ctx) {
if (remoteHoldtime == 0) {
return; // Nothing to do
}
if (sessionTimeout != null) {
sessionTimeout.cancel();
}
sessionTimeout = timer.newTimeout(new SessionTimeoutTask(ctx),
remoteHoldtime,
TimeUnit.SECONDS);
}
/**
* Task class for BGP Session timeout.
*/
private final class SessionTimeoutTask implements TimerTask {
private final ChannelHandlerContext ctx;
/**
* Constructor for given Channel Handler Context.
*
* @param ctx the Channel Handler Context to use
*/
SessionTimeoutTask(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void run(Timeout timeout) throws Exception {
if (timeout.isCancelled()) {
return;
}
if (!ctx.getChannel().isOpen()) {
return;
}
log.debug("BGP Session Timeout: peer {}", remoteAddress);
//
// ERROR: Invalid Optional Parameter Length field: Unspecific
//
// Send NOTIFICATION and close the connection
int errorCode = HoldTimerExpired.ERROR_CODE;
int errorSubcode = Notifications.ERROR_SUBCODE_UNSPECIFIC;
ChannelBuffer txMessage =
prepareBgpNotification(errorCode, errorSubcode, null);
ctx.getChannel().write(txMessage);
closeChannel(ctx);
}
}
/**
* An exception indicating a parsing error of the BGP message.
*/
private static class BgpParseException extends Exception {
/**
* Default constructor.
*/
public BgpParseException() {
super();
}
/**
* Constructor for a specific exception details message.
*
* @param message the message with the exception details
*/
public BgpParseException(String message) {
super(message);
}
}
}
package org.onlab.onos.sdnip.bgp;
import static com.google.common.base.Preconditions.checkNotNull;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelException;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.onlab.onos.sdnip.RouteListener;
import org.onlab.onos.sdnip.RouteUpdate;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* BGP Session Manager class.
*/
public class BgpSessionManager {
private static final Logger log =
LoggerFactory.getLogger(BgpSessionManager.class);
private Channel serverChannel; // Listener for incoming BGP connections
private ConcurrentMap<SocketAddress, BgpSession> bgpSessions =
new ConcurrentHashMap<>();
private IpAddress myBgpId; // Same BGP ID for all peers
private BgpRouteSelector bgpRouteSelector = new BgpRouteSelector();
private ConcurrentMap<IpPrefix, BgpRouteEntry> bgpRoutes =
new ConcurrentHashMap<>();
private final RouteListener routeListener;
/**
* Constructor for given route listener.
*
* @param routeListener the route listener to use
*/
public BgpSessionManager(RouteListener routeListener) {
this.routeListener = checkNotNull(routeListener);
}
/**
* Gets the BGP sessions.
*
* @return the BGP sessions
*/
public Collection<BgpSession> getBgpSessions() {
return bgpSessions.values();
}
/**
* Gets the BGP routes.
*
* @return the BGP routes
*/
public Collection<BgpRouteEntry> getBgpRoutes() {
return bgpRoutes.values();
}
/**
* Processes the connection from a BGP peer.
*
* @param bgpSession the BGP session for the peer
* @return true if the connection can be established, otherwise false
*/
boolean peerConnected(BgpSession bgpSession) {
// Test whether there is already a session from the same remote
if (bgpSessions.get(bgpSession.getRemoteAddress()) != null) {
return false; // Duplicate BGP session
}
bgpSessions.put(bgpSession.getRemoteAddress(), bgpSession);
//
// If the first connection, set my BGP ID to the local address
// of the socket.
//
if (bgpSession.getLocalAddress() instanceof InetSocketAddress) {
InetAddress inetAddr =
((InetSocketAddress) bgpSession.getLocalAddress()).getAddress();
IpAddress ip4Address = IpAddress.valueOf(inetAddr.getAddress());
updateMyBgpId(ip4Address);
}
return true;
}
/**
* Processes the disconnection from a BGP peer.
*
* @param bgpSession the BGP session for the peer
*/
void peerDisconnected(BgpSession bgpSession) {
bgpSessions.remove(bgpSession.getRemoteAddress());
}
/**
* Conditionally updates the local BGP ID if it wasn't set already.
* <p/>
* NOTE: A BGP instance should use same BGP ID across all BGP sessions.
*
* @param ip4Address the IPv4 address to use as BGP ID
*/
private synchronized void updateMyBgpId(IpAddress ip4Address) {
if (myBgpId == null) {
myBgpId = ip4Address;
log.debug("BGP: My BGP ID is {}", myBgpId);
}
}
/**
* Gets the local BGP Identifier as an IPv4 address.
*
* @return the local BGP Identifier as an IPv4 address
*/
IpAddress getMyBgpId() {
return myBgpId;
}
/**
* Gets the BGP Route Selector.
*
* @return the BGP Route Selector
*/
BgpRouteSelector getBgpRouteSelector() {
return bgpRouteSelector;
}
/**
* Starts up BGP Session Manager operation.
*
* @param listenPortNumber the port number to listen on. By default
* it should be BgpConstants.BGP_PORT (179)
*/
public void startUp(int listenPortNumber) {
log.debug("BGP Session Manager startUp()");
ChannelFactory channelFactory =
new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
// Allocate a new session per connection
BgpSession bgpSessionHandler =
new BgpSession(BgpSessionManager.this);
BgpFrameDecoder bgpFrameDecoder =
new BgpFrameDecoder(bgpSessionHandler);
// Setup the processing pipeline
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("BgpFrameDecoder", bgpFrameDecoder);
pipeline.addLast("BgpSession", bgpSessionHandler);
return pipeline;
}
};
InetSocketAddress listenAddress =
new InetSocketAddress(listenPortNumber);
ServerBootstrap serverBootstrap = new ServerBootstrap(channelFactory);
// serverBootstrap.setOptions("reuseAddr", true);
serverBootstrap.setOption("child.keepAlive", true);
serverBootstrap.setOption("child.tcpNoDelay", true);
serverBootstrap.setPipelineFactory(pipelineFactory);
try {
serverChannel = serverBootstrap.bind(listenAddress);
} catch (ChannelException e) {
log.debug("Exception binding to BGP port {}: ",
listenAddress.getPort(), e);
}
}
/**
* Shuts down the BGP Session Manager operation.
*/
public void shutDown() {
// TODO: Complete the implementation: remove routes, etc.
if (serverChannel != null) {
serverChannel.close();
}
}
/**
* Class to receive and process the BGP routes from each BGP Session/Peer.
*/
class BgpRouteSelector {
/**
* Processes route entry updates: added/updated and deleted route
* entries.
*
* @param bgpSession the BGP session the route entry updates were
* received on
* @param addedBgpRouteEntries the added/updated route entries to
* process
* @param deletedBgpRouteEntries the deleted route entries to process
*/
synchronized void routeUpdates(BgpSession bgpSession,
Collection<BgpRouteEntry> addedBgpRouteEntries,
Collection<BgpRouteEntry> deletedBgpRouteEntries) {
//
// TODO: Merge the updates from different BGP Peers,
// by choosing the best route.
//
// Process the deleted route entries
for (BgpRouteEntry bgpRouteEntry : deletedBgpRouteEntries) {
processDeletedRoute(bgpSession, bgpRouteEntry);
}
// Process the added/updated route entries
for (BgpRouteEntry bgpRouteEntry : addedBgpRouteEntries) {
processAddedRoute(bgpSession, bgpRouteEntry);
}
}
/**
* Processes an added/updated route entry.
*
* @param bgpSession the BGP session the route entry update was
* received on
* @param bgpRouteEntry the added/updated route entry
*/
private void processAddedRoute(BgpSession bgpSession,
BgpRouteEntry bgpRouteEntry) {
RouteUpdate routeUpdate;
BgpRouteEntry bestBgpRouteEntry =
bgpRoutes.get(bgpRouteEntry.prefix());
//
// Install the new route entry if it is better than the
// current best route.
//
if ((bestBgpRouteEntry == null) ||
bgpRouteEntry.isBetterThan(bestBgpRouteEntry)) {
bgpRoutes.put(bgpRouteEntry.prefix(), bgpRouteEntry);
routeUpdate =
new RouteUpdate(RouteUpdate.Type.UPDATE, bgpRouteEntry);
// Forward the result route updates to the Route Listener
routeListener.update(routeUpdate);
return;
}
//
// If the route entry arrived on the same BGP Session as
// the current best route, then elect the next best route
// and install it.
//
if (bestBgpRouteEntry.getBgpSession() !=
bgpRouteEntry.getBgpSession()) {
return;
}
// Find the next best route
bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
if (bestBgpRouteEntry == null) {
//
// TODO: Shouldn't happen. Install the new route as a
// pre-caution.
//
log.debug("BGP next best route for prefix {} is missing. " +
"Adding the route that is currently processed.",
bgpRouteEntry.prefix());
bestBgpRouteEntry = bgpRouteEntry;
}
// Install the next best route
bgpRoutes.put(bestBgpRouteEntry.prefix(), bestBgpRouteEntry);
routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
bestBgpRouteEntry);
// Forward the result route updates to the Route Listener
routeListener.update(routeUpdate);
}
/**
* Processes a deleted route entry.
*
* @param bgpSession the BGP session the route entry update was
* received on
* @param bgpRouteEntry the deleted route entry
*/
private void processDeletedRoute(BgpSession bgpSession,
BgpRouteEntry bgpRouteEntry) {
RouteUpdate routeUpdate;
BgpRouteEntry bestBgpRouteEntry =
bgpRoutes.get(bgpRouteEntry.prefix());
//
// Remove the route entry only if it was the best one.
// Install the the next best route if it exists.
//
// NOTE: We intentionally use "==" instead of method equals(),
// because we need to check whether this is same object.
//
if (bgpRouteEntry != bestBgpRouteEntry) {
return; // Nothing to do
}
//
// Find the next best route
//
bestBgpRouteEntry = findBestBgpRoute(bgpRouteEntry.prefix());
if (bestBgpRouteEntry != null) {
// Install the next best route
bgpRoutes.put(bestBgpRouteEntry.prefix(),
bestBgpRouteEntry);
routeUpdate = new RouteUpdate(RouteUpdate.Type.UPDATE,
bestBgpRouteEntry);
// Forward the result route updates to the Route Listener
routeListener.update(routeUpdate);
return;
}
//
// No route found. Remove the route entry
//
bgpRoutes.remove(bgpRouteEntry.prefix());
routeUpdate = new RouteUpdate(RouteUpdate.Type.DELETE,
bgpRouteEntry);
// Forward the result route updates to the Route Listener
routeListener.update(routeUpdate);
}
/**
* Finds the best route entry among all BGP Sessions.
*
* @param prefix the prefix of the route
* @return the best route if found, otherwise null
*/
private BgpRouteEntry findBestBgpRoute(IpPrefix prefix) {
BgpRouteEntry bestRoute = null;
// Iterate across all BGP Sessions and select the best route
for (BgpSession bgpSession : bgpSessions.values()) {
BgpRouteEntry route = bgpSession.findBgpRouteEntry(prefix);
if (route == null) {
continue;
}
if ((bestRoute == null) || route.isBetterThan(bestRoute)) {
bestRoute = route;
}
}
return bestRoute;
}
}
}
/**
* Implementation of the BGP protocol.
*/
package org.onlab.onos.sdnip.bgp;
\ No newline at end of file
package org.onlab.onos.sdnip.cli;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.sdnip.SdnIpService;
import org.onlab.onos.sdnip.bgp.BgpConstants;
import org.onlab.onos.sdnip.bgp.BgpRouteEntry;
/**
* Command to show the routes learned through BGP.
*/
@Command(scope = "onos", name = "bgp-routes",
description = "Lists all routes received from BGP")
public class BgpRoutesListCommand extends AbstractShellCommand {
private static final String FORMAT =
"prefix=%s, nexthop=%s, origin=%s, localpref=%s, med=%s, aspath=%s, bgpid=%s";
@Override
protected void execute() {
SdnIpService service = get(SdnIpService.class);
for (BgpRouteEntry route : service.getBgpRoutes()) {
printRoute(route);
}
}
private void printRoute(BgpRouteEntry route) {
if (route != null) {
print(FORMAT, route.prefix(), route.nextHop(),
originToString(route.getOrigin()), route.getLocalPref(),
route.getMultiExitDisc(), route.getAsPath(),
route.getBgpSession().getRemoteBgpId());
}
}
private static String originToString(int origin) {
String originString = "UNKNOWN";
switch (origin) {
case BgpConstants.Update.Origin.IGP:
originString = "IGP";
break;
case BgpConstants.Update.Origin.EGP:
originString = "EGP";
break;
case BgpConstants.Update.Origin.INCOMPLETE:
originString = "INCOMPLETE";
break;
default:
break;
}
return originString;
}
}
package org.onlab.onos.sdnip.cli;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.sdnip.RouteEntry;
import org.onlab.onos.sdnip.SdnIpService;
/**
* Command to show the list of routes in SDN-IP's routing table.
*/
@Command(scope = "onos", name = "routes",
description = "Lists all routes known to SDN-IP")
public class RoutesListCommand extends AbstractShellCommand {
private static final String FORMAT =
"prefix=%s, nexthop=%s";
@Override
protected void execute() {
SdnIpService service = get(SdnIpService.class);
for (RouteEntry route : service.getRoutes()) {
printRoute(route);
}
}
private void printRoute(RouteEntry route) {
if (route != null) {
print(FORMAT, route.prefix(), route.nextHop());
}
}
}
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
<command>
<action class="org.onlab.onos.sdnip.cli.BgpRoutesListCommand"/>
</command>
<command>
<action class="org.onlab.onos.sdnip.cli.RoutesListCommand"/>
</command>
</command-bundle>
</blueprint>
package org.onlab.onos.sdnip;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reportMatcher;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.easymock.IArgumentMatcher;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.flow.DefaultTrafficSelector;
import org.onlab.onos.net.flow.DefaultTrafficTreatment;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.intent.IntentId;
import org.onlab.onos.net.intent.IntentService;
import org.onlab.onos.net.intent.PointToPointIntent;
import org.onlab.onos.sdnip.bgp.BgpConstants;
import org.onlab.onos.sdnip.config.BgpPeer;
import org.onlab.onos.sdnip.config.BgpSpeaker;
import org.onlab.onos.sdnip.config.Interface;
import org.onlab.onos.sdnip.config.InterfaceAddress;
import org.onlab.onos.sdnip.config.SdnIpConfigService;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import com.google.common.collect.Sets;
/**
* Unit tests for PeerConnectivityManager interface.
*/
public class PeerConnectivityManagerTest {
private PeerConnectivityManager peerConnectivityManager;
private IntentService intentService;
private SdnIpConfigService configInfoService;
private InterfaceService interfaceService;
private Map<String, BgpSpeaker> bgpSpeakers;
private Map<String, Interface> interfaces;
private Map<IpAddress, BgpPeer> peers;
private Map<String, BgpSpeaker> configuredBgpSpeakers;
private Map<String, Interface> configuredInterfaces;
private Map<IpAddress, BgpPeer> configuredPeers;
private List<PointToPointIntent> intentList;
private final String dpid1 = "00:00:00:00:00:00:00:01";
private final String dpid2 = "00:00:00:00:00:00:00:02";
private final DeviceId deviceId1 =
DeviceId.deviceId(SdnIp.dpidToUri(dpid1));
private final DeviceId deviceId2 =
DeviceId.deviceId(SdnIp.dpidToUri(dpid2));
// Interfaces connected to BGP speakers
private final ConnectPoint s1Eth100 =
new ConnectPoint(deviceId1, PortNumber.portNumber(100));
private final ConnectPoint s2Eth100 =
new ConnectPoint(deviceId2, PortNumber.portNumber(100));
// Interfaces connected to BGP peers
private final ConnectPoint s1Eth1 =
new ConnectPoint(deviceId1, PortNumber.portNumber(1));
private final ConnectPoint s2Eth1 =
new ConnectPoint(deviceId2, PortNumber.portNumber(1));
// We don't compare the intent ID so all expected intents can use the same ID
private final IntentId testIntentId = new IntentId(0);
private final TrafficTreatment noTreatment =
DefaultTrafficTreatment.builder().build();
@Before
public void setUp() throws Exception {
bgpSpeakers = Collections.unmodifiableMap(setUpBgpSpeakers());
interfaces = Collections.unmodifiableMap(setUpInterfaces());
peers = Collections.unmodifiableMap(setUpPeers());
initPeerConnectivity();
intentList = setUpIntentList();
}
/**
* Sets up BGP speakers.
*
* @return configured BGP speakers as a map from speaker name to speaker
*/
private Map<String, BgpSpeaker> setUpBgpSpeakers() {
configuredBgpSpeakers = new HashMap<>();
BgpSpeaker bgpSpeaker1 = new BgpSpeaker(
"bgpSpeaker1",
"00:00:00:00:00:00:00:01", 100,
"00:00:00:00:00:01");
List<InterfaceAddress> interfaceAddresses1 =
new LinkedList<InterfaceAddress>();
interfaceAddresses1.add(new InterfaceAddress(dpid1, 1, "192.168.10.101"));
interfaceAddresses1.add(new InterfaceAddress(dpid2, 1, "192.168.20.101"));
bgpSpeaker1.setInterfaceAddresses(interfaceAddresses1);
configuredBgpSpeakers.put(bgpSpeaker1.name(), bgpSpeaker1);
// BGP speaker2 is attached to the same switch port with speaker1
BgpSpeaker bgpSpeaker2 = new BgpSpeaker(
"bgpSpeaker2",
"00:00:00:00:00:00:00:01", 100,
"00:00:00:00:00:02");
List<InterfaceAddress> interfaceAddresses2 =
new LinkedList<InterfaceAddress>();
interfaceAddresses2.add(new InterfaceAddress(dpid1, 1, "192.168.10.102"));
interfaceAddresses2.add(new InterfaceAddress(dpid2, 1, "192.168.20.102"));
bgpSpeaker2.setInterfaceAddresses(interfaceAddresses2);
configuredBgpSpeakers.put(bgpSpeaker2.name(), bgpSpeaker2);
BgpSpeaker bgpSpeaker3 = new BgpSpeaker(
"bgpSpeaker3",
"00:00:00:00:00:00:00:02", 100,
"00:00:00:00:00:03");
List<InterfaceAddress> interfaceAddresses3 =
new LinkedList<InterfaceAddress>();
interfaceAddresses3.add(new InterfaceAddress(dpid1, 1, "192.168.10.103"));
interfaceAddresses3.add(new InterfaceAddress(dpid2, 1, "192.168.20.103"));
bgpSpeaker3.setInterfaceAddresses(interfaceAddresses3);
configuredBgpSpeakers.put(bgpSpeaker3.name(), bgpSpeaker3);
return configuredBgpSpeakers;
}
/**
* Sets up logical interfaces, which emulate the configured interfaces
* in SDN-IP application.
*
* @return configured interfaces as a MAP from Interface name to Interface
*/
private Map<String, Interface> setUpInterfaces() {
configuredInterfaces = new HashMap<>();
String interfaceSw1Eth1 = "s1-eth1";
Interface intfsw1eth1 = new Interface(s1Eth1,
Collections.singleton(IpPrefix.valueOf("192.168.10.0/24")),
MacAddress.valueOf("00:00:00:00:00:01"));
configuredInterfaces.put(interfaceSw1Eth1, intfsw1eth1);
String interfaceSw2Eth1 = "s2-eth1";
Interface intfsw2eth1 = new Interface(s2Eth1,
Collections.singleton(IpPrefix.valueOf("192.168.20.0/24")),
MacAddress.valueOf("00:00:00:00:00:02"));
configuredInterfaces.put(interfaceSw2Eth1, intfsw2eth1);
interfaceService = createMock(InterfaceService.class);
expect(interfaceService.getInterface(s1Eth1))
.andReturn(intfsw1eth1).anyTimes();
expect(interfaceService.getInterface(s2Eth1))
.andReturn(intfsw2eth1).anyTimes();
// Non-existent interface used during one of the tests
expect(interfaceService.getInterface(new ConnectPoint(
DeviceId.deviceId(SdnIp.dpidToUri("00:00:00:00:00:00:01:00")),
PortNumber.portNumber(1))))
.andReturn(null).anyTimes();
expect(interfaceService.getInterfaces()).andReturn(
Sets.newHashSet(configuredInterfaces.values())).anyTimes();
replay(interfaceService);
return configuredInterfaces;
}
/**
* Sets up BGP daemon peers.
*
* @return configured BGP peers as a MAP from peer IP address to BgpPeer
*/
private Map<IpAddress, BgpPeer> setUpPeers() {
configuredPeers = new HashMap<>();
String peerSw1Eth1 = "192.168.10.1";
configuredPeers.put(IpAddress.valueOf(peerSw1Eth1),
new BgpPeer(dpid1, 1, peerSw1Eth1));
// Two BGP peers are connected to switch 2 port 1.
String peer1Sw2Eth1 = "192.168.20.1";
configuredPeers.put(IpAddress.valueOf(peer1Sw2Eth1),
new BgpPeer(dpid2, 1, peer1Sw2Eth1));
String peer2Sw2Eth1 = "192.168.20.2";
configuredPeers.put(IpAddress.valueOf(peer2Sw2Eth1),
new BgpPeer(dpid2, 1, peer2Sw2Eth1));
return configuredPeers;
}
/**
* Sets up expected point to point intent list.
*
* @return point to point intent list
*/
private List<PointToPointIntent> setUpIntentList() {
intentList = new ArrayList<PointToPointIntent>();
setUpBgpIntents();
setUpIcmpIntents();
return intentList;
}
/**
* Constructs a BGP intent and put it into the intentList.
* <p/>
* The purpose of this method is too simplify the setUpBgpIntents() method,
* and to make the setUpBgpIntents() easy to read.
*
* @param srcPrefix source IP prefix to match
* @param dstPrefix destination IP prefix to match
* @param srcTcpPort source TCP port to match
* @param dstTcpPort destination TCP port to match
* @param srcConnectPoint source connect point for PointToPointIntent
* @param dstConnectPoint destination connect point for PointToPointIntent
*/
private void bgpPathintentConstructor(String srcPrefix, String dstPrefix,
Short srcTcpPort, Short dstTcpPort,
ConnectPoint srcConnectPoint, ConnectPoint dstConnectPoint) {
TrafficSelector.Builder builder = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_TCP)
.matchIPSrc(IpPrefix.valueOf(srcPrefix))
.matchIPDst(IpPrefix.valueOf(dstPrefix));
if (srcTcpPort != null) {
builder.matchTcpSrc(srcTcpPort);
}
if (dstTcpPort != null) {
builder.matchTcpDst(dstTcpPort);
}
PointToPointIntent intent = new PointToPointIntent(
testIntentId, builder.build(), noTreatment,
srcConnectPoint, dstConnectPoint);
intentList.add(intent);
}
/**
* Sets up intents for BGP paths.
*/
private void setUpBgpIntents() {
Short bgpPort = Short.valueOf((short) BgpConstants.BGP_PORT);
// Start to build intents between BGP speaker1 and BGP peer1
bgpPathintentConstructor(
"192.168.10.101/32", "192.168.10.1/32", null, bgpPort,
s1Eth100, s1Eth1);
bgpPathintentConstructor(
"192.168.10.101/32", "192.168.10.1/32", bgpPort, null,
s1Eth100, s1Eth1);
bgpPathintentConstructor(
"192.168.10.1/32", "192.168.10.101/32", null, bgpPort,
s1Eth1, s1Eth100);
bgpPathintentConstructor(
"192.168.10.1/32", "192.168.10.101/32", bgpPort, null,
s1Eth1, s1Eth100);
// Start to build intents between BGP speaker1 and BGP peer2
bgpPathintentConstructor(
"192.168.20.101/32", "192.168.20.1/32", null, bgpPort,
s1Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.101/32", "192.168.20.1/32", bgpPort, null,
s1Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.1/32", "192.168.20.101/32", null, bgpPort,
s2Eth1, s1Eth100);
bgpPathintentConstructor(
"192.168.20.1/32", "192.168.20.101/32", bgpPort, null,
s2Eth1, s1Eth100);
// Start to build intents between BGP speaker1 and BGP peer3
bgpPathintentConstructor(
"192.168.20.101/32", "192.168.20.2/32", null, bgpPort,
s1Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.101/32", "192.168.20.2/32", bgpPort, null,
s1Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.2/32", "192.168.20.101/32", null, bgpPort,
s2Eth1, s1Eth100);
bgpPathintentConstructor(
"192.168.20.2/32", "192.168.20.101/32", bgpPort, null,
s2Eth1, s1Eth100);
//
// Start to build intents between BGP speaker2 and BGP peer1
bgpPathintentConstructor(
"192.168.10.102/32", "192.168.10.1/32", null, bgpPort,
s1Eth100, s1Eth1);
bgpPathintentConstructor(
"192.168.10.102/32", "192.168.10.1/32", bgpPort, null,
s1Eth100, s1Eth1);
bgpPathintentConstructor(
"192.168.10.1/32", "192.168.10.102/32", null, bgpPort,
s1Eth1, s1Eth100);
bgpPathintentConstructor(
"192.168.10.1/32", "192.168.10.102/32", bgpPort, null,
s1Eth1, s1Eth100);
// Start to build intents between BGP speaker2 and BGP peer2
bgpPathintentConstructor(
"192.168.20.102/32", "192.168.20.1/32", null, bgpPort,
s1Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.102/32", "192.168.20.1/32", bgpPort, null,
s1Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.1/32", "192.168.20.102/32", null, bgpPort,
s2Eth1, s1Eth100);
bgpPathintentConstructor(
"192.168.20.1/32", "192.168.20.102/32", bgpPort, null,
s2Eth1, s1Eth100);
// Start to build intents between BGP speaker2 and BGP peer3
bgpPathintentConstructor(
"192.168.20.102/32", "192.168.20.2/32", null, bgpPort,
s1Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.102/32", "192.168.20.2/32", bgpPort, null,
s1Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.2/32", "192.168.20.102/32", null, bgpPort,
s2Eth1, s1Eth100);
bgpPathintentConstructor(
"192.168.20.2/32", "192.168.20.102/32", bgpPort, null,
s2Eth1, s1Eth100);
//
// Start to build intents between BGP speaker3 and BGP peer1
bgpPathintentConstructor(
"192.168.10.103/32", "192.168.10.1/32", null, bgpPort,
s2Eth100, s1Eth1);
bgpPathintentConstructor(
"192.168.10.103/32", "192.168.10.1/32", bgpPort, null,
s2Eth100, s1Eth1);
bgpPathintentConstructor(
"192.168.10.1/32", "192.168.10.103/32", null, bgpPort,
s1Eth1, s2Eth100);
bgpPathintentConstructor(
"192.168.10.1/32", "192.168.10.103/32", bgpPort, null,
s1Eth1, s2Eth100);
// Start to build intents between BGP speaker3 and BGP peer2
bgpPathintentConstructor(
"192.168.20.103/32", "192.168.20.1/32", null, bgpPort,
s2Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.103/32", "192.168.20.1/32", bgpPort, null,
s2Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.1/32", "192.168.20.103/32", null, bgpPort,
s2Eth1, s2Eth100);
bgpPathintentConstructor(
"192.168.20.1/32", "192.168.20.103/32", bgpPort, null,
s2Eth1, s2Eth100);
// Start to build intents between BGP speaker3 and BGP peer3
bgpPathintentConstructor(
"192.168.20.103/32", "192.168.20.2/32", null, bgpPort,
s2Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.103/32", "192.168.20.2/32", bgpPort, null,
s2Eth100, s2Eth1);
bgpPathintentConstructor(
"192.168.20.2/32", "192.168.20.103/32", null, bgpPort,
s2Eth1, s2Eth100);
bgpPathintentConstructor(
"192.168.20.2/32", "192.168.20.103/32", bgpPort, null,
s2Eth1, s2Eth100);
}
/**
* Constructs a BGP intent and put it into the intentList.
* <p/>
* The purpose of this method is too simplify the setUpBgpIntents() method,
* and to make the setUpBgpIntents() easy to read.
*
* @param srcPrefix source IP prefix to match
* @param dstPrefix destination IP prefix to match
* @param srcConnectPoint source connect point for PointToPointIntent
* @param dstConnectPoint destination connect point for PointToPointIntent
*/
private void icmpPathintentConstructor(String srcPrefix, String dstPrefix,
ConnectPoint srcConnectPoint, ConnectPoint dstConnectPoint) {
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPProtocol(IPv4.PROTOCOL_ICMP)
.matchIPSrc(IpPrefix.valueOf(srcPrefix))
.matchIPDst(IpPrefix.valueOf(dstPrefix))
.build();
PointToPointIntent intent = new PointToPointIntent(
testIntentId, selector, noTreatment,
srcConnectPoint, dstConnectPoint);
intentList.add(intent);
}
/**
* Sets up intents for ICMP paths.
*/
private void setUpIcmpIntents() {
// Start to build intents between BGP speaker1 and BGP peer1
icmpPathintentConstructor(
"192.168.10.101/32", "192.168.10.1/32", s1Eth100, s1Eth1);
icmpPathintentConstructor(
"192.168.10.1/32", "192.168.10.101/32", s1Eth1, s1Eth100);
// Start to build intents between BGP speaker1 and BGP peer2
icmpPathintentConstructor(
"192.168.20.101/32", "192.168.20.1/32", s1Eth100, s2Eth1);
icmpPathintentConstructor(
"192.168.20.1/32", "192.168.20.101/32", s2Eth1, s1Eth100);
// Start to build intents between BGP speaker1 and BGP peer3
icmpPathintentConstructor(
"192.168.20.101/32", "192.168.20.2/32", s1Eth100, s2Eth1);
icmpPathintentConstructor(
"192.168.20.2/32", "192.168.20.101/32", s2Eth1, s1Eth100);
//
// Start to build intents between BGP speaker2 and BGP peer1
icmpPathintentConstructor(
"192.168.10.102/32", "192.168.10.1/32", s1Eth100, s1Eth1);
icmpPathintentConstructor(
"192.168.10.1/32", "192.168.10.102/32", s1Eth1, s1Eth100);
// Start to build intents between BGP speaker2 and BGP peer2
icmpPathintentConstructor(
"192.168.20.102/32", "192.168.20.1/32", s1Eth100, s2Eth1);
icmpPathintentConstructor(
"192.168.20.1/32", "192.168.20.102/32", s2Eth1, s1Eth100);
// Start to build intents between BGP speaker2 and BGP peer3
icmpPathintentConstructor(
"192.168.20.102/32", "192.168.20.2/32", s1Eth100, s2Eth1);
icmpPathintentConstructor(
"192.168.20.2/32", "192.168.20.102/32", s2Eth1, s1Eth100);
//
// Start to build intents between BGP speaker3 and BGP peer1
icmpPathintentConstructor(
"192.168.10.103/32", "192.168.10.1/32", s2Eth100, s1Eth1);
icmpPathintentConstructor(
"192.168.10.1/32", "192.168.10.103/32", s1Eth1, s2Eth100);
// Start to build intents between BGP speaker3 and BGP peer2
icmpPathintentConstructor(
"192.168.20.103/32", "192.168.20.1/32", s2Eth100, s2Eth1);
icmpPathintentConstructor(
"192.168.20.1/32", "192.168.20.103/32", s2Eth1, s2Eth100);
// Start to build intents between BGP speaker3 and BGP peer3
icmpPathintentConstructor(
"192.168.20.103/32", "192.168.20.2/32", s2Eth100, s2Eth1);
icmpPathintentConstructor(
"192.168.20.2/32", "192.168.20.103/32", s2Eth1, s2Eth100);
}
/**
* Initializes peer connectivity testing environment.
*/
private void initPeerConnectivity() {
configInfoService = createMock(SdnIpConfigService.class);
expect(configInfoService.getBgpPeers()).andReturn(peers).anyTimes();
expect(configInfoService.getBgpSpeakers()).andReturn(bgpSpeakers).anyTimes();
replay(configInfoService);
intentService = createMock(IntentService.class);
replay(intentService);
peerConnectivityManager = new PeerConnectivityManager(configInfoService,
interfaceService, intentService);
}
/*
* EasyMock matcher that matches {@link PointToPointIntent}s but
* ignores the {@link IntentId} when matching.
* <p/>
* The normal intent equals method tests that the intent IDs are equal,
* however in these tests we can't know what the intent IDs will be in
* advance, so we can't set up expected intents with the correct IDs. Thus,
* the solution is to use an EasyMock matcher that verifies that all the
* value properties of the provided intent match the expected values, but
* ignores the intent ID when testing equality.
*/
private static final class IdAgnosticPointToPointIntentMatcher implements
IArgumentMatcher {
private final PointToPointIntent intent;
private String providedIntentString;
/**
* Constructor taking the expected intent to match against.
*
* @param intent the expected intent
*/
public IdAgnosticPointToPointIntentMatcher(PointToPointIntent intent) {
this.intent = intent;
}
@Override
public void appendTo(StringBuffer strBuffer) {
strBuffer.append("PointToPointIntentMatcher unable to match: "
+ providedIntentString);
}
@Override
public boolean matches(Object object) {
if (!(object instanceof PointToPointIntent)) {
return false;
}
PointToPointIntent providedIntent = (PointToPointIntent) object;
providedIntentString = providedIntent.toString();
PointToPointIntent matchIntent =
new PointToPointIntent(providedIntent.id(),
intent.selector(), intent.treatment(),
intent.ingressPoint(), intent.egressPoint());
return matchIntent.equals(providedIntent);
}
}
/**
* Matcher method to set an expected intent to match against (ignoring the
* the intent ID).
*
* @param intent the expected intent
* @return something of type PointToPointIntent
*/
private static PointToPointIntent eqExceptId(
PointToPointIntent intent) {
reportMatcher(new IdAgnosticPointToPointIntentMatcher(intent));
return null;
}
/**
* Tests whether peer connectivity manager can set up correct BGP and
* ICMP intents according to specific configuration.
* <p/>
* Two tricky cases included in the configuration are: 2 peers on a same
* switch port, peer on the same switch with BGPd.
*/
@Test
public void testConnectionSetup() {
reset(intentService);
// Sets up the expected PointToPoint intents.
for (int i = 0; i < intentList.size(); i++) {
intentService.submit(eqExceptId(intentList.get(i)));
}
replay(intentService);
// Running the interface to be tested.
peerConnectivityManager.start();
verify(intentService);
}
/**
* Tests a corner case, when there are no interfaces in the configuration.
*/
@Test
public void testNullInterfaces() {
reset(interfaceService);
expect(interfaceService.getInterfaces()).andReturn(
Sets.<Interface>newHashSet()).anyTimes();
expect(interfaceService.getInterface(s2Eth1))
.andReturn(null).anyTimes();
expect(interfaceService.getInterface(s1Eth1))
.andReturn(null).anyTimes();
replay(interfaceService);
reset(configInfoService);
expect(configInfoService.getBgpPeers()).andReturn(peers).anyTimes();
expect(configInfoService.getBgpSpeakers()).andReturn(bgpSpeakers).anyTimes();
replay(configInfoService);
reset(intentService);
replay(intentService);
peerConnectivityManager.start();
verify(intentService);
}
/**
* Tests a corner case, when there are no BGP peers in the configuration.
*/
@Test
public void testNullBgpPeers() {
reset(interfaceService);
expect(interfaceService.getInterfaces()).andReturn(
Sets.newHashSet(interfaces.values())).anyTimes();
replay(interfaceService);
reset(configInfoService);
expect(configInfoService.getBgpPeers()).andReturn(
new HashMap<IpAddress, BgpPeer>()).anyTimes();
expect(configInfoService.getBgpSpeakers()).andReturn(
bgpSpeakers).anyTimes();
replay(configInfoService);
reset(intentService);
replay(intentService);
peerConnectivityManager.start();
verify(intentService);
}
/**
* Tests a corner case, when there is no BGP speakers in the configuration.
*/
@Test
public void testNullBgpSpeakers() {
reset(interfaceService);
expect(interfaceService.getInterfaces()).andReturn(
Sets.newHashSet(interfaces.values())).anyTimes();
replay(interfaceService);
reset(configInfoService);
expect(configInfoService.getBgpPeers()).andReturn(
peers).anyTimes();
expect(configInfoService.getBgpSpeakers()).andReturn(
null).anyTimes();
replay(configInfoService);
reset(intentService);
replay(intentService);
peerConnectivityManager.start();
verify(intentService);
}
/**
* Tests a corner case, when there is no Interface configured for one BGP
* peer.
*/
@Test
public void testNoPeerInterface() {
String peerSw100Eth1 = "192.168.200.1";
configuredPeers.put(IpAddress.valueOf(peerSw100Eth1),
new BgpPeer("00:00:00:00:00:00:01:00", 1, peerSw100Eth1));
testConnectionSetup();
}
/**
* Tests a corner case, when there is no Interface configured for one BGP
* speaker.
* TODO: we should add a configuration correctness checking module/method
* before testing this corner case.
*/
@Ignore
@Test
public void testNoSpeakerInterface() {
BgpSpeaker bgpSpeaker100 = new BgpSpeaker(
"bgpSpeaker100",
"00:00:00:00:00:00:01:00", 100,
"00:00:00:00:01:00");
List<InterfaceAddress> interfaceAddresses100 =
new LinkedList<InterfaceAddress>();
interfaceAddresses100.add(new InterfaceAddress(dpid1, 1, "192.168.10.201"));
interfaceAddresses100.add(new InterfaceAddress(dpid2, 1, "192.168.20.201"));
bgpSpeaker100.setInterfaceAddresses(interfaceAddresses100);
configuredBgpSpeakers.put(bgpSpeaker100.name(), bgpSpeaker100);
testConnectionSetup();
}
}
package org.onlab.onos.sdnip;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
/**
* Unit tests for the RouteEntry class.
*/
public class RouteEntryTest {
/**
* Tests valid class constructor.
*/
@Test
public void testConstructor() {
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
RouteEntry routeEntry = new RouteEntry(prefix, nextHop);
assertThat(routeEntry.toString(),
is("RouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8}"));
}
/**
* Tests invalid class constructor for null IPv4 prefix.
*/
@Test(expected = NullPointerException.class)
public void testInvalidConstructorNullPrefix() {
IpPrefix prefix = null;
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
new RouteEntry(prefix, nextHop);
}
/**
* Tests invalid class constructor for null IPv4 next-hop.
*/
@Test(expected = NullPointerException.class)
public void testInvalidConstructorNullNextHop() {
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = null;
new RouteEntry(prefix, nextHop);
}
/**
* Tests getting the fields of a route entry.
*/
@Test
public void testGetFields() {
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
RouteEntry routeEntry = new RouteEntry(prefix, nextHop);
assertThat(routeEntry.prefix(), is(prefix));
assertThat(routeEntry.nextHop(), is(nextHop));
}
/**
* Tests creating a binary string from IPv4 prefix.
*/
@Test
public void testCreateBinaryString() {
IpPrefix prefix;
prefix = IpPrefix.valueOf("0.0.0.0/0");
assertThat(RouteEntry.createBinaryString(prefix), is(""));
prefix = IpPrefix.valueOf("192.168.166.0/22");
assertThat(RouteEntry.createBinaryString(prefix),
is("1100000010101000101001"));
prefix = IpPrefix.valueOf("192.168.166.0/23");
assertThat(RouteEntry.createBinaryString(prefix),
is("11000000101010001010011"));
prefix = IpPrefix.valueOf("192.168.166.0/24");
assertThat(RouteEntry.createBinaryString(prefix),
is("110000001010100010100110"));
prefix = IpPrefix.valueOf("130.162.10.1/25");
assertThat(RouteEntry.createBinaryString(prefix),
is("1000001010100010000010100"));
prefix = IpPrefix.valueOf("255.255.255.255/32");
assertThat(RouteEntry.createBinaryString(prefix),
is("11111111111111111111111111111111"));
}
/**
* Tests equality of {@link RouteEntry}.
*/
@Test
public void testEquality() {
IpPrefix prefix1 = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop1 = IpAddress.valueOf("5.6.7.8");
RouteEntry routeEntry1 = new RouteEntry(prefix1, nextHop1);
IpPrefix prefix2 = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop2 = IpAddress.valueOf("5.6.7.8");
RouteEntry routeEntry2 = new RouteEntry(prefix2, nextHop2);
assertThat(routeEntry1, is(routeEntry2));
}
/**
* Tests non-equality of {@link RouteEntry}.
*/
@Test
public void testNonEquality() {
IpPrefix prefix1 = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop1 = IpAddress.valueOf("5.6.7.8");
RouteEntry routeEntry1 = new RouteEntry(prefix1, nextHop1);
IpPrefix prefix2 = IpPrefix.valueOf("1.2.3.0/25"); // Different
IpAddress nextHop2 = IpAddress.valueOf("5.6.7.8");
RouteEntry routeEntry2 = new RouteEntry(prefix2, nextHop2);
IpPrefix prefix3 = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop3 = IpAddress.valueOf("5.6.7.9"); // Different
RouteEntry routeEntry3 = new RouteEntry(prefix3, nextHop3);
assertThat(routeEntry1, is(not(routeEntry2)));
assertThat(routeEntry1, is(not(routeEntry3)));
}
/**
* Tests object string representation.
*/
@Test
public void testToString() {
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
RouteEntry routeEntry = new RouteEntry(prefix, nextHop);
assertThat(routeEntry.toString(),
is("RouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8}"));
}
}
package org.onlab.onos.sdnip.bgp;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import org.junit.Test;
/**
* Unit tests for the BgpRouteEntry.AsPath class.
*/
public class AsPathTest {
/**
* Generates an AS Path.
*
* @return a generated AS Path
*/
private BgpRouteEntry.AsPath generateAsPath() {
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
segmentAsNumbers1.add((long) 1);
segmentAsNumbers1.add((long) 2);
segmentAsNumbers1.add((long) 3);
BgpRouteEntry.PathSegment pathSegment1 =
new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
pathSegments.add(pathSegment1);
//
byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
segmentAsNumbers2.add((long) 4);
segmentAsNumbers2.add((long) 5);
segmentAsNumbers2.add((long) 6);
BgpRouteEntry.PathSegment pathSegment2 =
new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
pathSegments.add(pathSegment2);
//
BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
return asPath;
}
/**
* Tests valid class constructor.
*/
@Test
public void testConstructor() {
BgpRouteEntry.AsPath asPath = generateAsPath();
String expectedString =
"AsPath{pathSegments=" +
"[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " +
"PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}";
assertThat(asPath.toString(), is(expectedString));
}
/**
* Tests invalid class constructor for null Path Segments.
*/
@Test(expected = NullPointerException.class)
public void testInvalidConstructorNullPathSegments() {
ArrayList<BgpRouteEntry.PathSegment> pathSegments = null;
new BgpRouteEntry.AsPath(pathSegments);
}
/**
* Tests getting the fields of an AS Path.
*/
@Test
public void testGetFields() {
// Create the fields to compare against
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
segmentAsNumbers1.add((long) 1);
segmentAsNumbers1.add((long) 2);
segmentAsNumbers1.add((long) 3);
BgpRouteEntry.PathSegment pathSegment1 =
new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
pathSegments.add(pathSegment1);
//
byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
segmentAsNumbers2.add((long) 4);
segmentAsNumbers2.add((long) 5);
segmentAsNumbers2.add((long) 6);
BgpRouteEntry.PathSegment pathSegment2 =
new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
pathSegments.add(pathSegment2);
// Generate the entry to test
BgpRouteEntry.AsPath asPath = generateAsPath();
assertThat(asPath.getPathSegments(), is(pathSegments));
}
/**
* Tests getting the AS Path Length.
*/
@Test
public void testGetAsPathLength() {
BgpRouteEntry.AsPath asPath = generateAsPath();
assertThat(asPath.getAsPathLength(), is(4));
// Create an empty AS Path
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
asPath = new BgpRouteEntry.AsPath(pathSegments);
assertThat(asPath.getAsPathLength(), is(0));
}
/**
* Tests equality of {@link BgpRouteEntry.AsPath}.
*/
@Test
public void testEquality() {
BgpRouteEntry.AsPath asPath1 = generateAsPath();
BgpRouteEntry.AsPath asPath2 = generateAsPath();
assertThat(asPath1, is(asPath2));
}
/**
* Tests non-equality of {@link BgpRouteEntry.AsPath}.
*/
@Test
public void testNonEquality() {
BgpRouteEntry.AsPath asPath1 = generateAsPath();
// Setup AS Path 2
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
segmentAsNumbers1.add((long) 1);
segmentAsNumbers1.add((long) 2);
segmentAsNumbers1.add((long) 3);
BgpRouteEntry.PathSegment pathSegment1 =
new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
pathSegments.add(pathSegment1);
//
byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
segmentAsNumbers2.add((long) 4);
segmentAsNumbers2.add((long) 55); // Different
segmentAsNumbers2.add((long) 6);
BgpRouteEntry.PathSegment pathSegment2 =
new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
pathSegments.add(pathSegment2);
//
BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments);
assertThat(asPath1, is(not(asPath2)));
}
/**
* Tests object string representation.
*/
@Test
public void testToString() {
BgpRouteEntry.AsPath asPath = generateAsPath();
String expectedString =
"AsPath{pathSegments=" +
"[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " +
"PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}";
assertThat(asPath.toString(), is(expectedString));
}
}
package org.onlab.onos.sdnip.bgp;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
/**
* Unit tests for the BgpRouteEntry class.
*/
public class BgpRouteEntryTest {
private BgpSession bgpSession;
private static final IpAddress BGP_SESSION_BGP_ID =
IpAddress.valueOf("10.0.0.1");
private static final IpAddress BGP_SESSION_IP_ADDRESS =
IpAddress.valueOf("20.0.0.1");
private BgpSession bgpSession2;
private static final IpAddress BGP_SESSION_BGP_ID2 =
IpAddress.valueOf("10.0.0.2");
private static final IpAddress BGP_SESSION_IP_ADDRESS2 =
IpAddress.valueOf("20.0.0.1");
private BgpSession bgpSession3;
private static final IpAddress BGP_SESSION_BGP_ID3 =
IpAddress.valueOf("10.0.0.1");
private static final IpAddress BGP_SESSION_IP_ADDRESS3 =
IpAddress.valueOf("20.0.0.2");
@Before
public void setUp() throws Exception {
// Mock objects for testing
bgpSession = createMock(BgpSession.class);
bgpSession2 = createMock(BgpSession.class);
bgpSession3 = createMock(BgpSession.class);
// Setup the BGP Sessions
expect(bgpSession.getRemoteBgpId())
.andReturn(BGP_SESSION_BGP_ID).anyTimes();
expect(bgpSession.getRemoteIp4Address())
.andReturn(BGP_SESSION_IP_ADDRESS).anyTimes();
//
expect(bgpSession2.getRemoteBgpId())
.andReturn(BGP_SESSION_BGP_ID2).anyTimes();
expect(bgpSession2.getRemoteIp4Address())
.andReturn(BGP_SESSION_IP_ADDRESS2).anyTimes();
//
expect(bgpSession3.getRemoteBgpId())
.andReturn(BGP_SESSION_BGP_ID3).anyTimes();
expect(bgpSession3.getRemoteIp4Address())
.andReturn(BGP_SESSION_IP_ADDRESS3).anyTimes();
replay(bgpSession);
replay(bgpSession2);
replay(bgpSession3);
}
/**
* Generates a BGP Route Entry.
*
* @return a generated BGP Route Entry
*/
private BgpRouteEntry generateBgpRouteEntry() {
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
byte origin = BgpConstants.Update.Origin.IGP;
// Setup the AS Path
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
segmentAsNumbers1.add((long) 1);
segmentAsNumbers1.add((long) 2);
segmentAsNumbers1.add((long) 3);
BgpRouteEntry.PathSegment pathSegment1 =
new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
pathSegments.add(pathSegment1);
//
byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
segmentAsNumbers2.add((long) 4);
segmentAsNumbers2.add((long) 5);
segmentAsNumbers2.add((long) 6);
BgpRouteEntry.PathSegment pathSegment2 =
new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
pathSegments.add(pathSegment2);
//
BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
//
long localPref = 100;
long multiExitDisc = 20;
BgpRouteEntry bgpRouteEntry =
new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
localPref);
bgpRouteEntry.setMultiExitDisc(multiExitDisc);
return bgpRouteEntry;
}
/**
* Tests valid class constructor.
*/
@Test
public void testConstructor() {
BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
String expectedString =
"BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " +
"bgpId=10.0.0.1, origin=0, asPath=AsPath{pathSegments=" +
"[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " +
"PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}, " +
"localPref=100, multiExitDisc=20}";
assertThat(bgpRouteEntry.toString(), is(expectedString));
}
/**
* Tests invalid class constructor for null BGP Session.
*/
@Test(expected = NullPointerException.class)
public void testInvalidConstructorNullBgpSession() {
BgpSession bgpSessionNull = null;
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
byte origin = BgpConstants.Update.Origin.IGP;
// Setup the AS Path
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
//
long localPref = 100;
new BgpRouteEntry(bgpSessionNull, prefix, nextHop, origin, asPath,
localPref);
}
/**
* Tests invalid class constructor for null AS Path.
*/
@Test(expected = NullPointerException.class)
public void testInvalidConstructorNullAsPath() {
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
byte origin = BgpConstants.Update.Origin.IGP;
BgpRouteEntry.AsPath asPath = null;
long localPref = 100;
new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
localPref);
}
/**
* Tests getting the fields of a BGP route entry.
*/
@Test
public void testGetFields() {
// Create the fields to compare against
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
byte origin = BgpConstants.Update.Origin.IGP;
// Setup the AS Path
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
segmentAsNumbers1.add((long) 1);
segmentAsNumbers1.add((long) 2);
segmentAsNumbers1.add((long) 3);
BgpRouteEntry.PathSegment pathSegment1 =
new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
pathSegments.add(pathSegment1);
//
byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
segmentAsNumbers2.add((long) 4);
segmentAsNumbers2.add((long) 5);
segmentAsNumbers2.add((long) 6);
BgpRouteEntry.PathSegment pathSegment2 =
new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
pathSegments.add(pathSegment2);
//
BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
//
long localPref = 100;
long multiExitDisc = 20;
// Generate the entry to test
BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
assertThat(bgpRouteEntry.prefix(), is(prefix));
assertThat(bgpRouteEntry.nextHop(), is(nextHop));
assertThat(bgpRouteEntry.getBgpSession(), is(bgpSession));
assertThat(bgpRouteEntry.getOrigin(), is(origin));
assertThat(bgpRouteEntry.getAsPath(), is(asPath));
assertThat(bgpRouteEntry.getLocalPref(), is(localPref));
assertThat(bgpRouteEntry.getMultiExitDisc(), is(multiExitDisc));
}
/**
* Tests whether a BGP route entry is a local route.
*/
@Test
public void testIsLocalRoute() {
//
// Test non-local route
//
BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
assertThat(bgpRouteEntry.isLocalRoute(), is(false));
//
// Test local route with AS Path that begins with AS_SET
//
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
byte origin = BgpConstants.Update.Origin.IGP;
// Setup the AS Path
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SET;
ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
segmentAsNumbers1.add((long) 1);
segmentAsNumbers1.add((long) 2);
segmentAsNumbers1.add((long) 3);
BgpRouteEntry.PathSegment pathSegment1 =
new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
pathSegments.add(pathSegment1);
//
byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
segmentAsNumbers2.add((long) 4);
segmentAsNumbers2.add((long) 5);
segmentAsNumbers2.add((long) 6);
BgpRouteEntry.PathSegment pathSegment2 =
new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
pathSegments.add(pathSegment2);
//
BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
//
long localPref = 100;
long multiExitDisc = 20;
//
bgpRouteEntry =
new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
localPref);
bgpRouteEntry.setMultiExitDisc(multiExitDisc);
assertThat(bgpRouteEntry.isLocalRoute(), is(true));
//
// Test local route with empty AS Path
//
pathSegments = new ArrayList<>();
asPath = new BgpRouteEntry.AsPath(pathSegments);
bgpRouteEntry =
new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
localPref);
bgpRouteEntry.setMultiExitDisc(multiExitDisc);
assertThat(bgpRouteEntry.isLocalRoute(), is(true));
}
/**
* Tests getting the BGP Neighbor AS number for a route.
*/
@Test
public void testGetNeighborAs() {
//
// Get neighbor AS for non-local route
//
BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
assertThat(bgpRouteEntry.getNeighborAs(), is((long) 1));
//
// Get neighbor AS for a local route
//
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
byte origin = BgpConstants.Update.Origin.IGP;
// Setup the AS Path
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
//
long localPref = 100;
long multiExitDisc = 20;
//
bgpRouteEntry =
new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
localPref);
bgpRouteEntry.setMultiExitDisc(multiExitDisc);
assertThat(bgpRouteEntry.getNeighborAs(), is(BgpConstants.BGP_AS_0));
}
/**
* Tests whether a BGP route entry has AS Path loop.
*/
@Test
public void testHasAsPathLoop() {
BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
// Test for loops: test each AS number in the interval [1, 6]
for (int i = 1; i <= 6; i++) {
assertThat(bgpRouteEntry.hasAsPathLoop(i), is(true));
}
// Test for non-loops
assertThat(bgpRouteEntry.hasAsPathLoop(500), is(false));
}
/**
* Tests the BGP Decision Process comparison of BGP routes.
*/
@Test
public void testBgpDecisionProcessComparison() {
BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry();
//
// Compare two routes that are same
//
assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true));
//
// Compare two routes with different LOCAL_PREF
//
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
byte origin = BgpConstants.Update.Origin.IGP;
// Setup the AS Path
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
segmentAsNumbers1.add((long) 1);
segmentAsNumbers1.add((long) 2);
segmentAsNumbers1.add((long) 3);
BgpRouteEntry.PathSegment pathSegment1 =
new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
pathSegments.add(pathSegment1);
//
byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
segmentAsNumbers2.add((long) 4);
segmentAsNumbers2.add((long) 5);
segmentAsNumbers2.add((long) 6);
BgpRouteEntry.PathSegment pathSegment2 =
new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
pathSegments.add(pathSegment2);
//
BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
//
long localPref = 50; // Different
long multiExitDisc = 20;
bgpRouteEntry2 =
new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
localPref);
bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
//
assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
localPref = bgpRouteEntry1.getLocalPref(); // Restore
//
// Compare two routes with different AS_PATH length
//
ArrayList<BgpRouteEntry.PathSegment> pathSegments2 = new ArrayList<>();
pathSegments2.add(pathSegment1);
// Different AS Path
BgpRouteEntry.AsPath asPath2 = new BgpRouteEntry.AsPath(pathSegments2);
bgpRouteEntry2 =
new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath2,
localPref);
bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
//
assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(false));
assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(true));
//
// Compare two routes with different ORIGIN
//
origin = BgpConstants.Update.Origin.EGP; // Different
bgpRouteEntry2 =
new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
localPref);
bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
//
assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
origin = bgpRouteEntry1.getOrigin(); // Restore
//
// Compare two routes with different MULTI_EXIT_DISC
//
multiExitDisc = 10; // Different
bgpRouteEntry2 =
new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
localPref);
bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
//
assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
multiExitDisc = bgpRouteEntry1.getMultiExitDisc(); // Restore
//
// Compare two routes with different BGP ID
//
bgpRouteEntry2 =
new BgpRouteEntry(bgpSession2, prefix, nextHop, origin, asPath,
localPref);
bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
//
assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
//
// Compare two routes with different BGP address
//
bgpRouteEntry2 =
new BgpRouteEntry(bgpSession3, prefix, nextHop, origin, asPath,
localPref);
bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
//
assertThat(bgpRouteEntry1.isBetterThan(bgpRouteEntry2), is(true));
assertThat(bgpRouteEntry2.isBetterThan(bgpRouteEntry1), is(false));
}
/**
* Tests equality of {@link BgpRouteEntry}.
*/
@Test
public void testEquality() {
BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
BgpRouteEntry bgpRouteEntry2 = generateBgpRouteEntry();
assertThat(bgpRouteEntry1, is(bgpRouteEntry2));
}
/**
* Tests non-equality of {@link BgpRouteEntry}.
*/
@Test
public void testNonEquality() {
BgpRouteEntry bgpRouteEntry1 = generateBgpRouteEntry();
// Setup BGP Route 2
IpPrefix prefix = IpPrefix.valueOf("1.2.3.0/24");
IpAddress nextHop = IpAddress.valueOf("5.6.7.8");
byte origin = BgpConstants.Update.Origin.IGP;
// Setup the AS Path
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
segmentAsNumbers1.add((long) 1);
segmentAsNumbers1.add((long) 2);
segmentAsNumbers1.add((long) 3);
BgpRouteEntry.PathSegment pathSegment1 =
new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
pathSegments.add(pathSegment1);
//
byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
segmentAsNumbers2.add((long) 4);
segmentAsNumbers2.add((long) 5);
segmentAsNumbers2.add((long) 6);
BgpRouteEntry.PathSegment pathSegment2 =
new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
pathSegments.add(pathSegment2);
//
BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
//
long localPref = 500; // Different
long multiExitDisc = 20;
BgpRouteEntry bgpRouteEntry2 =
new BgpRouteEntry(bgpSession, prefix, nextHop, origin, asPath,
localPref);
bgpRouteEntry2.setMultiExitDisc(multiExitDisc);
assertThat(bgpRouteEntry1, is(not(bgpRouteEntry2)));
}
/**
* Tests object string representation.
*/
@Test
public void testToString() {
BgpRouteEntry bgpRouteEntry = generateBgpRouteEntry();
String expectedString =
"BgpRouteEntry{prefix=1.2.3.0/24, nextHop=5.6.7.8, " +
"bgpId=10.0.0.1, origin=0, asPath=AsPath{pathSegments=" +
"[PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}, " +
"PathSegment{type=1, segmentAsNumbers=[4, 5, 6]}]}, " +
"localPref=100, multiExitDisc=20}";
assertThat(bgpRouteEntry.toString(), is(expectedString));
}
}
package org.onlab.onos.sdnip.bgp;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.onos.sdnip.RouteListener;
import org.onlab.onos.sdnip.RouteUpdate;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.util.TestUtils;
import org.onlab.util.TestUtils.TestUtilsException;
import com.google.common.net.InetAddresses;
/**
* Unit tests for the BgpSessionManager class.
*/
public class BgpSessionManagerTest {
private static final IpAddress IP_LOOPBACK_ID =
IpAddress.valueOf("127.0.0.1");
private static final IpAddress BGP_PEER1_ID = IpAddress.valueOf("10.0.0.1");
private static final long DEFAULT_LOCAL_PREF = 10;
private static final long DEFAULT_MULTI_EXIT_DISC = 20;
// The BGP Session Manager to test
private BgpSessionManager bgpSessionManager;
// Remote Peer state
private ClientBootstrap peerBootstrap;
private TestBgpPeerChannelHandler peerChannelHandler =
new TestBgpPeerChannelHandler(BGP_PEER1_ID, DEFAULT_LOCAL_PREF);
private TestBgpPeerFrameDecoder peerFrameDecoder =
new TestBgpPeerFrameDecoder();
// The socket that the Remote Peer should connect to
private InetSocketAddress connectToSocket;
private final DummyRouteListener dummyRouteListener =
new DummyRouteListener();
/**
* Dummy implementation for the RouteListener interface.
*/
private class DummyRouteListener implements RouteListener {
@Override
public void update(RouteUpdate routeUpdate) {
// Nothing to do
}
}
@Before
public void setUp() throws Exception {
//
// Setup the BGP Session Manager to test, and start listening for BGP
// connections.
//
bgpSessionManager = new BgpSessionManager(dummyRouteListener);
// NOTE: We use port 0 to bind on any available port
bgpSessionManager.startUp(0);
// Get the port number the BGP Session Manager is listening on
Channel serverChannel = TestUtils.getField(bgpSessionManager,
"serverChannel");
SocketAddress socketAddress = serverChannel.getLocalAddress();
InetSocketAddress inetSocketAddress =
(InetSocketAddress) socketAddress;
//
// Setup the BGP Peer, i.e., the "remote" BGP router that will
// initiate the BGP connection, send BGP UPDATE messages, etc.
//
ChannelFactory channelFactory =
new NioClientSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
// Setup the transmitting pipeline
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("TestBgpPeerFrameDecoder",
peerFrameDecoder);
pipeline.addLast("TestBgpPeerChannelHandler",
peerChannelHandler);
return pipeline;
}
};
peerBootstrap = new ClientBootstrap(channelFactory);
peerBootstrap.setOption("child.keepAlive", true);
peerBootstrap.setOption("child.tcpNoDelay", true);
peerBootstrap.setPipelineFactory(pipelineFactory);
InetAddress connectToAddress = InetAddresses.forString("127.0.0.1");
connectToSocket = new InetSocketAddress(connectToAddress,
inetSocketAddress.getPort());
}
@After
public void tearDown() throws Exception {
bgpSessionManager.shutDown();
bgpSessionManager = null;
}
/**
* Gets BGP RIB-IN routes by waiting until they are received.
* <p/>
* NOTE: We keep checking once a second the number of received routes,
* up to 5 seconds.
*
* @param bgpSession the BGP session that is expected to receive the
* routes
* @param expectedRoutes the expected number of routes
* @return the BGP RIB-IN routes as received within the expected
* time interval
*/
private Collection<BgpRouteEntry> waitForBgpRibIn(BgpSession bgpSession,
long expectedRoutes)
throws InterruptedException {
Collection<BgpRouteEntry> bgpRibIn = bgpSession.getBgpRibIn();
final int maxChecks = 5; // Max wait of 5 seconds
for (int i = 0; i < maxChecks; i++) {
if (bgpRibIn.size() == expectedRoutes) {
break;
}
Thread.sleep(1000);
bgpRibIn = bgpSession.getBgpRibIn();
}
return bgpRibIn;
}
/**
* Gets BGP merged routes by waiting until they are received.
* <p/>
* NOTE: We keep checking once a second the number of received routes,
* up to 5 seconds.
*
* @param expectedRoutes the expected number of routes
* @return the BGP Session Manager routes as received within the expected
* time interval
*/
private Collection<BgpRouteEntry> waitForBgpRoutes(long expectedRoutes)
throws InterruptedException {
Collection<BgpRouteEntry> bgpRoutes = bgpSessionManager.getBgpRoutes();
final int maxChecks = 5; // Max wait of 5 seconds
for (int i = 0; i < maxChecks; i++) {
if (bgpRoutes.size() == expectedRoutes) {
break;
}
Thread.sleep(1000);
bgpRoutes = bgpSessionManager.getBgpRoutes();
}
return bgpRoutes;
}
/**
* Tests that the BGP OPEN messages have been exchanged, followed by
* KEEPALIVE.
* <p>
* The BGP Peer opens the sessions and transmits OPEN Message, eventually
* followed by KEEPALIVE. The tested BGP listener should respond by
* OPEN Message, followed by KEEPALIVE.
*
* @throws TestUtilsException TestUtils error
*/
@Test
public void testExchangedBgpOpenMessages()
throws InterruptedException, TestUtilsException {
// Initiate the connection
peerBootstrap.connect(connectToSocket);
// Wait until the OPEN message is received
peerFrameDecoder.receivedOpenMessageLatch.await(2000,
TimeUnit.MILLISECONDS);
// Wait until the KEEPALIVE message is received
peerFrameDecoder.receivedKeepaliveMessageLatch.await(2000,
TimeUnit.MILLISECONDS);
//
// Test the fields from the BGP OPEN message:
// BGP version, AS number, BGP ID
//
assertThat(peerFrameDecoder.remoteBgpVersion,
is(BgpConstants.BGP_VERSION));
assertThat(peerFrameDecoder.remoteAs,
is(TestBgpPeerChannelHandler.PEER_AS));
assertThat(peerFrameDecoder.remoteBgpIdentifier, is(IP_LOOPBACK_ID));
//
// Test that a BgpSession instance has been created
//
assertThat(bgpSessionManager.getMyBgpId(), is(IP_LOOPBACK_ID));
assertThat(bgpSessionManager.getBgpSessions(), hasSize(1));
BgpSession bgpSession =
bgpSessionManager.getBgpSessions().iterator().next();
assertThat(bgpSession, notNullValue());
long sessionAs = TestUtils.getField(bgpSession, "localAs");
assertThat(sessionAs, is(TestBgpPeerChannelHandler.PEER_AS));
}
/**
* Tests that the BGP UPDATE messages have been received and processed.
*/
@Test
public void testProcessedBgpUpdateMessages() throws InterruptedException {
BgpSession bgpSession;
IpAddress nextHopRouter;
BgpRouteEntry bgpRouteEntry;
ChannelBuffer message;
Collection<BgpRouteEntry> bgpRibIn;
Collection<BgpRouteEntry> bgpRoutes;
// Initiate the connection
peerBootstrap.connect(connectToSocket);
// Wait until the OPEN message is received
peerFrameDecoder.receivedOpenMessageLatch.await(2000,
TimeUnit.MILLISECONDS);
// Wait until the KEEPALIVE message is received
peerFrameDecoder.receivedKeepaliveMessageLatch.await(2000,
TimeUnit.MILLISECONDS);
// Get the BGP Session handler
bgpSession = bgpSessionManager.getBgpSessions().iterator().next();
// Prepare routes to add/delete
nextHopRouter = IpAddress.valueOf("10.20.30.40");
Collection<IpPrefix> addedRoutes = new LinkedList<>();
Collection<IpPrefix> withdrawnRoutes = new LinkedList<>();
addedRoutes.add(IpPrefix.valueOf("0.0.0.0/0"));
addedRoutes.add(IpPrefix.valueOf("20.0.0.0/8"));
addedRoutes.add(IpPrefix.valueOf("30.0.0.0/16"));
addedRoutes.add(IpPrefix.valueOf("40.0.0.0/24"));
addedRoutes.add(IpPrefix.valueOf("50.0.0.0/32"));
withdrawnRoutes.add(IpPrefix.valueOf("60.0.0.0/8"));
withdrawnRoutes.add(IpPrefix.valueOf("70.0.0.0/16"));
withdrawnRoutes.add(IpPrefix.valueOf("80.0.0.0/24"));
withdrawnRoutes.add(IpPrefix.valueOf("90.0.0.0/32"));
// Write the routes
message = peerChannelHandler.prepareBgpUpdate(nextHopRouter,
addedRoutes,
withdrawnRoutes);
peerChannelHandler.savedCtx.getChannel().write(message);
// Check that the routes have been received, processed and stored
bgpRibIn = waitForBgpRibIn(bgpSession, 5);
assertThat(bgpRibIn, hasSize(5));
bgpRoutes = waitForBgpRoutes(5);
assertThat(bgpRoutes, hasSize(5));
// Setup the AS Path
ArrayList<BgpRouteEntry.PathSegment> pathSegments = new ArrayList<>();
byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers1 = new ArrayList<>();
segmentAsNumbers1.add((long) 65010);
segmentAsNumbers1.add((long) 65020);
segmentAsNumbers1.add((long) 65030);
BgpRouteEntry.PathSegment pathSegment1 =
new BgpRouteEntry.PathSegment(pathSegmentType1, segmentAsNumbers1);
pathSegments.add(pathSegment1);
//
byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
ArrayList<Long> segmentAsNumbers2 = new ArrayList<>();
segmentAsNumbers2.add((long) 65041);
segmentAsNumbers2.add((long) 65042);
segmentAsNumbers2.add((long) 65043);
BgpRouteEntry.PathSegment pathSegment2 =
new BgpRouteEntry.PathSegment(pathSegmentType2, segmentAsNumbers2);
pathSegments.add(pathSegment2);
//
BgpRouteEntry.AsPath asPath = new BgpRouteEntry.AsPath(pathSegments);
//
bgpRouteEntry =
new BgpRouteEntry(bgpSession,
IpPrefix.valueOf("0.0.0.0/0"),
nextHopRouter,
(byte) BgpConstants.Update.Origin.IGP,
asPath,
DEFAULT_LOCAL_PREF);
bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
assertThat(bgpRibIn, hasItem(bgpRouteEntry));
//
bgpRouteEntry =
new BgpRouteEntry(bgpSession,
IpPrefix.valueOf("20.0.0.0/8"),
nextHopRouter,
(byte) BgpConstants.Update.Origin.IGP,
asPath,
DEFAULT_LOCAL_PREF);
bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
assertThat(bgpRibIn, hasItem(bgpRouteEntry));
//
bgpRouteEntry =
new BgpRouteEntry(bgpSession,
IpPrefix.valueOf("30.0.0.0/16"),
nextHopRouter,
(byte) BgpConstants.Update.Origin.IGP,
asPath,
DEFAULT_LOCAL_PREF);
bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
assertThat(bgpRibIn, hasItem(bgpRouteEntry));
//
bgpRouteEntry =
new BgpRouteEntry(bgpSession,
IpPrefix.valueOf("40.0.0.0/24"),
nextHopRouter,
(byte) BgpConstants.Update.Origin.IGP,
asPath,
DEFAULT_LOCAL_PREF);
bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
assertThat(bgpRibIn, hasItem(bgpRouteEntry));
//
bgpRouteEntry =
new BgpRouteEntry(bgpSession,
IpPrefix.valueOf("50.0.0.0/32"),
nextHopRouter,
(byte) BgpConstants.Update.Origin.IGP,
asPath,
DEFAULT_LOCAL_PREF);
bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
assertThat(bgpRibIn, hasItem(bgpRouteEntry));
// Delete some routes
addedRoutes = new LinkedList<>();
withdrawnRoutes = new LinkedList<>();
withdrawnRoutes.add(IpPrefix.valueOf("0.0.0.0/0"));
withdrawnRoutes.add(IpPrefix.valueOf("50.0.0.0/32"));
// Write the routes
message = peerChannelHandler.prepareBgpUpdate(nextHopRouter,
addedRoutes,
withdrawnRoutes);
peerChannelHandler.savedCtx.getChannel().write(message);
// Check that the routes have been received, processed and stored
bgpRibIn = waitForBgpRibIn(bgpSession, 3);
assertThat(bgpRibIn, hasSize(3));
bgpRoutes = waitForBgpRoutes(3);
assertThat(bgpRoutes, hasSize(3));
//
bgpRouteEntry =
new BgpRouteEntry(bgpSession,
IpPrefix.valueOf("20.0.0.0/8"),
nextHopRouter,
(byte) BgpConstants.Update.Origin.IGP,
asPath,
DEFAULT_LOCAL_PREF);
bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
assertThat(bgpRibIn, hasItem(bgpRouteEntry));
//
bgpRouteEntry =
new BgpRouteEntry(bgpSession,
IpPrefix.valueOf("30.0.0.0/16"),
nextHopRouter,
(byte) BgpConstants.Update.Origin.IGP,
asPath,
DEFAULT_LOCAL_PREF);
bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
assertThat(bgpRibIn, hasItem(bgpRouteEntry));
//
bgpRouteEntry =
new BgpRouteEntry(bgpSession,
IpPrefix.valueOf("40.0.0.0/24"),
nextHopRouter,
(byte) BgpConstants.Update.Origin.IGP,
asPath,
DEFAULT_LOCAL_PREF);
bgpRouteEntry.setMultiExitDisc(DEFAULT_MULTI_EXIT_DISC);
assertThat(bgpRibIn, hasItem(bgpRouteEntry));
// Close the channel and test there are no routes
peerChannelHandler.closeChannel();
bgpRoutes = waitForBgpRoutes(0);
assertThat(bgpRoutes, hasSize(0));
}
}
package org.onlab.onos.sdnip.bgp;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import org.junit.Test;
/**
* Unit tests for the BgpRouteEntry.PathSegment class.
*/
public class PathSegmentTest {
/**
* Generates a Path Segment.
*
* @return a generated PathSegment
*/
private BgpRouteEntry.PathSegment generatePathSegment() {
byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers = new ArrayList<>();
segmentAsNumbers.add((long) 1);
segmentAsNumbers.add((long) 2);
segmentAsNumbers.add((long) 3);
BgpRouteEntry.PathSegment pathSegment =
new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
return pathSegment;
}
/**
* Tests valid class constructor.
*/
@Test
public void testConstructor() {
BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
String expectedString =
"PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}";
assertThat(pathSegment.toString(), is(expectedString));
}
/**
* Tests invalid class constructor for null Segment AS Numbers.
*/
@Test(expected = NullPointerException.class)
public void testInvalidConstructorNullSegmentAsNumbers() {
byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers = null;
new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
}
/**
* Tests getting the fields of a Path Segment.
*/
@Test
public void testGetFields() {
// Create the fields to compare against
byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers = new ArrayList<>();
segmentAsNumbers.add((long) 1);
segmentAsNumbers.add((long) 2);
segmentAsNumbers.add((long) 3);
// Generate the entry to test
BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
assertThat(pathSegment.getType(), is(pathSegmentType));
assertThat(pathSegment.getSegmentAsNumbers(), is(segmentAsNumbers));
}
/**
* Tests equality of {@link BgpRouteEntry.PathSegment}.
*/
@Test
public void testEquality() {
BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment();
BgpRouteEntry.PathSegment pathSegment2 = generatePathSegment();
assertThat(pathSegment1, is(pathSegment2));
}
/**
* Tests non-equality of {@link BgpRouteEntry.PathSegment}.
*/
@Test
public void testNonEquality() {
BgpRouteEntry.PathSegment pathSegment1 = generatePathSegment();
// Setup Path Segment 2
byte pathSegmentType = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
ArrayList<Long> segmentAsNumbers = new ArrayList<>();
segmentAsNumbers.add((long) 1);
segmentAsNumbers.add((long) 22); // Different
segmentAsNumbers.add((long) 3);
//
BgpRouteEntry.PathSegment pathSegment2 =
new BgpRouteEntry.PathSegment(pathSegmentType, segmentAsNumbers);
assertThat(pathSegment1, is(not(pathSegment2)));
}
/**
* Tests object string representation.
*/
@Test
public void testToString() {
BgpRouteEntry.PathSegment pathSegment = generatePathSegment();
String expectedString =
"PathSegment{type=2, segmentAsNumbers=[1, 2, 3]}";
assertThat(pathSegment.toString(), is(expectedString));
}
}
package org.onlab.onos.sdnip.bgp;
import java.util.Collection;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
/**
* Class for handling the remote BGP Peer session.
*/
class TestBgpPeerChannelHandler extends SimpleChannelHandler {
static final long PEER_AS = 65001;
static final int PEER_HOLDTIME = 120; // 120 seconds
final IpAddress bgpId; // The BGP ID
final long localPref; // Local preference for routes
final long multiExitDisc = 20; // MED value
ChannelHandlerContext savedCtx;
/**
* Constructor for given BGP ID.
*
* @param bgpId the BGP ID to use
* @param localPref the local preference for the routes to use
*/
TestBgpPeerChannelHandler(IpAddress bgpId,
long localPref) {
this.bgpId = bgpId;
this.localPref = localPref;
}
/**
* Closes the channel.
*/
void closeChannel() {
savedCtx.getChannel().close();
}
@Override
public void channelConnected(ChannelHandlerContext ctx,
ChannelStateEvent channelEvent) {
this.savedCtx = ctx;
// Prepare and transmit BGP OPEN message
ChannelBuffer message = prepareBgpOpen();
ctx.getChannel().write(message);
// Prepare and transmit BGP KEEPALIVE message
message = prepareBgpKeepalive();
ctx.getChannel().write(message);
}
@Override
public void channelDisconnected(ChannelHandlerContext ctx,
ChannelStateEvent channelEvent) {
// Nothing to do
}
/**
* Prepares BGP OPEN message.
*
* @return the message to transmit (BGP header included)
*/
ChannelBuffer prepareBgpOpen() {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
message.writeByte(BgpConstants.BGP_VERSION);
message.writeShort((int) PEER_AS);
message.writeShort(PEER_HOLDTIME);
message.writeInt(bgpId.toInt());
message.writeByte(0); // No Optional Parameters
return prepareBgpMessage(BgpConstants.BGP_TYPE_OPEN, message);
}
/**
* Prepares BGP UPDATE message.
*
* @param nextHopRouter the next-hop router address for the routes to add
* @param addedRoutes the routes to add
* @param withdrawnRoutes the routes to withdraw
* @return the message to transmit (BGP header included)
*/
ChannelBuffer prepareBgpUpdate(IpAddress nextHopRouter,
Collection<IpPrefix> addedRoutes,
Collection<IpPrefix> withdrawnRoutes) {
int attrFlags;
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
ChannelBuffer pathAttributes =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
// Encode the Withdrawn Routes
ChannelBuffer encodedPrefixes = encodePackedPrefixes(withdrawnRoutes);
message.writeShort(encodedPrefixes.readableBytes());
message.writeBytes(encodedPrefixes);
// Encode the Path Attributes
// ORIGIN: IGP
attrFlags = 0x40; // Transitive flag
pathAttributes.writeByte(attrFlags);
pathAttributes.writeByte(BgpConstants.Update.Origin.TYPE);
pathAttributes.writeByte(1); // Data length
pathAttributes.writeByte(BgpConstants.Update.Origin.IGP);
// AS_PATH: Two Path Segments of 3 ASes each
attrFlags = 0x40; // Transitive flag
pathAttributes.writeByte(attrFlags);
pathAttributes.writeByte(BgpConstants.Update.AsPath.TYPE);
pathAttributes.writeByte(16); // Data length
byte pathSegmentType1 = (byte) BgpConstants.Update.AsPath.AS_SEQUENCE;
pathAttributes.writeByte(pathSegmentType1);
pathAttributes.writeByte(3); // Three ASes
pathAttributes.writeShort(65010); // AS=65010
pathAttributes.writeShort(65020); // AS=65020
pathAttributes.writeShort(65030); // AS=65030
byte pathSegmentType2 = (byte) BgpConstants.Update.AsPath.AS_SET;
pathAttributes.writeByte(pathSegmentType2);
pathAttributes.writeByte(3); // Three ASes
pathAttributes.writeShort(65041); // AS=65041
pathAttributes.writeShort(65042); // AS=65042
pathAttributes.writeShort(65043); // AS=65043
// NEXT_HOP: nextHopRouter
attrFlags = 0x40; // Transitive flag
pathAttributes.writeByte(attrFlags);
pathAttributes.writeByte(BgpConstants.Update.NextHop.TYPE);
pathAttributes.writeByte(4); // Data length
pathAttributes.writeInt(nextHopRouter.toInt()); // Next-hop router
// LOCAL_PREF: localPref
attrFlags = 0x40; // Transitive flag
pathAttributes.writeByte(attrFlags);
pathAttributes.writeByte(BgpConstants.Update.LocalPref.TYPE);
pathAttributes.writeByte(4); // Data length
pathAttributes.writeInt((int) localPref); // Preference value
// MULTI_EXIT_DISC: multiExitDisc
attrFlags = 0x80; // Optional
// Non-Transitive flag
pathAttributes.writeByte(attrFlags);
pathAttributes.writeByte(BgpConstants.Update.MultiExitDisc.TYPE);
pathAttributes.writeByte(4); // Data length
pathAttributes.writeInt((int) multiExitDisc); // Preference value
// The NLRI prefixes
encodedPrefixes = encodePackedPrefixes(addedRoutes);
// Write the Path Attributes, beginning with its length
message.writeShort(pathAttributes.readableBytes());
message.writeBytes(pathAttributes);
message.writeBytes(encodedPrefixes);
return prepareBgpMessage(BgpConstants.BGP_TYPE_UPDATE, message);
}
/**
* Encodes a collection of IPv4 network prefixes in a packed format.
* <p>
* The IPv4 prefixes are encoded in the form:
* <Length, Prefix> where Length is the length in bits of the IPv4 prefix,
* and Prefix is the IPv4 prefix (padded with trailing bits to the end
* of an octet).
*
* @param prefixes the prefixes to encode
* @return the buffer with the encoded prefixes
*/
private ChannelBuffer encodePackedPrefixes(Collection<IpPrefix> prefixes) {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
// Write each of the prefixes
for (IpPrefix prefix : prefixes) {
int prefixBitlen = prefix.prefixLength();
int prefixBytelen = (prefixBitlen + 7) / 8; // Round-up
message.writeByte(prefixBitlen);
IpAddress address = prefix.toIpAddress();
long value = address.toInt() & 0xffffffffL;
for (int i = 0; i < IpAddress.INET_LEN; i++) {
if (prefixBytelen-- == 0) {
break;
}
long nextByte =
(value >> ((IpAddress.INET_LEN - i - 1) * 8)) & 0xff;
message.writeByte((int) nextByte);
}
}
return message;
}
/**
* Prepares BGP KEEPALIVE message.
*
* @return the message to transmit (BGP header included)
*/
ChannelBuffer prepareBgpKeepalive() {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
return prepareBgpMessage(BgpConstants.BGP_TYPE_KEEPALIVE, message);
}
/**
* Prepares BGP NOTIFICATION message.
*
* @param errorCode the BGP NOTIFICATION Error Code
* @param errorSubcode the BGP NOTIFICATION Error Subcode if applicable,
* otherwise BgpConstants.Notifications.ERROR_SUBCODE_UNSPECIFIC
* @param payload the BGP NOTIFICATION Data if applicable, otherwise null
* @return the message to transmit (BGP header included)
*/
ChannelBuffer prepareBgpNotification(int errorCode, int errorSubcode,
ChannelBuffer data) {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_MESSAGE_MAX_LENGTH);
// Prepare the NOTIFICATION message payload
message.writeByte(errorCode);
message.writeByte(errorSubcode);
if (data != null) {
message.writeBytes(data);
}
return prepareBgpMessage(BgpConstants.BGP_TYPE_NOTIFICATION, message);
}
/**
* Prepares BGP message.
*
* @param type the BGP message type
* @param payload the message payload to transmit (BGP header excluded)
* @return the message to transmit (BGP header included)
*/
private ChannelBuffer prepareBgpMessage(int type, ChannelBuffer payload) {
ChannelBuffer message =
ChannelBuffers.buffer(BgpConstants.BGP_HEADER_LENGTH +
payload.readableBytes());
// Write the marker
for (int i = 0; i < BgpConstants.BGP_HEADER_MARKER_LENGTH; i++) {
message.writeByte(0xff);
}
// Write the rest of the BGP header
message.writeShort(BgpConstants.BGP_HEADER_LENGTH +
payload.readableBytes());
message.writeByte(type);
// Write the payload
message.writeBytes(payload);
return message;
}
}
package org.onlab.onos.sdnip.bgp;
import java.util.concurrent.CountDownLatch;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
import org.onlab.packet.IpAddress;
/**
* Class for handling the decoding of the BGP messages at the remote
* BGP peer session.
*/
class TestBgpPeerFrameDecoder extends FrameDecoder {
int remoteBgpVersion; // 1 octet
long remoteAs; // 2 octets
long remoteHoldtime; // 2 octets
IpAddress remoteBgpIdentifier; // 4 octets -> IPv4 address
final CountDownLatch receivedOpenMessageLatch = new CountDownLatch(1);
final CountDownLatch receivedKeepaliveMessageLatch = new CountDownLatch(1);
@Override
protected Object decode(ChannelHandlerContext ctx,
Channel channel,
ChannelBuffer buf) throws Exception {
// Test for minimum length of the BGP message
if (buf.readableBytes() < BgpConstants.BGP_HEADER_LENGTH) {
// No enough data received
return null;
}
//
// Mark the current buffer position in case we haven't received
// the whole message.
//
buf.markReaderIndex();
//
// Read and check the BGP message Marker field: it must be all ones
//
byte[] marker = new byte[BgpConstants.BGP_HEADER_MARKER_LENGTH];
buf.readBytes(marker);
for (int i = 0; i < marker.length; i++) {
if (marker[i] != (byte) 0xff) {
// ERROR: Connection Not Synchronized. Close the channel.
ctx.getChannel().close();
return null;
}
}
//
// Read and check the BGP message Length field
//
int length = buf.readUnsignedShort();
if ((length < BgpConstants.BGP_HEADER_LENGTH) ||
(length > BgpConstants.BGP_MESSAGE_MAX_LENGTH)) {
// ERROR: Bad Message Length. Close the channel.
ctx.getChannel().close();
return null;
}
//
// Test whether the rest of the message is received:
// So far we have read the Marker (16 octets) and the
// Length (2 octets) fields.
//
int remainingMessageLen =
length - BgpConstants.BGP_HEADER_MARKER_LENGTH - 2;
if (buf.readableBytes() < remainingMessageLen) {
// No enough data received
buf.resetReaderIndex();
return null;
}
//
// Read the BGP message Type field, and process based on that type
//
int type = buf.readUnsignedByte();
remainingMessageLen--; // Adjust after reading the type
ChannelBuffer message = buf.readBytes(remainingMessageLen);
//
// Process the remaining of the message based on the message type
//
switch (type) {
case BgpConstants.BGP_TYPE_OPEN:
processBgpOpen(ctx, message);
break;
case BgpConstants.BGP_TYPE_UPDATE:
// NOTE: Not used as part of the test, because ONOS does not
// originate UPDATE messages.
break;
case BgpConstants.BGP_TYPE_NOTIFICATION:
// NOTE: Not used as part of the testing (yet)
break;
case BgpConstants.BGP_TYPE_KEEPALIVE:
processBgpKeepalive(ctx, message);
break;
default:
// ERROR: Bad Message Type. Close the channel.
ctx.getChannel().close();
return null;
}
return null;
}
/**
* Processes BGP OPEN message.
*
* @param ctx the Channel Handler Context.
* @param message the message to process.
*/
private void processBgpOpen(ChannelHandlerContext ctx,
ChannelBuffer message) {
int minLength =
BgpConstants.BGP_OPEN_MIN_LENGTH - BgpConstants.BGP_HEADER_LENGTH;
if (message.readableBytes() < minLength) {
// ERROR: Bad Message Length. Close the channel.
ctx.getChannel().close();
return;
}
//
// Parse the OPEN message
//
remoteBgpVersion = message.readUnsignedByte();
remoteAs = message.readUnsignedShort();
remoteHoldtime = message.readUnsignedShort();
remoteBgpIdentifier = IpAddress.valueOf((int) message.readUnsignedInt());
// Optional Parameters
int optParamLen = message.readUnsignedByte();
if (message.readableBytes() < optParamLen) {
// ERROR: Bad Message Length. Close the channel.
ctx.getChannel().close();
return;
}
message.readBytes(optParamLen); // NOTE: data ignored
// BGP OPEN message successfully received
receivedOpenMessageLatch.countDown();
}
/**
* Processes BGP KEEPALIVE message.
*
* @param ctx the Channel Handler Context.
* @param message the message to process.
*/
private void processBgpKeepalive(ChannelHandlerContext ctx,
ChannelBuffer message) {
if (message.readableBytes() + BgpConstants.BGP_HEADER_LENGTH !=
BgpConstants.BGP_KEEPALIVE_EXPECTED_LENGTH) {
// ERROR: Bad Message Length. Close the channel.
ctx.getChannel().close();
return;
}
// BGP KEEPALIVE message successfully received
receivedKeepaliveMessageLatch.countDown();
}
}
......@@ -26,6 +26,16 @@
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-osgi</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
......
package org.onlab.onos.cli;
import org.apache.karaf.shell.commands.Option;
import org.apache.karaf.shell.console.OsgiCommandSupport;
import org.onlab.osgi.DefaultServiceDirectory;
import org.onlab.osgi.ServiceNotFoundException;
......@@ -9,6 +10,10 @@ import org.onlab.osgi.ServiceNotFoundException;
*/
public abstract class AbstractShellCommand extends OsgiCommandSupport {
@Option(name = "-j", aliases = "--json", description = "Output JSON",
required = false, multiValued = false)
private boolean json = false;
/**
* Returns the reference to the implementation of the specified service.
*
......@@ -46,6 +51,15 @@ public abstract class AbstractShellCommand extends OsgiCommandSupport {
*/
protected abstract void execute();
/**
* Indicates whether JSON format should be output.
*
* @return true if JSON is requested
*/
protected boolean outputJson() {
return json;
}
@Override
protected Object doExecute() throws Exception {
try {
......
package org.onlab.onos.cli;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.collect.Lists;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cluster.ClusterService;
import org.onlab.onos.cluster.ControllerNode;
......@@ -26,7 +28,10 @@ public class MastersListCommand extends AbstractShellCommand {
MastershipService mastershipService = get(MastershipService.class);
List<ControllerNode> nodes = newArrayList(service.getNodes());
Collections.sort(nodes, Comparators.NODE_COMPARATOR);
ControllerNode self = service.getLocalNode();
if (outputJson()) {
print("%s", json(service, mastershipService, nodes));
} else {
for (ControllerNode node : nodes) {
List<DeviceId> ids = Lists.newArrayList(mastershipService.getDevicesOf(node.id()));
Collections.sort(ids, Comparators.ELEMENT_ID_COMPARATOR);
......@@ -36,5 +41,37 @@ public class MastersListCommand extends AbstractShellCommand {
}
}
}
}
// Produces JSON structure.
private JsonNode json(ClusterService service, MastershipService mastershipService,
List<ControllerNode> nodes) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
ControllerNode self = service.getLocalNode();
for (ControllerNode node : nodes) {
List<DeviceId> ids = Lists.newArrayList(mastershipService.getDevicesOf(node.id()));
result.add(mapper.createObjectNode()
.put("id", node.id().toString())
.put("size", ids.size())
.set("devices", json(mapper, ids)));
}
return result;
}
/**
* Produces a JSON array containing the specified device identifiers.
*
* @param mapper object mapper
* @param ids collection of device identifiers
* @return JSON array
*/
public static JsonNode json(ObjectMapper mapper, Iterable<DeviceId> ids) {
ArrayNode result = mapper.createArrayNode();
for (DeviceId deviceId : ids) {
result.add(deviceId.toString());
}
return result;
}
}
......
package org.onlab.onos.cli;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cluster.ClusterService;
import org.onlab.onos.cluster.ControllerNode;
......@@ -24,6 +27,9 @@ public class NodesListCommand extends AbstractShellCommand {
ClusterService service = get(ClusterService.class);
List<ControllerNode> nodes = newArrayList(service.getNodes());
Collections.sort(nodes, Comparators.NODE_COMPARATOR);
if (outputJson()) {
print("%s", json(service, nodes));
} else {
ControllerNode self = service.getLocalNode();
for (ControllerNode node : nodes) {
print(FMT, node.id(), node.ip(), node.tcpPort(),
......@@ -31,5 +37,22 @@ public class NodesListCommand extends AbstractShellCommand {
node.equals(self) ? "*" : "");
}
}
}
// Produces JSON structure.
private JsonNode json(ClusterService service, List<ControllerNode> nodes) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
ControllerNode self = service.getLocalNode();
for (ControllerNode node : nodes) {
result.add(mapper.createObjectNode()
.put("id", node.id().toString())
.put("ip", node.ip().toString())
.put("tcpPort", node.tcpPort())
.put("state", service.getState(node.id()).toString())
.put("self", node.equals(self)));
}
return result;
}
}
......
package org.onlab.onos.cli;
import static com.google.common.collect.Lists.newArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.mastership.MastershipService;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.device.DeviceService;
/**
* Lists mastership roles of nodes for each device.
*/
@Command(scope = "onos", name = "roles",
description = "Lists mastership roles of nodes for each device.")
public class RolesCommand extends AbstractShellCommand {
private static final String FMT_HDR = "%s: master=%s\nstandbys: %s nodes";
private static final String FMT_SB = "\t%s";
@Override
protected void execute() {
DeviceService deviceService = get(DeviceService.class);
MastershipService roleService = get(MastershipService.class);
for (Device d : getSortedDevices(deviceService)) {
DeviceId did = d.id();
printRoles(roleService, did);
}
}
/**
* Returns the list of devices sorted using the device ID URIs.
*
* @param service device service
* @return sorted device list
*/
protected static List<Device> getSortedDevices(DeviceService service) {
List<Device> devices = newArrayList(service.getDevices());
Collections.sort(devices, Comparators.ELEMENT_COMPARATOR);
return devices;
}
/**
* Prints the role information for a device.
*
* @param deviceId the ID of the device
* @param master the current master
*/
protected void printRoles(MastershipService service, DeviceId deviceId) {
List<NodeId> nodes = service.getNodesFor(deviceId);
NodeId first = null;
NodeId master = null;
if (!nodes.isEmpty()) {
first = nodes.get(0);
}
if (first != null &&
first.equals(service.getMasterFor(deviceId))) {
master = nodes.get(0);
nodes.remove(master);
}
print(FMT_HDR, deviceId, master == null ? "NONE" : master, nodes.size());
for (NodeId nid : nodes) {
print(FMT_SB, nid);
}
}
}
package org.onlab.onos.cli;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.CoreService;
import org.onlab.onos.cluster.ClusterService;
......@@ -22,6 +23,19 @@ public class SummaryCommand extends AbstractShellCommand {
protected void execute() {
TopologyService topologyService = get(TopologyService.class);
Topology topology = topologyService.currentTopology();
if (outputJson()) {
print("%s", new ObjectMapper().createObjectNode()
.put("node", get(ClusterService.class).getLocalNode().ip().toString())
.put("version", get(CoreService.class).version().toString())
.put("nodes", get(ClusterService.class).getNodes().size())
.put("devices", get(DeviceService.class).getDeviceCount())
.put("links", get(LinkService.class).getLinkCount())
.put("hosts", get(HostService.class).getHostCount())
.put("clusters", topologyService.getClusters(topology).size())
.put("paths", topology.pathCount())
.put("flows", get(FlowRuleService.class).getFlowRuleCount())
.put("intents", get(IntentService.class).getIntentCount()));
} else {
print("node=%s, version=%s",
get(ClusterService.class).getLocalNode().ip(),
get(CoreService.class).version().toString());
......@@ -35,5 +49,6 @@ public class SummaryCommand extends AbstractShellCommand {
get(FlowRuleService.class).getFlowRuleCount(),
get(IntentService.class).getIntentCount());
}
}
}
......
package org.onlab.onos.cli.net;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
......@@ -10,6 +11,7 @@ import org.onlab.onos.net.topology.TopologyCluster;
import java.util.Collections;
import java.util.List;
import static org.onlab.onos.cli.MastersListCommand.json;
import static org.onlab.onos.net.topology.ClusterId.clusterId;
/**
......@@ -33,11 +35,14 @@ public class ClusterDevicesCommand extends ClustersListCommand {
} else {
List<DeviceId> ids = Lists.newArrayList(service.getClusterDevices(topology, cluster));
Collections.sort(ids, Comparators.ELEMENT_ID_COMPARATOR);
if (outputJson()) {
print("%s", json(new ObjectMapper(), ids));
} else {
for (DeviceId deviceId : ids) {
print("%s", deviceId);
}
}
}
}
}
......
......@@ -5,6 +5,7 @@ import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.topology.TopologyCluster;
import static org.onlab.onos.cli.net.LinksListCommand.json;
import static org.onlab.onos.cli.net.LinksListCommand.linkString;
import static org.onlab.onos.net.topology.ClusterId.clusterId;
......@@ -26,6 +27,8 @@ public class ClusterLinksCommand extends ClustersListCommand {
TopologyCluster cluster = service.getCluster(topology, clusterId(cid));
if (cluster == null) {
error("No such cluster %s", cid);
} else if (outputJson()) {
print("%s", json(service.getClusterLinks(topology, cluster)));
} else {
for (Link link : service.getClusterLinks(topology, cluster)) {
print(linkString(link));
......
package org.onlab.onos.cli.net;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.google.common.collect.Lists;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.Comparators;
......@@ -24,9 +27,26 @@ public class ClustersListCommand extends TopologyCommand {
List<TopologyCluster> clusters = Lists.newArrayList(service.getClusters(topology));
Collections.sort(clusters, Comparators.CLUSTER_COMPARATOR);
if (outputJson()) {
print("%s", json(clusters));
} else {
for (TopologyCluster cluster : clusters) {
print(FMT, cluster.id().index(), cluster.deviceCount(), cluster.linkCount());
}
}
}
// Produces a JSON result.
private JsonNode json(Iterable<TopologyCluster> clusters) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (TopologyCluster cluster : clusters) {
result.add(mapper.createObjectNode()
.put("id", cluster.id().index())
.put("deviceCount", cluster.deviceCount())
.put("linkCount", cluster.linkCount()));
}
return result;
}
}
......
package org.onlab.onos.cli.net;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.apache.karaf.shell.commands.Option;
import org.onlab.onos.cli.Comparators;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.Port;
......@@ -22,6 +27,14 @@ public class DevicePortsListCommand extends DevicesListCommand {
private static final String FMT = " port=%s, state=%s";
@Option(name = "-e", aliases = "--enabled", description = "Show only enabled ports",
required = false, multiValued = false)
private boolean enabled = false;
@Option(name = "-d", aliases = "--disabled", description = "Show only disabled ports",
required = false, multiValued = false)
private boolean disabled = false;
@Argument(index = 0, name = "uri", description = "Device ID",
required = false, multiValued = false)
String uri = null;
......@@ -30,27 +43,79 @@ public class DevicePortsListCommand extends DevicesListCommand {
protected void execute() {
DeviceService service = get(DeviceService.class);
if (uri == null) {
if (outputJson()) {
print("%s", jsonPorts(service, getSortedDevices(service)));
} else {
for (Device device : getSortedDevices(service)) {
printDevice(service, device);
}
}
} else {
Device device = service.getDevice(deviceId(uri));
if (device == null) {
error("No such device %s", uri);
} else if (outputJson()) {
print("%s", jsonPorts(service, new ObjectMapper(), device));
} else {
printDevice(service, device);
}
}
}
/**
* Produces JSON array containing ports of the specified devices.
*
* @param service device service
* @param devices collection of devices
* @return JSON array
*/
public JsonNode jsonPorts(DeviceService service, Iterable<Device> devices) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (Device device : devices) {
result.add(jsonPorts(service, mapper, device));
}
return result;
}
/**
* Produces JSON array containing ports of the specified device.
*
* @param service device service
* @param mapper object mapper
* @param device infrastructure devices
* @return JSON array
*/
public JsonNode jsonPorts(DeviceService service, ObjectMapper mapper, Device device) {
ObjectNode result = mapper.createObjectNode();
ArrayNode ports = mapper.createArrayNode();
for (Port port : service.getPorts(device.id())) {
if (isIncluded(port)) {
ports.add(mapper.createObjectNode()
.put("port", port.number().toString())
.put("isEnabled", port.isEnabled()));
}
}
return result.put("device", device.id().toString()).set("ports", ports);
}
// Determines if a port should be included in output.
private boolean isIncluded(Port port) {
return enabled && port.isEnabled() || disabled && !port.isEnabled() ||
!enabled && !disabled;
}
@Override
protected void printDevice(DeviceService service, Device device) {
super.printDevice(service, device);
List<Port> ports = new ArrayList<>(service.getPorts(device.id()));
Collections.sort(ports, Comparators.PORT_COMPARATOR);
for (Port port : ports) {
if (isIncluded(port)) {
print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled");
}
}
}
}
......
package org.onlab.onos.cli.net;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.cli.Comparators;
......@@ -24,10 +28,53 @@ public class DevicesListCommand extends AbstractShellCommand {
@Override
protected void execute() {
DeviceService service = get(DeviceService.class);
if (outputJson()) {
print("%s", json(service, getSortedDevices(service)));
} else {
for (Device device : getSortedDevices(service)) {
printDevice(service, device);
}
}
}
/**
* Returns JSON node representing the specified devices.
*
* @param service device service
* @param devices collection of devices
* @return JSON node
*/
public static JsonNode json(DeviceService service, Iterable<Device> devices) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (Device device : devices) {
result.add(json(service, mapper, device));
}
return result;
}
/**
* Returns JSON node representing the specified device.
*
* @param service device service
* @param mapper object mapper
* @param device infrastructure device
* @return JSON node
*/
public static ObjectNode json(DeviceService service, ObjectMapper mapper,
Device device) {
ObjectNode result = mapper.createObjectNode();
if (device != null) {
result.put("id", device.id().toString())
.put("available", service.isAvailable(device.id()))
.put("role", service.getRole(device.id()).toString())
.put("mfr", device.manufacturer())
.put("hw", device.hwVersion())
.put("sw", device.swVersion())
.put("serial", device.serialNumber());
}
return result;
}
/**
* Returns the list of devices sorted using the device ID URIs.
......
package org.onlab.onos.cli.net;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Maps;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
......@@ -12,6 +16,8 @@ import org.onlab.onos.net.device.DeviceService;
import org.onlab.onos.net.flow.FlowEntry;
import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
import org.onlab.onos.net.flow.FlowRuleService;
import org.onlab.onos.net.flow.criteria.Criterion;
import org.onlab.onos.net.flow.instructions.Instruction;
import java.util.Collections;
import java.util.List;
......@@ -48,10 +54,74 @@ public class FlowsListCommand extends AbstractShellCommand {
DeviceService deviceService = get(DeviceService.class);
FlowRuleService service = get(FlowRuleService.class);
Map<Device, List<FlowEntry>> flows = getSortedFlows(deviceService, service);
if (outputJson()) {
print("%s", json(coreService, getSortedDevices(deviceService), flows));
} else {
for (Device d : getSortedDevices(deviceService)) {
printFlows(d, flows.get(d), coreService);
}
}
}
/**
* Produces a JSON array of flows grouped by the each device.
*
* @param coreService core service
* @param devices collection of devices to group flow by
* @param flows collection of flows per each device
* @return JSON array
*/
private JsonNode json(CoreService coreService, Iterable<Device> devices,
Map<Device, List<FlowEntry>> flows) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (Device device : devices) {
result.add(json(coreService, mapper, device, flows.get(device)));
}
return result;
}
// Produces JSON object with the flows of the given device.
private ObjectNode json(CoreService coreService, ObjectMapper mapper,
Device device, List<FlowEntry> flows) {
ObjectNode result = mapper.createObjectNode();
ArrayNode array = mapper.createArrayNode();
for (FlowEntry flow : flows) {
array.add(json(coreService, mapper, flow));
}
result.put("device", device.id().toString())
.put("flowCount", flows.size())
.set("flows", array);
return result;
}
// Produces JSON structure with the specified flow data.
private ObjectNode json(CoreService coreService, ObjectMapper mapper,
FlowEntry flow) {
ObjectNode result = mapper.createObjectNode();
ArrayNode crit = mapper.createArrayNode();
for (Criterion c : flow.selector().criteria()) {
crit.add(c.toString());
}
ArrayNode instr = mapper.createArrayNode();
for (Instruction i : flow.treatment().instructions()) {
instr.add(i.toString());
}
result.put("flowId", Long.toHexString(flow.id().value()))
.put("state", flow.state().toString())
.put("bytes", flow.bytes())
.put("packets", flow.packets())
.put("life", flow.life())
.put("appId", coreService.getAppId(flow.appId()).name());
result.set("selector", crit);
result.set("treatment", instr);
return result;
}
/**
* Returns the list of devices sorted using the device ID URIs.
......
package org.onlab.onos.cli.net;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.cli.Comparators;
import org.onlab.onos.net.Host;
import org.onlab.onos.net.host.HostService;
import org.onlab.packet.IpPrefix;
import java.util.Collections;
import java.util.List;
......@@ -24,10 +29,41 @@ public class HostsListCommand extends AbstractShellCommand {
@Override
protected void execute() {
HostService service = get(HostService.class);
if (outputJson()) {
print("%s", json(getSortedHosts(service)));
} else {
for (Host host : getSortedHosts(service)) {
printHost(host);
}
}
}
// Produces JSON structure.
private static JsonNode json(Iterable<Host> hosts) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (Host host : hosts) {
result.add(json(mapper, host));
}
return result;
}
// Produces JSON structure.
private static JsonNode json(ObjectMapper mapper, Host host) {
ObjectNode loc = LinksListCommand.json(mapper, host.location())
.put("time", host.location().time());
ArrayNode ips = mapper.createArrayNode();
for (IpPrefix ip : host.ipAddresses()) {
ips.add(ip.toString());
}
ObjectNode result = mapper.createObjectNode()
.put("id", host.id().toString())
.put("mac", host.mac().toString())
.put("vlan", host.vlan().toString());
result.set("location", loc);
result.set("ips", ips);
return result;
}
/**
* Returns the list of devices sorted using the device ID URIs.
......@@ -44,7 +80,7 @@ public class HostsListCommand extends AbstractShellCommand {
/**
* Prints information about a host.
*
* @param host
* @param host end-station host
*/
protected void printHost(Host host) {
if (host != null) {
......@@ -54,4 +90,4 @@ public class HostsListCommand extends AbstractShellCommand {
host.vlan(), host.ipAddresses());
}
}
}
}
......
......@@ -90,11 +90,15 @@ public class IntentPushTestCommand extends AbstractShellCommand
service.submit(intent);
}
try {
latch.await(5, TimeUnit.SECONDS);
if (latch.await(10, TimeUnit.SECONDS)) {
printResults(count);
} else {
print("I FAIL MISERABLY -> %d", latch.getCount());
}
} catch (InterruptedException e) {
print(e.toString());
}
service.removeListener(this);
}
......@@ -140,6 +144,8 @@ public class IntentPushTestCommand extends AbstractShellCommand
} else {
log.warn("install event latch is null");
}
} else {
log.info("I FAIL -> {}", event);
}
}
}
......
package org.onlab.onos.cli.net;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.link.LinkService;
......@@ -27,10 +32,56 @@ public class LinksListCommand extends AbstractShellCommand {
LinkService service = get(LinkService.class);
Iterable<Link> links = uri != null ?
service.getDeviceLinks(deviceId(uri)) : service.getLinks();
if (outputJson()) {
print("%s", json(links));
} else {
for (Link link : links) {
print(linkString(link));
}
}
}
/**
* Produces a JSON array containing the specified links.
*
* @param links collection of links
* @return JSON array
*/
public static JsonNode json(Iterable<Link> links) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (Link link : links) {
result.add(json(mapper, link));
}
return result;
}
/**
* Produces a JSON object for the specified link.
*
* @param mapper object mapper
* @param link link to encode
* @return JSON object
*/
public static ObjectNode json(ObjectMapper mapper, Link link) {
ObjectNode result = mapper.createObjectNode();
result.set("src", json(mapper, link.src()));
result.set("dst", json(mapper, link.dst()));
return result;
}
/**
* Produces a JSON object for the specified connect point.
*
* @param mapper object mapper
* @param connectPoint connection point to encode
* @return JSON object
*/
public static ObjectNode json(ObjectMapper mapper, ConnectPoint connectPoint) {
return mapper.createObjectNode()
.put("device", connectPoint.deviceId().toString())
.put("port", connectPoint.port().toString());
}
/**
* Returns a formatted string representing the given link.
......
package org.onlab.onos.cli.net;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.net.Link;
......@@ -32,10 +35,31 @@ public class PathListCommand extends TopologyCommand {
protected void execute() {
init();
Set<Path> paths = service.getPaths(topology, deviceId(src), deviceId(dst));
if (outputJson()) {
print("%s", json(paths));
} else {
for (Path path : paths) {
print(pathString(path));
}
}
}
/**
* Produces a JSON array containing the specified paths.
*
* @param paths collection of paths
* @return JSON array
*/
public static JsonNode json(Iterable<Path> paths) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
for (Path path : paths) {
result.add(LinksListCommand.json(mapper, path)
.put("cost", path.cost())
.set("links", LinksListCommand.json(path.links())));
}
return result;
}
/**
* Produces a formatted string representing the specified path.
......
package org.onlab.onos.cli.net;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.karaf.shell.commands.Command;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.net.topology.Topology;
......@@ -30,8 +31,17 @@ public class TopologyCommand extends AbstractShellCommand {
@Override
protected void execute() {
init();
if (outputJson()) {
print("%s", new ObjectMapper().createObjectNode()
.put("time", topology.time())
.put("deviceCount", topology.deviceCount())
.put("linkCount", topology.linkCount())
.put("clusterCount", topology.clusterCount())
.put("pathCount", topology.pathCount()));
} else {
print(FMT, topology.time(), topology.deviceCount(), topology.linkCount(),
topology.clusterCount(), topology.pathCount());
}
}
}
......
......@@ -13,6 +13,10 @@
<command>
<action class="org.onlab.onos.cli.NodeRemoveCommand"/>
</command>
<command>
<action class="org.onlab.onos.cli.RolesCommand"/>
</command>
<command>
<action class="org.onlab.onos.cli.MastersListCommand"/>
<completers>
......
......@@ -12,8 +12,12 @@ public final class ControllerNodeToNodeId
@Override
public NodeId apply(ControllerNode input) {
if (input == null) {
return null;
} else {
return input.id();
}
}
/**
* Returns a Function to convert ControllerNode to NodeId.
......
package org.onlab.onos.mastership;
import java.util.List;
import java.util.Set;
import org.onlab.onos.cluster.NodeId;
......@@ -50,6 +51,15 @@ public interface MastershipService {
NodeId getMasterFor(DeviceId deviceId);
/**
* Returns controllers connected to a given device, in order of
* preference. The first entry in the list is the current master.
*
* @param deviceId the identifier of the device
* @return a list of controller IDs
*/
List<NodeId> getNodesFor(DeviceId deviceId);
/**
* Returns the devices for which a controller is master.
*
* @param nodeId the ID of the controller
......
package org.onlab.onos.mastership;
import java.util.List;
import java.util.Set;
import org.onlab.onos.cluster.NodeId;
......@@ -41,6 +42,15 @@ public interface MastershipStore extends Store<MastershipEvent, MastershipStoreD
NodeId getMaster(DeviceId deviceId);
/**
* Returns the controllers connected to a device, in mastership-
* preference order.
*
* @param deviceId the device identifier
* @return an ordered list of controller IDs
*/
List<NodeId> getNodes(DeviceId deviceId);
/**
* Returns the devices that a controller instance is master of.
*
* @param nodeId the instance identifier
......@@ -48,6 +58,7 @@ public interface MastershipStore extends Store<MastershipEvent, MastershipStoreD
*/
Set<DeviceId> getDevices(NodeId nodeId);
/**
* Sets a device's role for a specified controller instance.
*
......
package org.onlab.onos.net;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.packet.ChassisId;
import java.util.Objects;
......@@ -16,6 +17,7 @@ public class DefaultDevice extends AbstractElement implements Device {
private final String serialNumber;
private final String hwVersion;
private final String swVersion;
private final ChassisId chassisId;
// For serialization
private DefaultDevice() {
......@@ -24,6 +26,7 @@ public class DefaultDevice extends AbstractElement implements Device {
this.hwVersion = null;
this.swVersion = null;
this.serialNumber = null;
this.chassisId = null;
}
/**
......@@ -40,13 +43,15 @@ public class DefaultDevice extends AbstractElement implements Device {
*/
public DefaultDevice(ProviderId providerId, DeviceId id, Type type,
String manufacturer, String hwVersion, String swVersion,
String serialNumber, Annotations... annotations) {
String serialNumber, ChassisId chassisId,
Annotations... annotations) {
super(providerId, id, annotations);
this.type = type;
this.manufacturer = manufacturer;
this.hwVersion = hwVersion;
this.swVersion = swVersion;
this.serialNumber = serialNumber;
this.chassisId = chassisId;
}
@Override
......@@ -80,6 +85,11 @@ public class DefaultDevice extends AbstractElement implements Device {
}
@Override
public ChassisId chassisId() {
return chassisId;
}
@Override
public int hashCode() {
return Objects.hash(id, type, manufacturer, hwVersion, swVersion, serialNumber);
}
......
package org.onlab.onos.net;
import org.onlab.packet.ChassisId;
/**
* Representation of a network infrastructure device.
*/
......@@ -54,6 +56,13 @@ public interface Device extends Element {
*/
String serialNumber();
/**
* Returns the device chassis id.
*
* @return chassis id
*/
ChassisId chassisId();
// Device realizedBy(); ?
// ports are not provided directly, but rather via DeviceService.getPorts(Device device);
......
......@@ -2,6 +2,7 @@ package org.onlab.onos.net.device;
import org.onlab.onos.net.AbstractDescription;
import org.onlab.onos.net.SparseAnnotations;
import org.onlab.packet.ChassisId;
import java.net.URI;
......@@ -20,6 +21,7 @@ public class DefaultDeviceDescription extends AbstractDescription
private final String hwVersion;
private final String swVersion;
private final String serialNumber;
private final ChassisId chassisId;
/**
* Creates a device description using the supplied information.
......@@ -34,7 +36,7 @@ public class DefaultDeviceDescription extends AbstractDescription
*/
public DefaultDeviceDescription(URI uri, Type type, String manufacturer,
String hwVersion, String swVersion,
String serialNumber,
String serialNumber, ChassisId chassis,
SparseAnnotations... annotations) {
super(annotations);
this.uri = checkNotNull(uri, "Device URI cannot be null");
......@@ -43,6 +45,7 @@ public class DefaultDeviceDescription extends AbstractDescription
this.hwVersion = hwVersion;
this.swVersion = swVersion;
this.serialNumber = serialNumber;
this.chassisId = chassis;
}
/**
......@@ -54,7 +57,7 @@ public class DefaultDeviceDescription extends AbstractDescription
SparseAnnotations... annotations) {
this(base.deviceURI(), base.type(), base.manufacturer(),
base.hwVersion(), base.swVersion(), base.serialNumber(),
annotations);
base.chassisId(), annotations);
}
@Override
......@@ -88,6 +91,11 @@ public class DefaultDeviceDescription extends AbstractDescription
}
@Override
public ChassisId chassisId() {
return chassisId;
}
@Override
public String toString() {
return toStringHelper(this)
.add("uri", uri).add("type", type).add("mfr", manufacturer)
......@@ -104,5 +112,6 @@ public class DefaultDeviceDescription extends AbstractDescription
this.hwVersion = null;
this.swVersion = null;
this.serialNumber = null;
this.chassisId = null;
}
}
......
......@@ -2,6 +2,7 @@ package org.onlab.onos.net.device;
import org.onlab.onos.net.Description;
import org.onlab.onos.net.Device;
import org.onlab.packet.ChassisId;
import java.net.URI;
......@@ -54,4 +55,11 @@ public interface DeviceDescription extends Description {
*/
String serialNumber();
/**
* Returns a device chassis id.
*
* @return chassis id
*/
ChassisId chassisId();
}
......
......@@ -6,9 +6,10 @@ import static org.slf4j.LoggerFactory.getLogger;
import org.onlab.onos.net.DeviceId;
import org.slf4j.Logger;
public class DefaultFlowEntry extends DefaultFlowRule implements FlowEntry {
public class DefaultFlowEntry extends DefaultFlowRule
implements FlowEntry, StoredFlowEntry {
private final Logger log = getLogger(getClass());
private static final Logger log = getLogger(DefaultFlowEntry.class);
private long life;
private long packets;
......
......@@ -11,7 +11,7 @@ import org.slf4j.Logger;
public class DefaultFlowRule implements FlowRule {
private final Logger log = getLogger(getClass());
private static final Logger log = getLogger(DefaultFlowRule.class);
private final DeviceId deviceId;
private final int priority;
......
......@@ -65,6 +65,7 @@ public interface FlowEntry extends FlowRule {
*/
long bytes();
// TODO: consider removing this attribute
/**
* When this flow entry was last deemed active.
* @return epoch time of last activity
......@@ -72,35 +73,6 @@ public interface FlowEntry extends FlowRule {
long lastSeen();
/**
* Sets the last active epoch time.
*/
void setLastSeen();
/**
* Sets the new state for this entry.
* @param newState new flow entry state.
*/
void setState(FlowEntryState newState);
/**
* Sets how long this entry has been entered in the system.
* @param life epoch time
*/
void setLife(long life);
/**
* Number of packets seen by this entry.
* @param packets a long value
*/
void setPackets(long packets);
/**
* Number of bytes seen by this rule.
* @param bytes a long value
*/
void setBytes(long bytes);
/**
* Indicates the error type.
* @return an integer value of the error
*/
......
package org.onlab.onos.net.flow;
public interface StoredFlowEntry extends FlowEntry {
/**
* Sets the last active epoch time.
*/
void setLastSeen();
/**
* Sets the new state for this entry.
* @param newState new flow entry state.
*/
void setState(FlowEntryState newState);
/**
* Sets how long this entry has been entered in the system.
* @param life epoch time
*/
void setLife(long life);
/**
* Number of packets seen by this entry.
* @param packets a long value
*/
void setPackets(long packets);
/**
* Number of bytes seen by this rule.
* @param bytes a long value
*/
void setBytes(long bytes);
}
package org.onlab.onos.net.host;
import java.util.Collections;
import java.util.Set;
import org.onlab.onos.net.AbstractDescription;
import org.onlab.onos.net.HostLocation;
import org.onlab.onos.net.SparseAnnotations;
......@@ -7,6 +10,8 @@ import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import com.google.common.collect.ImmutableSet;
import static com.google.common.base.MoreObjects.toStringHelper;
/**
......@@ -18,7 +23,7 @@ public class DefaultHostDescription extends AbstractDescription
private final MacAddress mac;
private final VlanId vlan;
private final HostLocation location;
private final IpPrefix ip;
private final Set<IpPrefix> ip;
/**
* Creates a host description using the supplied information.
......@@ -31,7 +36,7 @@ public class DefaultHostDescription extends AbstractDescription
public DefaultHostDescription(MacAddress mac, VlanId vlan,
HostLocation location,
SparseAnnotations... annotations) {
this(mac, vlan, location, null, annotations);
this(mac, vlan, location, Collections.<IpPrefix>emptySet(), annotations);
}
/**
......@@ -46,11 +51,26 @@ public class DefaultHostDescription extends AbstractDescription
public DefaultHostDescription(MacAddress mac, VlanId vlan,
HostLocation location, IpPrefix ip,
SparseAnnotations... annotations) {
this(mac, vlan, location, ImmutableSet.of(ip), annotations);
}
/**
* Creates a host description using the supplied information.
*
* @param mac host MAC address
* @param vlan host VLAN identifier
* @param location host location
* @param ip host IP addresses
* @param annotations optional key/value annotations map
*/
public DefaultHostDescription(MacAddress mac, VlanId vlan,
HostLocation location, Set<IpPrefix> ip,
SparseAnnotations... annotations) {
super(annotations);
this.mac = mac;
this.vlan = vlan;
this.location = location;
this.ip = ip;
this.ip = ImmutableSet.copyOf(ip);
}
@Override
......@@ -69,7 +89,7 @@ public class DefaultHostDescription extends AbstractDescription
}
@Override
public IpPrefix ipAddress() {
public Set<IpPrefix> ipAddress() {
return ip;
}
......
package org.onlab.onos.net.host;
import java.util.Set;
import org.onlab.onos.net.Description;
import org.onlab.onos.net.HostLocation;
import org.onlab.packet.IpPrefix;
......@@ -38,6 +40,6 @@ public interface HostDescription extends Description {
* @return host IP address
*/
// FIXME: Switch to IpAddress
IpPrefix ipAddress();
Set<IpPrefix> ipAddress();
}
......
......@@ -12,7 +12,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
/**
* Abstraction of end-station to end-station bidirectional connectivity.
*/
public class HostToHostIntent extends ConnectivityIntent {
public final class HostToHostIntent extends ConnectivityIntent {
private final HostId one;
private final HostId two;
......
......@@ -4,6 +4,7 @@ import java.util.Collection;
import java.util.Objects;
import java.util.Set;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
......@@ -14,10 +15,12 @@ import com.google.common.base.MoreObjects;
* Abstraction of a connectivity intent that is implemented by a set of path
* segments.
*/
public class LinkCollectionIntent extends ConnectivityIntent implements InstallableIntent {
public final class LinkCollectionIntent extends ConnectivityIntent implements InstallableIntent {
private final Set<Link> links;
private final ConnectPoint egressPoint;
/**
* Creates a new point-to-point intent with the supplied ingress/egress
* ports and using the specified explicit path.
......@@ -26,19 +29,23 @@ public class LinkCollectionIntent extends ConnectivityIntent implements Installa
* @param selector traffic match
* @param treatment action
* @param links traversed links
* @param egressPoint egress point
* @throws NullPointerException {@code path} is null
*/
public LinkCollectionIntent(IntentId id,
TrafficSelector selector,
TrafficTreatment treatment,
Set<Link> links) {
Set<Link> links,
ConnectPoint egressPoint) {
super(id, selector, treatment);
this.links = links;
this.egressPoint = egressPoint;
}
protected LinkCollectionIntent() {
super();
this.links = null;
this.egressPoint = null;
}
@Override
......@@ -46,10 +53,25 @@ public class LinkCollectionIntent extends ConnectivityIntent implements Installa
return links;
}
/**
* Returns the set of links that represent the network connections needed
* by this intent.
*
* @return Set of links for the network hops needed by this intent
*/
public Set<Link> links() {
return links;
}
/**
* Returns the egress point of the intent.
*
* @return the egress point
*/
public ConnectPoint egressPoint() {
return egressPoint;
}
@Override
public boolean equals(Object o) {
if (this == o) {
......@@ -64,12 +86,13 @@ public class LinkCollectionIntent extends ConnectivityIntent implements Installa
LinkCollectionIntent that = (LinkCollectionIntent) o;
return Objects.equals(this.links, that.links);
return Objects.equals(this.links, that.links) &&
Objects.equals(this.egressPoint, that.egressPoint);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), links);
return Objects.hash(super.hashCode(), links, egressPoint);
}
@Override
......@@ -79,6 +102,7 @@ public class LinkCollectionIntent extends ConnectivityIntent implements Installa
.add("match", selector())
.add("action", treatment())
.add("links", links())
.add("egress", egressPoint())
.toString();
}
}
......
......@@ -15,7 +15,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
/**
* Abstraction of multiple source to single destination connectivity intent.
*/
public class MultiPointToSinglePointIntent extends ConnectivityIntent {
public final class MultiPointToSinglePointIntent extends ConnectivityIntent {
private final Set<ConnectPoint> ingressPoints;
private final ConnectPoint egressPoint;
......
......@@ -62,6 +62,9 @@ public abstract class AbstractProviderRegistry<P extends Provider, S extends Pro
((AbstractProviderService) service).invalidate();
services.remove(provider.id());
providers.remove(provider.id());
if (!provider.id().isAncillary()) {
providersByScheme.remove(provider.id().scheme());
}
}
}
......
......@@ -7,6 +7,8 @@ import org.onlab.onos.net.Provided;
*/
public interface Topology extends Provided {
// FIXME: Following is not true right now. It is actually System.nanoTime(),
// which has no relation to epoch time, wall clock, etc.
/**
* Returns the time, specified in milliseconds since start of epoch,
* when the topology became active and made available.
......
......@@ -37,6 +37,15 @@ public interface ClusterCommunicationService {
boolean multicast(ClusterMessage message, Set<NodeId> nodeIds) throws IOException;
/**
* Sends a message synchronously.
* @param message message to send
* @param toNodeId recipient node identifier
* @return ClusterMessageResponse which is reply future.
* @throws IOException
*/
ClusterMessageResponse sendAndReceive(ClusterMessage message, NodeId toNodeId) throws IOException;
/**
* Adds a new subscriber for the specified message subject.
*
* @param subject message subject
......
package org.onlab.onos.store.cluster.messaging;
import java.io.IOException;
import org.onlab.onos.cluster.NodeId;
// TODO: Should payload type be ByteBuffer?
......@@ -49,4 +51,14 @@ public class ClusterMessage {
public byte[] payload() {
return payload;
}
/**
* Sends a response to the sender.
*
* @param data payload response.
* @throws IOException
*/
public void respond(byte[] data) throws IOException {
throw new IllegalStateException("One can only repond to message recived from others.");
}
}
......
package org.onlab.onos.store.cluster.messaging;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.onlab.onos.cluster.NodeId;
public interface ClusterMessageResponse {
public NodeId sender();
public byte[] get(long timeout, TimeUnit timeunit) throws TimeoutException;
public byte[] get(long timeout) throws InterruptedException;
}
package org.onlab.onos.cluster;
import static com.google.common.base.Predicates.notNull;
import static org.junit.Assert.*;
import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
......@@ -30,12 +31,13 @@ public class ControllerNodeToNodeIdTest {
@Test
public final void testToNodeId() {
final Iterable<ControllerNode> nodes = Arrays.asList(CN1, CN2, CN3);
final Iterable<ControllerNode> nodes = Arrays.asList(CN1, CN2, CN3, null);
final List<NodeId> nodeIds = Arrays.asList(NID1, NID2, NID3);
assertEquals(nodeIds,
FluentIterable.from(nodes)
.transform(toNodeId())
.filter(notNull())
.toList());
}
......
......@@ -4,6 +4,7 @@ import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.MastershipRole;
import java.util.List;
import java.util.Set;
/**
......@@ -46,4 +47,9 @@ public class MastershipServiceAdapter implements MastershipService {
public MastershipTermService requestTermService() {
return null;
}
@Override
public List<NodeId> getNodesFor(DeviceId deviceId) {
return null;
}
}
......
......@@ -3,6 +3,7 @@ package org.onlab.onos.net;
import com.google.common.testing.EqualsTester;
import org.junit.Test;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.packet.ChassisId;
import static org.junit.Assert.assertEquals;
import static org.onlab.onos.net.Device.Type.SWITCH;
......@@ -21,14 +22,15 @@ public class DefaultDeviceTest {
static final String SW = "3.9.1";
static final String SN1 = "43311-12345";
static final String SN2 = "42346-43512";
static final ChassisId CID = new ChassisId();
@Test
public void testEquality() {
Device d1 = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1);
Device d2 = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1);
Device d3 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN2);
Device d4 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN2);
Device d5 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN1);
Device d1 = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID);
Device d2 = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID);
Device d3 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN2, CID);
Device d4 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN2, CID);
Device d5 = new DefaultDevice(PID, DID2, SWITCH, MFR, HW, SW, SN1, CID);
new EqualsTester().addEqualityGroup(d1, d2)
.addEqualityGroup(d3, d4)
......@@ -38,13 +40,13 @@ public class DefaultDeviceTest {
@Test
public void basics() {
Device device = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1);
Device device = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID);
validate(device);
}
@Test
public void annotations() {
Device device = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1,
Device device = new DefaultDevice(PID, DID1, SWITCH, MFR, HW, SW, SN1, CID,
DefaultAnnotations.builder().set("foo", "bar").build());
validate(device);
assertEquals("incorrect provider", "bar", device.annotations().value("foo"));
......
......@@ -3,6 +3,7 @@ package org.onlab.onos.net;
import com.google.common.testing.EqualsTester;
import org.junit.Test;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.packet.ChassisId;
import static org.junit.Assert.assertEquals;
import static org.onlab.onos.net.Device.Type.SWITCH;
......@@ -22,7 +23,8 @@ public class DefaultPortTest {
@Test
public void testEquality() {
Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n");
Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n",
new ChassisId());
Port p1 = new DefaultPort(device, portNumber(1), true);
Port p2 = new DefaultPort(device, portNumber(1), true);
Port p3 = new DefaultPort(device, portNumber(2), true);
......@@ -37,7 +39,8 @@ public class DefaultPortTest {
@Test
public void basics() {
Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n");
Device device = new DefaultDevice(PID, DID1, SWITCH, "m", "h", "s", "n",
new ChassisId());
Port port = new DefaultPort(device, portNumber(1), true);
assertEquals("incorrect element", device, port.element());
assertEquals("incorrect number", portNumber(1), port.number());
......
package org.onlab.onos.net;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.packet.ChassisId;
import org.onlab.packet.IpPrefix;
import java.util.ArrayList;
......@@ -37,7 +38,7 @@ public final class NetTestTools {
// Crates a new device with the specified id
public static Device device(String id) {
return new DefaultDevice(PID, did(id), Device.Type.SWITCH,
"mfg", "1.0", "1.1", "1234");
"mfg", "1.0", "1.1", "1234", new ChassisId());
}
// Crates a new host with the specified id
......@@ -47,10 +48,16 @@ public final class NetTestTools {
new HashSet<IpPrefix>());
}
// Short-hand for creating a connection point.
public static ConnectPoint connectPoint(String id, int port) {
return new ConnectPoint(did(id), portNumber(port));
}
// Short-hand for creating a link.
public static Link link(String src, int sp, String dst, int dp) {
return new DefaultLink(PID, new ConnectPoint(did(src), portNumber(sp)),
new ConnectPoint(did(dst), portNumber(dp)),
return new DefaultLink(PID,
connectPoint(src, sp),
connectPoint(dst, dp),
Link.Type.DIRECT);
}
......
package org.onlab.onos.net.device;
import org.junit.Test;
import org.onlab.packet.ChassisId;
import java.net.URI;
......@@ -18,12 +19,13 @@ public class DefaultDeviceDescriptionTest {
private static final String HW = "1.1.x";
private static final String SW = "3.9.1";
private static final String SN = "43311-12345";
private static final ChassisId CID = new ChassisId();
@Test
public void basics() {
DeviceDescription device =
new DefaultDeviceDescription(DURI, SWITCH, MFR, HW, SW, SN);
new DefaultDeviceDescription(DURI, SWITCH, MFR, HW, SW, SN, CID);
assertEquals("incorrect uri", DURI, device.deviceURI());
assertEquals("incorrect type", SWITCH, device.type());
assertEquals("incorrect manufacturer", MFR, device.manufacturer());
......@@ -31,6 +33,7 @@ public class DefaultDeviceDescriptionTest {
assertEquals("incorrect sw", SW, device.swVersion());
assertEquals("incorrect serial", SN, device.serialNumber());
assertTrue("incorrect toString", device.toString().contains("uri=of:foo"));
assertTrue("Incorrect chassis", device.chassisId().value() == 0);
}
}
......
......@@ -11,6 +11,7 @@ import org.onlab.onos.net.Device;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.packet.ChassisId;
/**
* Tests of the device event.
......@@ -19,7 +20,7 @@ public class DeviceEventTest extends AbstractEventTest {
private Device createDevice() {
return new DefaultDevice(new ProviderId("of", "foo"), deviceId("of:foo"),
Device.Type.SWITCH, "box", "hw", "sw", "sn");
Device.Type.SWITCH, "box", "hw", "sw", "sn", new ChassisId());
}
@Override
......
......@@ -8,6 +8,8 @@ import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import com.google.common.collect.ImmutableSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
......@@ -33,7 +35,7 @@ public class DefualtHostDecriptionTest {
assertEquals("incorrect mac", MAC, host.hwAddress());
assertEquals("incorrect vlan", VLAN, host.vlan());
assertEquals("incorrect location", LOC, host.location());
assertEquals("incorrect ip's", IP, host.ipAddress());
assertEquals("incorrect ip's", ImmutableSet.of(IP), host.ipAddress());
assertTrue("incorrect toString", host.toString().contains("vlan=10"));
}
......
......@@ -18,9 +18,9 @@ public class DefaultGraphDescriptionTest {
private static final DeviceId D3 = deviceId("3");
static final Device DEV1 = new DefaultDevice(PID, D1, SWITCH, "", "", "", "");
static final Device DEV2 = new DefaultDevice(PID, D2, SWITCH, "", "", "", "");
static final Device DEV3 = new DefaultDevice(PID, D3, SWITCH, "", "", "", "");
static final Device DEV1 = new DefaultDevice(PID, D1, SWITCH, "", "", "", "", null);
static final Device DEV2 = new DefaultDevice(PID, D2, SWITCH, "", "", "", "", null);
static final Device DEV3 = new DefaultDevice(PID, D3, SWITCH, "", "", "", "", null);
@Test
public void basics() {
......
......@@ -6,15 +6,15 @@
<parent>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-core-hz</artifactId>
<artifactId>onos-core</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-core-hz-net</artifactId>
<artifactId>onos-json</artifactId>
<packaging>bundle</packaging>
<description>ONOS Hazelcast based distributed store subsystems</description>
<description>ONOS JSON encode/decode facilities</description>
<dependencies>
<dependency>
......@@ -23,24 +23,22 @@
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-core-hz-common</artifactId>
<version>${project.version}</version>
<artifactId>onos-api</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-core-hz-common</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
<artifactId>onos-core-trivial</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
</dependency>
</dependencies>
<build>
......
package org.onlab.onos.json.impl;
/**
* Created by tom on 10/16/14.
*/
public class DeleteMe {
}
/**
* Implementation of JSON codec factory and of the builtin codecs.
*/
package org.onlab.onos.json.impl;
\ No newline at end of file
......@@ -42,23 +42,6 @@
<scope>test</scope>
</dependency>
<!-- TODO Consider removing store dependency.
Currently required for DistributedDeviceManagerTest. -->
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-core-hz-net</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<!-- FIXME: should be somewhere else -->
<artifactId>onos-core-hz-common</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
......
......@@ -3,6 +3,7 @@ package org.onlab.onos.cluster.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
......@@ -103,7 +104,6 @@ implements MastershipService, MastershipAdminService {
MastershipEvent event = null;
event = store.relinquishRole(
clusterService.getLocalNode().id(), deviceId);
if (event != null) {
post(event);
}
......@@ -127,6 +127,11 @@ implements MastershipService, MastershipAdminService {
return store.getDevices(nodeId);
}
@Override
public List<NodeId> getNodesFor(DeviceId deviceId) {
checkNotNull(deviceId, DEVICE_ID_NULL);
return store.getNodes(deviceId);
}
@Override
public MastershipTermService requestTermService() {
......@@ -223,7 +228,8 @@ implements MastershipService, MastershipAdminService {
return true;
}
//else {
//FIXME: break tie for equal-sized clusters, can we use hz's functions?
//FIXME: break tie for equal-sized clusters,
// maybe by number of connected switches
// }
return false;
}
......
/**
*
* Miscellaneous core system implementations.
*/
package org.onlab.onos.impl;
\ No newline at end of file
......
......@@ -144,7 +144,7 @@ public class DeviceManager
// Applies the specified role to the device; ignores NONE
private void applyRole(DeviceId deviceId, MastershipRole newRole) {
if (newRole.equals(MastershipRole.NONE)) {
if (!newRole.equals(MastershipRole.NONE)) {
Device device = store.getDevice(deviceId);
// FIXME: Device might not be there yet. (eventual consistent)
if (device == null) {
......@@ -161,6 +161,9 @@ public class DeviceManager
@Override
public void removeDevice(DeviceId deviceId) {
checkNotNull(deviceId, DEVICE_ID_NULL);
// XXX is this intended to apply to the full global topology?
// if so, we probably don't want the fact that we aren't
// MASTER to get in the way, as it would do now.
DeviceEvent event = store.removeDevice(deviceId);
if (event != null) {
log.info("Device {} administratively removed", deviceId);
......@@ -203,19 +206,21 @@ public class DeviceManager
log.info("Device {} connected", deviceId);
// check my Role
MastershipRole role = mastershipService.requestRoleFor(deviceId);
log.info("## - our role for {} is {} [master is {}]", deviceId, role,
mastershipService.getMasterFor(deviceId));
if (role != MastershipRole.MASTER) {
// TODO: Do we need to explicitly tell the Provider that
// this instance is no longer the MASTER? probably not
return;
}
MastershipTerm term = mastershipService.requestTermService()
.getMastershipTerm(deviceId);
if (!term.master().equals(clusterService.getLocalNode().id())) {
// lost mastership after requestRole told this instance was MASTER.
return;
}
// tell clock provider if this instance is the master
deviceClockProviderService.setMastershipTerm(deviceId, term);
......@@ -256,13 +261,31 @@ public class DeviceManager
// but if I was the last STANDBY connection, etc. and no one else
// was there to mark the device offline, this instance may need to
// temporarily request for Master Role and mark offline.
log.info("## for {} role is {}", deviceId, mastershipService.getLocalRole(deviceId));
if (!mastershipService.getLocalRole(deviceId).equals(MastershipRole.MASTER)) {
log.debug("Device {} disconnected, but I am not the master", deviceId);
//let go of ability to be backup
mastershipService.relinquishMastership(deviceId);
return;
}
DeviceEvent event = store.markOffline(deviceId);
DeviceEvent event = null;
try {
event = store.markOffline(deviceId);
} catch (IllegalStateException e) {
//there are times when this node will correctly have mastership, BUT
//that isn't reflected in the ClockManager before the device disconnects.
//we want to let go of the device anyways, so make sure this happens.
// FIXME: Come up with workaround for above scenario.
MastershipTerm term = termService.getMastershipTerm(deviceId);
final NodeId myNodeId = clusterService.getLocalNode().id();
// TODO: Move this type of check inside device clock manager, etc.
if (myNodeId.equals(term.master())) {
deviceClockProviderService.setMastershipTerm(deviceId, term);
event = store.markOffline(deviceId);
}
} finally {
//relinquish master role and ability to be backup.
mastershipService.relinquishMastership(deviceId);
......@@ -271,6 +294,7 @@ public class DeviceManager
post(event);
}
}
}
@Override
public void updatePorts(DeviceId deviceId,
......@@ -279,7 +303,15 @@ public class DeviceManager
checkNotNull(portDescriptions,
"Port descriptions list cannot be null");
checkValidity();
//XXX what's this doing here?
this.provider().id();
if (!mastershipService.getLocalRole(deviceId).equals(MastershipRole.MASTER)) {
// TODO If we become master, then we'll trigger something to update this
// info to fix any inconsistencies that may result during the handoff.
return;
}
List<DeviceEvent> events = store.updatePorts(this.provider().id(),
deviceId, portDescriptions);
for (DeviceEvent event : events) {
......@@ -293,6 +325,12 @@ public class DeviceManager
checkNotNull(deviceId, DEVICE_ID_NULL);
checkNotNull(portDescription, PORT_DESCRIPTION_NULL);
checkValidity();
if (!mastershipService.getLocalRole(deviceId).equals(MastershipRole.MASTER)) {
// TODO If we become master, then we'll trigger something to update this
// info to fix any inconsistencies that may result during the handoff.
return;
}
DeviceEvent event = store.updatePortStatus(this.provider().id(),
deviceId, portDescription);
if (event != null) {
......@@ -328,27 +366,37 @@ public class DeviceManager
final DeviceId did = event.subject();
final NodeId myNodeId = clusterService.getLocalNode().id();
log.info("## got Mastershipevent for dev {}", did);
if (myNodeId.equals(event.master())) {
MastershipTerm term = termService.getMastershipTerm(did);
if (term.master().equals(myNodeId)) {
if (!myNodeId.equals(term.master())) {
// something went wrong in consistency, let go
mastershipService.relinquishMastership(did);
applyRole(did, MastershipRole.STANDBY);
return;
}
log.info("## setting term for CPS as new master for {}", did);
// only set the new term if I am the master
deviceClockProviderService.setMastershipTerm(did, term);
}
// FIXME: we should check that the device is connected on our end.
// currently, this is not straight forward as the actual switch
// implementation is hidden from the registry.
if (!isAvailable(did)) {
//flag the device as online. Is there a better way to do this?
// implementation is hidden from the registry. Maybe we can ask the
// provider.
// if the device is null here, we are the first master to claim the
// device. No worries, the DeviceManager will create one soon.
Device device = getDevice(did);
if ((device != null) && !isAvailable(did)) {
//flag the device as online. Is there a better way to do this?
store.createOrUpdateDevice(device.providerId(), did,
new DefaultDeviceDescription(
did.uri(), device.type(), device.manufacturer(),
device.hwVersion(), device.swVersion(),
device.serialNumber()));
device.serialNumber(), device.chassisId()));
}
//TODO re-collect device information to fix potential staleness
applyRole(did, MastershipRole.MASTER);
} else {
applyRole(did, MastershipRole.STANDBY);
......
......@@ -3,8 +3,8 @@ package org.onlab.onos.net.flow.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
......@@ -45,6 +45,7 @@ import org.slf4j.Logger;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
/**
......@@ -197,6 +198,8 @@ public class FlowRuleManager
extends AbstractProviderService<FlowRuleProvider>
implements FlowRuleProviderService {
final Map<FlowEntry, Long> lastSeen = Maps.newConcurrentMap();
protected InternalFlowRuleProviderService(FlowRuleProvider provider) {
super(provider);
}
......@@ -205,6 +208,7 @@ public class FlowRuleManager
public void flowRemoved(FlowEntry flowEntry) {
checkNotNull(flowEntry, FLOW_RULE_NULL);
checkValidity();
lastSeen.remove(flowEntry);
FlowEntry stored = store.getFlowEntry(flowEntry);
if (stored == null) {
log.info("Rule already evicted from store: {}", flowEntry);
......@@ -292,14 +296,25 @@ public class FlowRuleManager
if (storedRule == null) {
return false;
}
long timeout = storedRule.timeout() * 1000;
Long currentTime = System.currentTimeMillis();
final long timeout = storedRule.timeout() * 1000;
final long currentTime = System.currentTimeMillis();
if (storedRule.packets() != swRule.packets()) {
storedRule.setLastSeen();
lastSeen.put(storedRule, currentTime);
return true;
}
if (!lastSeen.containsKey(storedRule)) {
// checking for the first time
lastSeen.put(storedRule, storedRule.lastSeen());
// Use following if lastSeen attr. was removed.
//lastSeen.put(storedRule, currentTime);
}
Long last = lastSeen.get(storedRule);
if (last == null) {
// concurrently removed? let the liveness check fail
return false;
}
if ((currentTime - storedRule.lastSeen()) <= timeout) {
if ((currentTime - last) <= timeout) {
return true;
}
return false;
......@@ -316,10 +331,7 @@ public class FlowRuleManager
public void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries) {
List<FlowEntry> storedRules = Lists.newLinkedList(store.getFlowEntries(deviceId));
Iterator<FlowEntry> switchRulesIterator = flowEntries.iterator();
while (switchRulesIterator.hasNext()) {
FlowEntry rule = switchRulesIterator.next();
for (FlowEntry rule : flowEntries) {
if (storedRules.remove(rule)) {
// we both have the rule, let's update some info then.
flowAdded(rule);
......@@ -401,7 +413,7 @@ public class FlowRuleManager
CompletedBatchOperation completed;
for (Future<CompletedBatchOperation> future : futures) {
completed = future.get();
success = validateBatchOperation(failed, completed, future);
success = validateBatchOperation(failed, completed);
}
return finalizeBatchOperation(success, failed);
......@@ -426,14 +438,13 @@ public class FlowRuleManager
long now = System.nanoTime();
long thisTimeout = end - now;
completed = future.get(thisTimeout, TimeUnit.NANOSECONDS);
success = validateBatchOperation(failed, completed, future);
success = validateBatchOperation(failed, completed);
}
return finalizeBatchOperation(success, failed);
}
private boolean validateBatchOperation(List<FlowEntry> failed,
CompletedBatchOperation completed,
Future<CompletedBatchOperation> future) {
CompletedBatchOperation completed) {
if (isCancelled()) {
throw new CancellationException();
......
......@@ -41,7 +41,7 @@ public class HostToHostIntentCompiler
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
private IdGenerator<IntentId> intentIdGenerator;
protected IdGenerator<IntentId> intentIdGenerator;
@Activate
public void activate() {
......
package org.onlab.onos.net.intent.impl;
import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.List;
import java.util.concurrent.Future;
......@@ -10,7 +13,9 @@ import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.onos.ApplicationId;
import org.onlab.onos.CoreService;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.flow.CompletedBatchOperation;
import org.onlab.onos.net.flow.DefaultFlowRule;
import org.onlab.onos.net.flow.DefaultTrafficSelector;
......@@ -29,9 +34,6 @@ import org.slf4j.Logger;
import com.google.common.collect.Lists;
import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Installer for {@link org.onlab.onos.net.intent.LinkCollectionIntent}
* path segment intents.
......@@ -79,15 +81,17 @@ public class LinkCollectionIntentInstaller implements IntentInstaller<LinkCollec
DefaultTrafficSelector.builder(intent.selector());
List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
for (Link link : intent.links()) {
TrafficTreatment treatment = builder()
.setOutput(link.src().port()).build();
FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
builder.build(), treatment,
123, appId, 600);
rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule));
rules.add(createBatchEntry(FlowRuleOperation.ADD,
builder.build(),
link.src().deviceId(),
link.src().port()));
}
rules.add(createBatchEntry(FlowRuleOperation.ADD,
builder.build(),
intent.egressPoint().deviceId(),
intent.egressPoint().port()));
return applyBatch(rules);
}
......@@ -98,13 +102,39 @@ public class LinkCollectionIntentInstaller implements IntentInstaller<LinkCollec
List<FlowRuleBatchEntry> rules = Lists.newLinkedList();
for (Link link : intent.links()) {
TrafficTreatment treatment = builder()
.setOutput(link.src().port()).build();
FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
builder.build(), treatment,
123, appId, 600);
rules.add(new FlowRuleBatchEntry(FlowRuleOperation.REMOVE, rule));
rules.add(createBatchEntry(FlowRuleOperation.REMOVE,
builder.build(),
link.src().deviceId(),
link.src().port()));
}
rules.add(createBatchEntry(FlowRuleOperation.REMOVE,
builder.build(),
intent.egressPoint().deviceId(),
intent.egressPoint().port()));
return applyBatch(rules);
}
/**
* Creates a FlowRuleBatchEntry based on the provided parameters.
*
* @param operation the FlowRuleOperation to use
* @param selector the traffic selector
* @param deviceId the device ID for the flow rule
* @param outPort the output port of the flow rule
* @return the new flow rule batch entry
*/
private FlowRuleBatchEntry createBatchEntry(FlowRuleOperation operation,
TrafficSelector selector,
DeviceId deviceId,
PortNumber outPort) {
TrafficTreatment treatment = builder().setOutput(outPort).build();
FlowRule rule = new DefaultFlowRule(deviceId,
selector, treatment, 123, appId, 600);
return new FlowRuleBatchEntry(operation, rule);
}
}
......
......@@ -37,7 +37,7 @@ public class MultiPointToSinglePointIntentCompiler
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PathService pathService;
private IdGenerator<IntentId> intentIdGenerator;
protected IdGenerator<IntentId> intentIdGenerator;
@Activate
public void activate() {
......@@ -62,7 +62,7 @@ public class MultiPointToSinglePointIntentCompiler
Intent result = new LinkCollectionIntent(intentIdGenerator.getNewId(),
intent.selector(), intent.treatment(),
links);
links, intent.egressPoint());
return Arrays.asList(result);
}
......
......@@ -96,7 +96,7 @@ public class PathIntentInstaller implements IntentInstaller<PathIntent> {
FlowRule rule = new DefaultFlowRule(link.src().deviceId(),
builder.build(), treatment,
123, appId, 600);
123, appId, 15);
rules.add(new FlowRuleBatchEntry(FlowRuleOperation.ADD, rule));
prev = link.dst();
}
......
......@@ -43,7 +43,7 @@ public class PointToPointIntentCompiler
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
private IdGenerator<IntentId> intentIdGenerator;
protected IdGenerator<IntentId> intentIdGenerator;
@Activate
public void activate() {
......
......@@ -16,6 +16,7 @@ import org.onlab.onos.event.EventDeliveryService;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.MastershipRole;
import org.onlab.onos.net.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceListener;
import org.onlab.onos.net.device.DeviceService;
......@@ -139,11 +140,17 @@ public class LinkManager
@Override
public void removeLinks(ConnectPoint connectPoint) {
if (deviceService.getRole(connectPoint.deviceId()) != MastershipRole.MASTER) {
return;
}
removeLinks(getLinks(connectPoint));
}
@Override
public void removeLinks(DeviceId deviceId) {
if (deviceService.getRole(deviceId) != MastershipRole.MASTER) {
return;
}
removeLinks(getDeviceLinks(deviceId));
}
......@@ -189,6 +196,15 @@ public class LinkManager
public void linkDetected(LinkDescription linkDescription) {
checkNotNull(linkDescription, LINK_DESC_NULL);
checkValidity();
ConnectPoint src = linkDescription.src();
ConnectPoint dst = linkDescription.dst();
// if we aren't master for the device associated with the ConnectPoint
// we probably shouldn't be doing this.
if ((deviceService.getRole(src.deviceId()) != MastershipRole.MASTER) ||
(deviceService.getRole(dst.deviceId()) != MastershipRole.MASTER)) {
return;
}
LinkEvent event = store.createOrUpdateLink(provider().id(),
linkDescription);
if (event != null) {
......@@ -201,6 +217,15 @@ public class LinkManager
public void linkVanished(LinkDescription linkDescription) {
checkNotNull(linkDescription, LINK_DESC_NULL);
checkValidity();
ConnectPoint src = linkDescription.src();
ConnectPoint dst = linkDescription.dst();
// if we aren't master for the device associated with the ConnectPoint
// we probably shouldn't be doing this.
if ((deviceService.getRole(src.deviceId()) != MastershipRole.MASTER) ||
(deviceService.getRole(dst.deviceId()) != MastershipRole.MASTER)) {
return;
}
LinkEvent event = store.removeLink(linkDescription.src(),
linkDescription.dst());
if (event != null) {
......@@ -213,7 +238,13 @@ public class LinkManager
public void linksVanished(ConnectPoint connectPoint) {
checkNotNull(connectPoint, "Connect point cannot be null");
checkValidity();
// if we aren't master for the device associated with the ConnectPoint
// we probably shouldn't be doing this.
if (deviceService.getRole(connectPoint.deviceId()) != MastershipRole.MASTER) {
return;
}
log.info("Links for connection point {} vanished", connectPoint);
// FIXME: This will remove links registered by other providers
removeLinks(getLinks(connectPoint));
}
......@@ -221,6 +252,11 @@ public class LinkManager
public void linksVanished(DeviceId deviceId) {
checkNotNull(deviceId, DEVICE_ID_NULL);
checkValidity();
// if we aren't master for the device associated with the ConnectPoint
// we probably shouldn't be doing this.
if (deviceService.getRole(deviceId) != MastershipRole.MASTER) {
return;
}
log.info("Links for device {} vanished", deviceId);
removeLinks(getDeviceLinks(deviceId));
}
......
......@@ -167,6 +167,7 @@ public class ProxyArpManager implements ProxyArpService {
return;
}
// TODO find the correct IP address
Ethernet arpReply = buildArpReply(dst.ipAddresses().iterator().next(),
dst.mac(), eth);
// TODO: check send status with host service.
......@@ -355,7 +356,7 @@ public class ProxyArpManager implements ProxyArpService {
arp.setTargetProtocolAddress(((ARP) request.getPayload())
.getSenderProtocolAddress());
arp.setSenderProtocolAddress(srcIp.toRealInt());
arp.setSenderProtocolAddress(srcIp.toInt());
eth.setPayload(arp);
return eth;
}
......
......@@ -23,6 +23,7 @@ import org.onlab.onos.net.topology.TopologyProviderRegistry;
import org.onlab.onos.net.topology.TopologyProviderService;
import org.slf4j.Logger;
import java.util.Collections;
import java.util.List;
import java.util.Timer;
import java.util.concurrent.ExecutorService;
......@@ -88,7 +89,7 @@ public class DefaultTopologyProvider extends AbstractProvider
linkService.addListener(linkListener);
isStarted = true;
triggerTopologyBuild(null);
triggerTopologyBuild(Collections.<Event>emptyList());
log.info("Started");
}
......
......@@ -37,6 +37,7 @@ import org.onlab.onos.net.device.PortDescription;
import org.onlab.onos.net.provider.AbstractProvider;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.trivial.impl.SimpleDeviceStore;
import org.onlab.packet.ChassisId;
import org.onlab.packet.IpPrefix;
import java.util.ArrayList;
......@@ -62,6 +63,7 @@ public class DeviceManagerTest {
private static final String SW1 = "3.8.1";
private static final String SW2 = "3.9.5";
private static final String SN = "43311-12345";
private static final ChassisId CID = new ChassisId();
private static final PortNumber P1 = PortNumber.portNumber(1);
private static final PortNumber P2 = PortNumber.portNumber(2);
......@@ -111,7 +113,7 @@ public class DeviceManagerTest {
private void connectDevice(DeviceId deviceId, String swVersion) {
DeviceDescription description =
new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
HW, swVersion, SN);
HW, swVersion, SN, CID);
providerService.deviceConnected(deviceId, description);
assertNotNull("device should be found", service.getDevice(DID1));
}
......
......@@ -8,7 +8,9 @@ import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_UPDATED;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
......@@ -44,6 +46,7 @@ import org.onlab.onos.net.flow.FlowRuleProvider;
import org.onlab.onos.net.flow.FlowRuleProviderRegistry;
import org.onlab.onos.net.flow.FlowRuleProviderService;
import org.onlab.onos.net.flow.FlowRuleService;
import org.onlab.onos.net.flow.StoredFlowEntry;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.flow.criteria.Criterion;
......@@ -54,6 +57,7 @@ import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.trivial.impl.SimpleFlowRuleStore;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
......@@ -68,7 +72,7 @@ public class FlowRuleManagerTest {
private static final DeviceId DID = DeviceId.deviceId("of:001");
private static final int TIMEOUT = 10;
private static final Device DEV = new DefaultDevice(
PID, DID, Type.SWITCH, "", "", "", "");
PID, DID, Type.SWITCH, "", "", "", "", null);
private FlowRuleManager mgr;
......@@ -166,16 +170,17 @@ public class FlowRuleManagerTest {
}
// TODO: If preserving iteration order is a requirement, redo FlowRuleStore.
//backing store is sensitive to the order of additions/removals
private boolean validateState(FlowEntryState... state) {
private boolean validateState(Map<FlowRule, FlowEntryState> expected) {
Map<FlowRule, FlowEntryState> expectedToCheck = new HashMap<>(expected);
Iterable<FlowEntry> rules = service.getFlowEntries(DID);
int i = 0;
for (FlowEntry f : rules) {
if (f.state() != state[i]) {
return false;
}
i++;
assertTrue("Unexpected FlowRule " + f, expectedToCheck.containsKey(f));
assertEquals("FlowEntry" + f, expectedToCheck.get(f), f.state());
expectedToCheck.remove(f);
}
assertEquals(Collections.emptySet(), expectedToCheck.entrySet());
return true;
}
......@@ -191,8 +196,10 @@ public class FlowRuleManagerTest {
mgr.applyFlowRules(r1, r2, r3);
assertEquals("3 rules should exist", 3, flowCount());
assertTrue("Entries should be pending add.",
validateState(FlowEntryState.PENDING_ADD, FlowEntryState.PENDING_ADD,
FlowEntryState.PENDING_ADD));
validateState(ImmutableMap.of(
r1, FlowEntryState.PENDING_ADD,
r2, FlowEntryState.PENDING_ADD,
r3, FlowEntryState.PENDING_ADD)));
}
@Test
......@@ -213,8 +220,10 @@ public class FlowRuleManagerTest {
validateEvents();
assertEquals("3 rule should exist", 3, flowCount());
assertTrue("Entries should be pending remove.",
validateState(FlowEntryState.PENDING_REMOVE, FlowEntryState.PENDING_REMOVE,
FlowEntryState.ADDED));
validateState(ImmutableMap.of(
f1, FlowEntryState.PENDING_REMOVE,
f2, FlowEntryState.PENDING_REMOVE,
f3, FlowEntryState.ADDED)));
mgr.removeFlowRules(f1);
assertEquals("3 rule should still exist", 3, flowCount());
......@@ -224,7 +233,7 @@ public class FlowRuleManagerTest {
public void flowRemoved() {
FlowRule f1 = addFlowRule(1);
FlowRule f2 = addFlowRule(2);
FlowEntry fe1 = new DefaultFlowEntry(f1);
StoredFlowEntry fe1 = new DefaultFlowEntry(f1);
FlowEntry fe2 = new DefaultFlowEntry(f2);
providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2));
service.removeFlowRules(f1);
......@@ -263,8 +272,10 @@ public class FlowRuleManagerTest {
providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2));
assertTrue("Entries should be added.",
validateState(FlowEntryState.ADDED, FlowEntryState.ADDED,
FlowEntryState.PENDING_ADD));
validateState(ImmutableMap.of(
f1, FlowEntryState.ADDED,
f2, FlowEntryState.ADDED,
f3, FlowEntryState.PENDING_ADD)));
validateEvents(RULE_ADDED, RULE_ADDED);
}
......@@ -336,7 +347,9 @@ public class FlowRuleManagerTest {
//only check that we are in pending remove. Events and actual remove state will
// be set by flowRemoved call.
validateState(FlowEntryState.PENDING_REMOVE, FlowEntryState.PENDING_REMOVE);
validateState(ImmutableMap.of(
f1, FlowEntryState.PENDING_REMOVE,
f2, FlowEntryState.PENDING_REMOVE));
}
@Test
......@@ -360,7 +373,9 @@ public class FlowRuleManagerTest {
Lists.newArrayList(fbe1, fbe2));
Future<CompletedBatchOperation> future = mgr.applyBatch(fbo);
assertTrue("Entries in wrong state",
validateState(FlowEntryState.PENDING_REMOVE, FlowEntryState.PENDING_ADD));
validateState(ImmutableMap.of(
f1, FlowEntryState.PENDING_REMOVE,
f2, FlowEntryState.PENDING_ADD)));
CompletedBatchOperation completed = null;
try {
completed = future.get();
......@@ -381,9 +396,18 @@ public class FlowRuleManagerTest {
mgr.applyFlowRules(f1);
assertTrue("Entries in wrong state",
validateState(ImmutableMap.of(
f1, FlowEntryState.PENDING_ADD)));
FlowEntry fe1 = new DefaultFlowEntry(f1);
providerService.pushFlowMetrics(DID, Collections.<FlowEntry>singletonList(fe1));
assertTrue("Entries in wrong state",
validateState(ImmutableMap.of(
f1, FlowEntryState.ADDED)));
FlowRuleBatchEntry fbe1 = new FlowRuleBatchEntry(
FlowRuleBatchEntry.FlowRuleOperation.REMOVE, f1);
......@@ -403,9 +427,9 @@ public class FlowRuleManagerTest {
* state.
*/
assertTrue("Entries in wrong state",
validateState(FlowEntryState.PENDING_REMOVE,
FlowEntryState.PENDING_ADD));
validateState(ImmutableMap.of(
f2, FlowEntryState.PENDING_REMOVE,
f1, FlowEntryState.PENDING_ADD)));
}
......
package org.onlab.onos.net.intent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.onlab.onos.net.ElementId;
import org.onlab.onos.net.Path;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.flow.criteria.Criterion;
import org.onlab.onos.net.flow.instructions.Instruction;
import org.onlab.onos.net.topology.LinkWeight;
import org.onlab.onos.net.topology.PathService;
import static org.onlab.onos.net.NetTestTools.createPath;
/**
* Common mocks used by the intent framework tests.
*/
public class IntentTestsMocks {
/**
* Mock traffic selector class used for satisfying API requirements.
*/
public static class MockSelector implements TrafficSelector {
@Override
public Set<Criterion> criteria() {
return new HashSet<>();
}
}
/**
* Mock traffic treatment class used for satisfying API requirements.
*/
public static class MockTreatment implements TrafficTreatment {
@Override
public List<Instruction> instructions() {
return new ArrayList<>();
}
}
/**
* Mock path service for creating paths within the test.
*/
public static class MockPathService implements PathService {
final String[] pathHops;
final String[] reversePathHops;
/**
* Constructor that provides a set of hops to mock.
*
* @param pathHops path hops to mock
*/
public MockPathService(String[] pathHops) {
this.pathHops = pathHops;
String[] reversed = pathHops.clone();
Collections.reverse(Arrays.asList(reversed));
reversePathHops = reversed;
}
@Override
public Set<Path> getPaths(ElementId src, ElementId dst) {
Set<Path> result = new HashSet<>();
String[] allHops = new String[pathHops.length];
if (src.toString().endsWith(pathHops[0])) {
System.arraycopy(pathHops, 0, allHops, 0, pathHops.length);
} else {
System.arraycopy(reversePathHops, 0, allHops, 0, pathHops.length);
}
result.add(createPath(allHops));
return result;
}
@Override
public Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight) {
return getPaths(src, dst);
}
}
}
package org.onlab.onos.net.intent;
import java.util.Collection;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.onlab.onos.net.Link;
/**
* Matcher to determine if a Collection of Links contains a path between a source
* and a destination.
*/
public class LinksHaveEntryWithSourceDestinationPairMatcher extends
TypeSafeMatcher<Collection<Link>> {
private final String source;
private final String destination;
/**
* Creates a matcher for a given path represented by a source and
* a destination.
*
* @param source string identifier for the source of the path
* @param destination string identifier for the destination of the path
*/
LinksHaveEntryWithSourceDestinationPairMatcher(String source,
String destination) {
this.source = source;
this.destination = destination;
}
@Override
public boolean matchesSafely(Collection<Link> links) {
for (Link link : links) {
if (link.src().elementId().toString().endsWith(source) &&
link.dst().elementId().toString().endsWith(destination)) {
return true;
}
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("link lookup for source \"");
description.appendText(source);
description.appendText(" and destination ");
description.appendText(destination);
description.appendText("\"");
}
@Override
public void describeMismatchSafely(Collection<Link> links,
Description mismatchDescription) {
mismatchDescription.appendText("was ").
appendText(links.toString());
}
/**
* Creates a link has path matcher.
*
* @param source string identifier for the source of the path
* @param destination string identifier for the destination of the path
* @return matcher to match the path
*/
public static LinksHaveEntryWithSourceDestinationPairMatcher linksHasPath(
String source,
String destination) {
return new LinksHaveEntryWithSourceDestinationPairMatcher(source,
destination);
}
}
package org.onlab.onos.net.intent;
import org.junit.Test;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.onlab.onos.net.NetTestTools.hid;
/**
* Unit tests for the HostToHostIntent class.
*/
public class TestHostToHostIntent {
private TrafficSelector selector = new IntentTestsMocks.MockSelector();
private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
private HostToHostIntent makeHostToHost(long id, HostId one, HostId two) {
return new HostToHostIntent(new IntentId(id),
one,
two,
selector,
treatment);
}
/**
* Tests the equals() method where two HostToHostIntents have references
* to the same hosts. These should compare equal.
*/
@Test
public void testSameEquals() {
HostId one = hid("00:00:00:00:00:01/-1");
HostId two = hid("00:00:00:00:00:02/-1");
HostToHostIntent i1 = makeHostToHost(12, one, two);
HostToHostIntent i2 = makeHostToHost(12, one, two);
assertThat(i1, is(equalTo(i2)));
}
/**
* Tests the equals() method where two HostToHostIntents have references
* to different Hosts. These should compare not equal.
*/
@Test
public void testLinksDifferentEquals() {
HostId one = hid("00:00:00:00:00:01/-1");
HostId two = hid("00:00:00:00:00:02/-1");
HostToHostIntent i1 = makeHostToHost(12, one, two);
HostToHostIntent i2 = makeHostToHost(12, two, one);
assertThat(i1, is(not(equalTo(i2))));
}
/**
* Tests the equals() method where two HostToHostIntents have different
* ids. These should compare not equal.
*/
@Test
public void testBaseDifferentEquals() {
HostId one = hid("00:00:00:00:00:01/-1");
HostId two = hid("00:00:00:00:00:02/-1");
HostToHostIntent i1 = makeHostToHost(12, one, two);
HostToHostIntent i2 = makeHostToHost(11, one, two);
assertThat(i1, is(not(equalTo(i2))));
}
/**
* Tests that the hashCode() values for two equivalent HostToHostIntent
* objects are the same.
*/
@Test
public void testHashCodeEquals() {
HostId one = hid("00:00:00:00:00:01/-1");
HostId two = hid("00:00:00:00:00:02/-1");
HostToHostIntent i1 = makeHostToHost(12, one, two);
HostToHostIntent i2 = makeHostToHost(12, one, two);
assertThat(i1.hashCode(), is(equalTo(i2.hashCode())));
}
/**
* Tests that the hashCode() values for two distinct LinkCollectionIntent
* objects are different.
*/
@Test
public void testHashCodeDifferent() {
HostId one = hid("00:00:00:00:00:01/-1");
HostId two = hid("00:00:00:00:00:02/-1");
HostToHostIntent i1 = makeHostToHost(12, one, two);
HostToHostIntent i2 = makeHostToHost(112, one, two);
assertThat(i1.hashCode(), is(not(equalTo(i2.hashCode()))));
}
/**
* Checks that the HostToHostIntent class is immutable.
*/
@Test
public void checkImmutability() {
ImmutableClassChecker.assertThatClassIsImmutable(HostToHostIntent.class);
}
}
package org.onlab.onos.net.intent;
import java.util.ArrayList;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.onlab.onos.net.NetTestTools.link;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.flow.criteria.Criterion;
import org.onlab.onos.net.flow.instructions.Instruction;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
/**
* Unit tests for the LinkCollectionIntent class.
*/
public class TestLinkCollectionIntent {
private static class MockSelector implements TrafficSelector {
@Override
public Set<Criterion> criteria() {
return new HashSet<Criterion>();
private Link link1 = link("dev1", 1, "dev2", 2);
private Link link2 = link("dev1", 1, "dev3", 2);
private Link link3 = link("dev2", 1, "dev3", 2);
private Set<Link> links1;
private Set<Link> links2;
private ConnectPoint egress1 = new ConnectPoint(DeviceId.deviceId("dev1"),
PortNumber.portNumber(3));
private ConnectPoint egress2 = new ConnectPoint(DeviceId.deviceId("dev2"),
PortNumber.portNumber(3));
private TrafficSelector selector = new IntentTestsMocks.MockSelector();
private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
private LinkCollectionIntent makeLinkCollection(long id, Set<Link> links,
ConnectPoint egress) {
return new LinkCollectionIntent(new IntentId(id),
selector, treatment, links, egress);
}
@Before
public void setup() {
links1 = new HashSet<>();
links2 = new HashSet<>();
}
/**
* Tests the equals() method where two LinkCollectionIntents have references
* to the same Links in different orders. These should compare equal.
*/
@Test
public void testSameEquals() {
links1.add(link1);
links1.add(link2);
links1.add(link3);
links2.add(link3);
links2.add(link2);
links2.add(link1);
LinkCollectionIntent i1 = makeLinkCollection(12, links1, egress1);
LinkCollectionIntent i2 = makeLinkCollection(12, links2, egress1);
assertThat(i1, is(equalTo(i2)));
}
/**
* Tests the equals() method where two LinkCollectionIntents have references
* to different Links. These should compare not equal.
*/
@Test
public void testLinksDifferentEquals() {
links1.add(link1);
links1.add(link2);
links2.add(link3);
links2.add(link1);
LinkCollectionIntent i1 = makeLinkCollection(12, links1, egress1);
LinkCollectionIntent i2 = makeLinkCollection(12, links2, egress1);
assertThat(i1, is(not(equalTo(i2))));
}
/**
* Tests the equals() method where two LinkCollectionIntents have references
* to the same Links but different egress points. These should compare not equal.
*/
@Test
public void testEgressDifferentEquals() {
links1.add(link1);
links1.add(link2);
links1.add(link3);
links2.add(link3);
links2.add(link2);
links2.add(link1);
LinkCollectionIntent i1 = makeLinkCollection(12, links1, egress1);
LinkCollectionIntent i2 = makeLinkCollection(12, links2, egress2);
assertThat(i1, is(not(equalTo(i2))));
}
private static class MockTreatment implements TrafficTreatment {
@Override
public List<Instruction> instructions() {
return new ArrayList<>();
/**
* Tests the equals() method where two LinkCollectionIntents have different
* ids. These should compare not equal.
*/
@Test
public void testBaseDifferentEquals() {
links1.add(link1);
links1.add(link2);
links2.add(link2);
links2.add(link1);
LinkCollectionIntent i1 = makeLinkCollection(1, links1, egress1);
LinkCollectionIntent i2 = makeLinkCollection(2, links2, egress1);
assertThat(i1, is(not(equalTo(i2))));
}
/**
* Tests that the hashCode() values for two equivalent LinkCollectionIntent
* objects are the same.
*/
@Test
public void testHashCodeEquals() {
links1.add(link1);
links1.add(link2);
links1.add(link3);
links2.add(link3);
links2.add(link2);
links2.add(link1);
LinkCollectionIntent i1 = makeLinkCollection(1, links1, egress1);
LinkCollectionIntent i2 = makeLinkCollection(1, links2, egress1);
assertThat(i1.hashCode(), is(equalTo(i2.hashCode())));
}
/**
* Tests that the hashCode() values for two distinct LinkCollectionIntent
* objects are different.
*/
@Test
public void testComparison() {
TrafficSelector selector = new MockSelector();
TrafficTreatment treatment = new MockTreatment();
Set<Link> links = new HashSet<>();
LinkCollectionIntent i1 = new LinkCollectionIntent(new IntentId(12),
selector, treatment, links);
LinkCollectionIntent i2 = new LinkCollectionIntent(new IntentId(12),
selector, treatment, links);
public void testHashCodeDifferent() {
links1.add(link1);
links1.add(link2);
links2.add(link1);
links2.add(link3);
LinkCollectionIntent i1 = makeLinkCollection(1, links1, egress1);
LinkCollectionIntent i2 = makeLinkCollection(1, links2, egress2);
assertThat(i1.equals(i2), is(true));
assertThat(i1.hashCode(), is(not(equalTo(i2.hashCode()))));
}
/**
* Checks that the HostToHostIntent class is immutable.
*/
@Test
public void checkImmutability() {
ImmutableClassChecker.assertThatClassIsImmutable(LinkCollectionIntent.class);
}
}
......
package org.onlab.onos.net.intent;
import java.util.HashSet;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.onlab.onos.net.NetTestTools.connectPoint;
/**
* Unit tests for the MultiPointToSinglePointIntent class.
*/
public class TestMultiPointToSinglePointIntent {
private ConnectPoint point1 = connectPoint("dev1", 1);
private ConnectPoint point2 = connectPoint("dev2", 1);
private ConnectPoint point3 = connectPoint("dev3", 1);
private TrafficSelector selector = new IntentTestsMocks.MockSelector();
private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
Set<ConnectPoint> ingress1;
Set<ConnectPoint> ingress2;
/**
* Creates a MultiPointToSinglePointIntent object.
*
* @param id identifier to use for the new intent
* @param ingress set of ingress points
* @param egress egress point
* @return MultiPointToSinglePoint intent
*/
private MultiPointToSinglePointIntent makeIntent(long id,
Set<ConnectPoint> ingress,
ConnectPoint egress) {
return new MultiPointToSinglePointIntent(new IntentId(id),
selector,
treatment,
ingress,
egress);
}
/**
* Initializes the ingress sets.
*/
@Before
public void setup() {
ingress1 = new HashSet<>();
ingress2 = new HashSet<>();
}
/**
* Tests the equals() method where two MultiPointToSinglePoint have references
* to the same Links in different orders. These should compare equal.
*/
@Test
public void testSameEquals() {
Set<ConnectPoint> ingress1 = new HashSet<>();
ingress1.add(point2);
ingress1.add(point3);
Set<ConnectPoint> ingress2 = new HashSet<>();
ingress2.add(point3);
ingress2.add(point2);
Intent i1 = makeIntent(12, ingress1, point1);
Intent i2 = makeIntent(12, ingress2, point1);
assertThat(i1, is(equalTo(i2)));
}
/**
* Tests the equals() method where two MultiPointToSinglePoint have references
* to different Links. These should compare not equal.
*/
@Test
public void testLinksDifferentEquals() {
ingress1.add(point3);
ingress2.add(point3);
ingress2.add(point2);
Intent i1 = makeIntent(12, ingress1, point1);
Intent i2 = makeIntent(12, ingress2, point1);
assertThat(i1, is(not(equalTo(i2))));
}
/**
* Tests the equals() method where two MultiPointToSinglePoint have different
* ids. These should compare not equal.
*/
@Test
public void testBaseDifferentEquals() {
ingress1.add(point3);
ingress2.add(point3);
Intent i1 = makeIntent(12, ingress1, point1);
Intent i2 = makeIntent(11, ingress2, point1);
assertThat(i1, is(not(equalTo(i2))));
}
/**
* Tests that the hashCode() values for two equivalent MultiPointToSinglePoint
* objects are the same.
*/
@Test
public void testHashCodeEquals() {
ingress1.add(point2);
ingress1.add(point3);
ingress2.add(point3);
ingress2.add(point2);
Intent i1 = makeIntent(12, ingress1, point1);
Intent i2 = makeIntent(12, ingress2, point1);
assertThat(i1.hashCode(), is(equalTo(i2.hashCode())));
}
/**
* Tests that the hashCode() values for two distinct MultiPointToSinglePoint
* objects are different.
*/
@Test
public void testHashCodeDifferent() {
ingress1.add(point2);
ingress2.add(point3);
ingress2.add(point2);
Intent i1 = makeIntent(12, ingress1, point1);
Intent i2 = makeIntent(12, ingress2, point1);
assertThat(i1.hashCode(), is(not(equalTo(i2.hashCode()))));
}
/**
* Checks that the MultiPointToSinglePointIntent class is immutable.
*/
@Test
public void checkImmutability() {
ImmutableClassChecker.
assertThatClassIsImmutable(MultiPointToSinglePointIntent.class);
}
}
package org.onlab.onos.net.intent;
import org.junit.Test;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.onlab.onos.net.NetTestTools.connectPoint;
/**
* Unit tests for the HostToHostIntent class.
*/
public class TestPointToPointIntent {
private TrafficSelector selector = new IntentTestsMocks.MockSelector();
private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
private ConnectPoint point1 = connectPoint("dev1", 1);
private ConnectPoint point2 = connectPoint("dev2", 1);
private PointToPointIntent makePointToPoint(long id,
ConnectPoint ingress,
ConnectPoint egress) {
return new PointToPointIntent(new IntentId(id),
selector,
treatment,
ingress,
egress);
}
/**
* Tests the equals() method where two PointToPointIntents have references
* to the same ingress and egress points. These should compare equal.
*/
@Test
public void testSameEquals() {
PointToPointIntent i1 = makePointToPoint(12, point1, point2);
PointToPointIntent i2 = makePointToPoint(12, point1, point2);
assertThat(i1, is(equalTo(i2)));
}
/**
* Tests the equals() method where two HostToHostIntents have references
* to different Hosts. These should compare not equal.
*/
@Test
public void testLinksDifferentEquals() {
PointToPointIntent i1 = makePointToPoint(12, point1, point2);
PointToPointIntent i2 = makePointToPoint(12, point2, point1);
assertThat(i1, is(not(equalTo(i2))));
}
/**
* Tests the equals() method where two HostToHostIntents have different
* ids. These should compare not equal.
*/
@Test
public void testBaseDifferentEquals() {
PointToPointIntent i1 = makePointToPoint(12, point1, point2);
PointToPointIntent i2 = makePointToPoint(11, point1, point2);
assertThat(i1, is(not(equalTo(i2))));
}
/**
* Tests that the hashCode() values for two equivalent HostToHostIntent
* objects are the same.
*/
@Test
public void testHashCodeEquals() {
PointToPointIntent i1 = makePointToPoint(12, point1, point2);
PointToPointIntent i2 = makePointToPoint(12, point1, point2);
assertThat(i1.hashCode(), is(equalTo(i2.hashCode())));
}
/**
* Tests that the hashCode() values for two distinct LinkCollectionIntent
* objects are different.
*/
@Test
public void testHashCodeDifferent() {
PointToPointIntent i1 = makePointToPoint(12, point1, point2);
PointToPointIntent i2 = makePointToPoint(22, point1, point2);
assertThat(i1.hashCode(), is(not(equalTo(i2.hashCode()))));
}
}
package org.onlab.onos.net.intent.impl;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.onlab.onos.net.Host;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.intent.HostToHostIntent;
import org.onlab.onos.net.intent.Intent;
import org.onlab.onos.net.intent.IntentId;
import org.onlab.onos.net.intent.IntentTestsMocks;
import org.onlab.onos.net.intent.PathIntent;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.onlab.onos.net.NetTestTools.hid;
import static org.onlab.onos.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath;
/**
* Unit tests for the HostToHost intent compiler.
*/
public class TestHostToHostIntentCompiler {
private static final String HOST_ONE_MAC = "00:00:00:00:00:01";
private static final String HOST_TWO_MAC = "00:00:00:00:00:02";
private static final String HOST_ONE_VLAN = "-1";
private static final String HOST_TWO_VLAN = "-1";
private static final String HOST_ONE = HOST_ONE_MAC + "/" + HOST_ONE_VLAN;
private static final String HOST_TWO = HOST_TWO_MAC + "/" + HOST_TWO_VLAN;
private TrafficSelector selector = new IntentTestsMocks.MockSelector();
private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
private HostId hostOneId = HostId.hostId(HOST_ONE);
private HostId hostTwoId = HostId.hostId(HOST_TWO);
private HostService mockHostService;
@Before
public void setup() {
Host hostOne = createMock(Host.class);
expect(hostOne.mac()).andReturn(new MacAddress(HOST_ONE_MAC.getBytes())).anyTimes();
expect(hostOne.vlan()).andReturn(VlanId.vlanId()).anyTimes();
replay(hostOne);
Host hostTwo = createMock(Host.class);
expect(hostTwo.mac()).andReturn(new MacAddress(HOST_TWO_MAC.getBytes())).anyTimes();
expect(hostTwo.vlan()).andReturn(VlanId.vlanId()).anyTimes();
replay(hostTwo);
mockHostService = createMock(HostService.class);
expect(mockHostService.getHost(eq(hostOneId))).andReturn(hostOne).anyTimes();
expect(mockHostService.getHost(eq(hostTwoId))).andReturn(hostTwo).anyTimes();
replay(mockHostService);
}
/**
* Creates a HostToHost intent based on two host Ids.
*
* @param oneIdString string for host one id
* @param twoIdString string for host two id
* @return HostToHostIntent for the two hosts
*/
private HostToHostIntent makeIntent(String oneIdString, String twoIdString) {
return new HostToHostIntent(new IntentId(12),
hid(oneIdString),
hid(twoIdString),
selector,
treatment);
}
/**
* Creates a compiler for HostToHost intents.
*
* @param hops string array describing the path hops to use when compiling
* @return HostToHost intent compiler
*/
private HostToHostIntentCompiler makeCompiler(String[] hops) {
HostToHostIntentCompiler compiler =
new HostToHostIntentCompiler();
compiler.pathService = new IntentTestsMocks.MockPathService(hops);
compiler.hostService = mockHostService;
IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator();
compiler.intentIdGenerator =
new IdBlockAllocatorBasedIntentIdGenerator(idBlockAllocator);
return compiler;
}
/**
* Tests a pair of hosts with 8 hops between them.
*/
@Test
public void testSingleLongPathCompilation() {
HostToHostIntent intent = makeIntent(HOST_ONE,
HOST_TWO);
assertThat(intent, is(notNullValue()));
String[] hops = {HOST_ONE, "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", HOST_TWO};
HostToHostIntentCompiler compiler = makeCompiler(hops);
assertThat(compiler, is(notNullValue()));
List<Intent> result = compiler.compile(intent);
assertThat(result, is(Matchers.notNullValue()));
assertThat(result, hasSize(2));
Intent forwardResultIntent = result.get(0);
assertThat(forwardResultIntent instanceof PathIntent, is(true));
Intent reverseResultIntent = result.get(1);
assertThat(reverseResultIntent instanceof PathIntent, is(true));
if (forwardResultIntent instanceof PathIntent) {
PathIntent forwardPathIntent = (PathIntent) forwardResultIntent;
assertThat(forwardPathIntent.path().links(), hasSize(9));
assertThat(forwardPathIntent.path().links(), linksHasPath(HOST_ONE, "h1"));
assertThat(forwardPathIntent.path().links(), linksHasPath("h1", "h2"));
assertThat(forwardPathIntent.path().links(), linksHasPath("h2", "h3"));
assertThat(forwardPathIntent.path().links(), linksHasPath("h3", "h4"));
assertThat(forwardPathIntent.path().links(), linksHasPath("h4", "h5"));
assertThat(forwardPathIntent.path().links(), linksHasPath("h5", "h6"));
assertThat(forwardPathIntent.path().links(), linksHasPath("h6", "h7"));
assertThat(forwardPathIntent.path().links(), linksHasPath("h7", "h8"));
assertThat(forwardPathIntent.path().links(), linksHasPath("h8", HOST_TWO));
}
if (reverseResultIntent instanceof PathIntent) {
PathIntent reversePathIntent = (PathIntent) reverseResultIntent;
assertThat(reversePathIntent.path().links(), hasSize(9));
assertThat(reversePathIntent.path().links(), linksHasPath("h1", HOST_ONE));
assertThat(reversePathIntent.path().links(), linksHasPath("h2", "h1"));
assertThat(reversePathIntent.path().links(), linksHasPath("h3", "h2"));
assertThat(reversePathIntent.path().links(), linksHasPath("h4", "h3"));
assertThat(reversePathIntent.path().links(), linksHasPath("h5", "h4"));
assertThat(reversePathIntent.path().links(), linksHasPath("h6", "h5"));
assertThat(reversePathIntent.path().links(), linksHasPath("h7", "h6"));
assertThat(reversePathIntent.path().links(), linksHasPath("h8", "h7"));
assertThat(reversePathIntent.path().links(), linksHasPath(HOST_TWO, "h8"));
}
}
}
package org.onlab.onos.net.intent.impl;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.ElementId;
import org.onlab.onos.net.Path;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.intent.Intent;
import org.onlab.onos.net.intent.IntentId;
import org.onlab.onos.net.intent.IntentTestsMocks;
import org.onlab.onos.net.intent.LinkCollectionIntent;
import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
import org.onlab.onos.net.topology.LinkWeight;
import org.onlab.onos.net.topology.PathService;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.onlab.onos.net.NetTestTools.connectPoint;
import static org.onlab.onos.net.NetTestTools.createPath;
import static org.onlab.onos.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath;
/**
* Unit tests for the MultiPointToSinglePoint intent compiler.
*/
public class TestMultiPointToSinglePointIntentCompiler {
private TrafficSelector selector = new IntentTestsMocks.MockSelector();
private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
/**
* Mock path service for creating paths within the test.
*/
private static class MockPathService implements PathService {
final String[] pathHops;
/**
* Constructor that provides a set of hops to mock.
*
* @param pathHops path hops to mock
*/
MockPathService(String[] pathHops) {
this.pathHops = pathHops;
}
@Override
public Set<Path> getPaths(ElementId src, ElementId dst) {
Set<Path> result = new HashSet<>();
String[] allHops = new String[pathHops.length + 1];
allHops[0] = src.toString();
System.arraycopy(pathHops, 0, allHops, 1, pathHops.length);
result.add(createPath(allHops));
return result;
}
@Override
public Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight) {
return null;
}
}
/**
* Creates a MultiPointToSinglePoint intent for a group of ingress points
* and an egress point.
*
* @param ingressIds array of ingress device ids
* @param egressId device id of the egress point
* @return MultiPointToSinglePoint intent
*/
private MultiPointToSinglePointIntent makeIntent(String[] ingressIds, String egressId) {
Set<ConnectPoint> ingressPoints = new HashSet<>();
ConnectPoint egressPoint = connectPoint(egressId, 1);
for (String ingressId : ingressIds) {
ingressPoints.add(connectPoint(ingressId, 1));
}
return new MultiPointToSinglePointIntent(
new IntentId(12),
selector,
treatment,
ingressPoints,
egressPoint);
}
/**
* Creates a compiler for MultiPointToSinglePoint intents.
*
* @param hops hops to use while computing paths for this intent
* @return MultiPointToSinglePoint intent
*/
private MultiPointToSinglePointIntentCompiler makeCompiler(String[] hops) {
MultiPointToSinglePointIntentCompiler compiler =
new MultiPointToSinglePointIntentCompiler();
compiler.pathService = new MockPathService(hops);
IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator();
compiler.intentIdGenerator =
new IdBlockAllocatorBasedIntentIdGenerator(idBlockAllocator);
return compiler;
}
/**
* Tests a single ingress point with 8 hops to its egress point.
*/
@Test
public void testSingleLongPathCompilation() {
String[] ingress = {"ingress"};
String egress = "egress";
MultiPointToSinglePointIntent intent = makeIntent(ingress, egress);
assertThat(intent, is(notNullValue()));
String[] hops = {"h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8",
egress};
MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
assertThat(compiler, is(notNullValue()));
List<Intent> result = compiler.compile(intent);
assertThat(result, is(Matchers.notNullValue()));
assertThat(result, hasSize(1));
Intent resultIntent = result.get(0);
assertThat(resultIntent instanceof LinkCollectionIntent, is(true));
if (resultIntent instanceof LinkCollectionIntent) {
LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent;
assertThat(linkIntent.links(), hasSize(9));
assertThat(linkIntent.links(), linksHasPath("ingress", "h1"));
assertThat(linkIntent.links(), linksHasPath("h1", "h2"));
assertThat(linkIntent.links(), linksHasPath("h2", "h3"));
assertThat(linkIntent.links(), linksHasPath("h4", "h5"));
assertThat(linkIntent.links(), linksHasPath("h5", "h6"));
assertThat(linkIntent.links(), linksHasPath("h7", "h8"));
assertThat(linkIntent.links(), linksHasPath("h8", "egress"));
}
}
/**
* Tests a simple topology where two ingress points share some path segments
* and some path segments are not shared.
*/
@Test
public void testTwoIngressCompilation() {
String[] ingress = {"ingress1", "ingress2"};
String egress = "egress";
MultiPointToSinglePointIntent intent = makeIntent(ingress, egress);
assertThat(intent, is(notNullValue()));
final String[] hops = {"inner1", "inner2", egress};
MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
assertThat(compiler, is(notNullValue()));
List<Intent> result = compiler.compile(intent);
assertThat(result, is(notNullValue()));
assertThat(result, hasSize(1));
Intent resultIntent = result.get(0);
assertThat(resultIntent instanceof LinkCollectionIntent, is(true));
if (resultIntent instanceof LinkCollectionIntent) {
LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent;
assertThat(linkIntent.links(), hasSize(4));
assertThat(linkIntent.links(), linksHasPath("ingress1", "inner1"));
assertThat(linkIntent.links(), linksHasPath("ingress2", "inner1"));
assertThat(linkIntent.links(), linksHasPath("inner1", "inner2"));
assertThat(linkIntent.links(), linksHasPath("inner2", "egress"));
}
}
/**
* Tests a large number of ingress points that share a common path to the
* egress point.
*/
@Test
public void testMultiIngressCompilation() {
String[] ingress = {"i1", "i2", "i3", "i4", "i5",
"i6", "i7", "i8", "i9", "i10"};
String egress = "e";
MultiPointToSinglePointIntent intent = makeIntent(ingress, egress);
assertThat(intent, is(notNullValue()));
final String[] hops = {"n1", egress};
MultiPointToSinglePointIntentCompiler compiler = makeCompiler(hops);
assertThat(compiler, is(notNullValue()));
List<Intent> result = compiler.compile(intent);
assertThat(result, is(notNullValue()));
assertThat(result, hasSize(1));
Intent resultIntent = result.get(0);
assertThat(resultIntent instanceof LinkCollectionIntent, is(true));
if (resultIntent instanceof LinkCollectionIntent) {
LinkCollectionIntent linkIntent = (LinkCollectionIntent) resultIntent;
assertThat(linkIntent.links(), hasSize(ingress.length + 1));
for (String ingressToCheck : ingress) {
assertThat(linkIntent.links(),
linksHasPath(ingressToCheck,
"n1"));
}
assertThat(linkIntent.links(), linksHasPath("n1", egress));
}
}
}
package org.onlab.onos.net.intent.impl;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.intent.Intent;
import org.onlab.onos.net.intent.IntentId;
import org.onlab.onos.net.intent.IntentTestsMocks;
import org.onlab.onos.net.intent.PathIntent;
import org.onlab.onos.net.intent.PointToPointIntent;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.onlab.onos.net.NetTestTools.connectPoint;
import static org.onlab.onos.net.intent.LinksHaveEntryWithSourceDestinationPairMatcher.linksHasPath;
/**
* Unit tests for the HostToHost intent compiler.
*/
public class TestPointToPointIntentCompiler {
private TrafficSelector selector = new IntentTestsMocks.MockSelector();
private TrafficTreatment treatment = new IntentTestsMocks.MockTreatment();
/**
* Creates a PointToPoint intent based on ingress and egress device Ids.
*
* @param ingressIdString string for id of ingress device
* @param egressIdString string for id of egress device
* @return PointToPointIntent for the two devices
*/
private PointToPointIntent makeIntent(String ingressIdString,
String egressIdString) {
return new PointToPointIntent(new IntentId(12),
selector,
treatment,
connectPoint(ingressIdString, 1),
connectPoint(egressIdString, 1));
}
/**
* Creates a compiler for HostToHost intents.
*
* @param hops string array describing the path hops to use when compiling
* @return HostToHost intent compiler
*/
private PointToPointIntentCompiler makeCompiler(String[] hops) {
PointToPointIntentCompiler compiler =
new PointToPointIntentCompiler();
compiler.pathService = new IntentTestsMocks.MockPathService(hops);
IdBlockAllocator idBlockAllocator = new DummyIdBlockAllocator();
compiler.intentIdGenerator =
new IdBlockAllocatorBasedIntentIdGenerator(idBlockAllocator);
return compiler;
}
/**
* Tests a pair of devices in an 8 hop path, forward direction.
*/
@Test
public void testForwardPathCompilation() {
PointToPointIntent intent = makeIntent("d1", "d8");
assertThat(intent, is(notNullValue()));
String[] hops = {"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"};
PointToPointIntentCompiler compiler = makeCompiler(hops);
assertThat(compiler, is(notNullValue()));
List<Intent> result = compiler.compile(intent);
assertThat(result, is(Matchers.notNullValue()));
assertThat(result, hasSize(1));
Intent forwardResultIntent = result.get(0);
assertThat(forwardResultIntent instanceof PathIntent, is(true));
if (forwardResultIntent instanceof PathIntent) {
PathIntent forwardPathIntent = (PathIntent) forwardResultIntent;
// 7 links for the hops, plus one default lnk on ingress and egress
assertThat(forwardPathIntent.path().links(), hasSize(hops.length + 1));
assertThat(forwardPathIntent.path().links(), linksHasPath("d1", "d2"));
assertThat(forwardPathIntent.path().links(), linksHasPath("d2", "d3"));
assertThat(forwardPathIntent.path().links(), linksHasPath("d3", "d4"));
assertThat(forwardPathIntent.path().links(), linksHasPath("d4", "d5"));
assertThat(forwardPathIntent.path().links(), linksHasPath("d5", "d6"));
assertThat(forwardPathIntent.path().links(), linksHasPath("d6", "d7"));
assertThat(forwardPathIntent.path().links(), linksHasPath("d7", "d8"));
}
}
/**
* Tests a pair of devices in an 8 hop path, forward direction.
*/
@Test
public void testReversePathCompilation() {
PointToPointIntent intent = makeIntent("d8", "d1");
assertThat(intent, is(notNullValue()));
String[] hops = {"d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8"};
PointToPointIntentCompiler compiler = makeCompiler(hops);
assertThat(compiler, is(notNullValue()));
List<Intent> result = compiler.compile(intent);
assertThat(result, is(Matchers.notNullValue()));
assertThat(result, hasSize(1));
Intent reverseResultIntent = result.get(0);
assertThat(reverseResultIntent instanceof PathIntent, is(true));
if (reverseResultIntent instanceof PathIntent) {
PathIntent reversePathIntent = (PathIntent) reverseResultIntent;
assertThat(reversePathIntent.path().links(), hasSize(hops.length + 1));
assertThat(reversePathIntent.path().links(), linksHasPath("d2", "d1"));
assertThat(reversePathIntent.path().links(), linksHasPath("d3", "d2"));
assertThat(reversePathIntent.path().links(), linksHasPath("d4", "d3"));
assertThat(reversePathIntent.path().links(), linksHasPath("d5", "d4"));
assertThat(reversePathIntent.path().links(), linksHasPath("d6", "d5"));
assertThat(reversePathIntent.path().links(), linksHasPath("d7", "d6"));
assertThat(reversePathIntent.path().links(), linksHasPath("d8", "d7"));
}
}
}
......@@ -59,6 +59,7 @@ public class LinkManagerTest {
protected LinkProviderService providerService;
protected TestProvider provider;
protected TestListener listener = new TestListener();
protected DeviceManager devmgr = new TestDeviceManager();
@Before
public void setUp() {
......@@ -68,7 +69,7 @@ public class LinkManagerTest {
registry = mgr;
mgr.store = new SimpleLinkStore();
mgr.eventDispatcher = new TestEventDispatcher();
mgr.deviceService = new DeviceManager();
mgr.deviceService = devmgr;
mgr.activate();
service.addListener(listener);
......@@ -259,4 +260,11 @@ public class LinkManagerTest {
}
}
private static class TestDeviceManager extends DeviceManager {
@Override
public MastershipRole getRole(DeviceId deviceId) {
return MastershipRole.MASTER;
}
}
}
......
......@@ -20,6 +20,7 @@
<module>api</module>
<module>net</module>
<module>store</module>
<module>json</module>
</modules>
<dependencies>
......
......@@ -19,21 +19,10 @@
<dependencies>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-api</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-core-serializers</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-nio</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-netty</artifactId>
......@@ -50,10 +39,6 @@
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<scope>test</scope>
......@@ -67,15 +52,12 @@
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-api</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
......
......@@ -53,7 +53,7 @@ public class DistributedClusterStore
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private ClusterCommunicationAdminService clusterCommunicationAdminService;
private final ClusterNodesDelegate nodesDelegate = new InnerNodesDelegate();
private final ClusterNodesDelegate nodesDelegate = new InternalNodesDelegate();
@Activate
public void activate() throws IOException {
......@@ -150,7 +150,7 @@ public class DistributedClusterStore
}
// Entity to handle back calls from the connection manager.
private class InnerNodesDelegate implements ClusterNodesDelegate {
private class InternalNodesDelegate implements ClusterNodesDelegate {
@Override
public DefaultControllerNode nodeDetected(NodeId nodeId, IpPrefix ip, int tcpPort) {
DefaultControllerNode node = nodes.get(nodeId);
......
......@@ -4,6 +4,9 @@ import static com.google.common.base.Preconditions.checkArgument;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
......@@ -17,17 +20,19 @@ import org.onlab.onos.store.cluster.impl.ClusterMembershipEvent;
import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
import org.onlab.onos.store.cluster.messaging.ClusterMessage;
import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
import org.onlab.onos.store.cluster.messaging.ClusterMessageResponse;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
import org.onlab.onos.store.serializers.ClusterMessageSerializer;
import org.onlab.onos.store.serializers.KryoPoolUtil;
import org.onlab.onos.store.serializers.KryoNamespaces;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.onos.store.serializers.MessageSubjectSerializer;
import org.onlab.util.KryoPool;
import org.onlab.util.KryoNamespace;
import org.onlab.netty.Endpoint;
import org.onlab.netty.Message;
import org.onlab.netty.MessageHandler;
import org.onlab.netty.MessagingService;
import org.onlab.netty.NettyMessagingService;
import org.onlab.netty.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -47,8 +52,8 @@ public class ClusterCommunicationManager
private static final KryoSerializer SERIALIZER = new KryoSerializer() {
@Override
protected void setupKryoPool() {
serializerPool = KryoPool.newBuilder()
.register(KryoPoolUtil.API)
serializerPool = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
.register(ClusterMessage.class, new ClusterMessageSerializer())
.register(ClusterMembershipEvent.class)
.register(byte[].class)
......@@ -114,7 +119,23 @@ public class ClusterCommunicationManager
message.subject().value(), SERIALIZER.encode(message));
return true;
} catch (IOException e) {
log.error("Failed to send cluster message to nodeId: " + toNodeId, e);
log.trace("Failed to send cluster message to nodeId: " + toNodeId, e);
throw e;
}
}
@Override
public ClusterMessageResponse sendAndReceive(ClusterMessage message, NodeId toNodeId) throws IOException {
ControllerNode node = clusterService.getNode(toNodeId);
checkArgument(node != null, "Unknown nodeId: %s", toNodeId);
Endpoint nodeEp = new Endpoint(node.ip().toString(), node.tcpPort());
try {
Response responseFuture =
messagingService.sendAndReceive(nodeEp, message.subject().value(), SERIALIZER.encode(message));
return new InternalClusterMessageResponse(toNodeId, responseFuture);
} catch (IOException e) {
log.error("Failed interaction with remote nodeId: " + toNodeId, e);
throw e;
}
}
......@@ -137,11 +158,52 @@ public class ClusterCommunicationManager
public void handle(Message message) {
try {
ClusterMessage clusterMessage = SERIALIZER.decode(message.payload());
handler.handle(clusterMessage);
handler.handle(new InternalClusterMessage(clusterMessage, message));
} catch (Exception e) {
log.error("Exception caught during ClusterMessageHandler", e);
throw e;
}
}
}
public static final class InternalClusterMessage extends ClusterMessage {
private final Message rawMessage;
public InternalClusterMessage(ClusterMessage clusterMessage, Message rawMessage) {
super(clusterMessage.sender(), clusterMessage.subject(), clusterMessage.payload());
this.rawMessage = rawMessage;
}
@Override
public void respond(byte[] response) throws IOException {
rawMessage.respond(response);
}
}
private static final class InternalClusterMessageResponse implements ClusterMessageResponse {
private final NodeId sender;
private final Response responseFuture;
public InternalClusterMessageResponse(NodeId sender, Response responseFuture) {
this.sender = sender;
this.responseFuture = responseFuture;
}
@Override
public NodeId sender() {
return sender;
}
@Override
public byte[] get(long timeout, TimeUnit timeunit)
throws TimeoutException {
return responseFuture.get(timeout, timeunit);
}
@Override
public byte[] get(long timeout) throws InterruptedException {
return responseFuture.get();
}
}
}
......
/**
* Common abstractions and facilities for implementing distributed store
* using gossip protocol.
*/
package org.onlab.onos.store.common.impl;
......@@ -44,6 +44,8 @@ public class DeviceClockManager implements DeviceClockService, DeviceClockProvid
@Override
public Timestamp getTimestamp(DeviceId deviceId) {
MastershipTerm term = deviceMastershipTerms.get(deviceId);
log.trace("term info for {} is: {}", deviceId, term);
if (term == null) {
throw new IllegalStateException("Requesting timestamp for a deviceId without mastership");
}
......@@ -52,6 +54,7 @@ public class DeviceClockManager implements DeviceClockService, DeviceClockProvid
@Override
public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
log.info("adding term info {} {}", deviceId, term.master());
deviceMastershipTerms.put(deviceId, term);
}
}
......
......@@ -15,7 +15,7 @@ import org.onlab.onos.net.device.DefaultPortDescription;
import org.onlab.onos.net.device.DeviceDescription;
import org.onlab.onos.net.device.PortDescription;
import org.onlab.onos.store.Timestamp;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
/*
* Collection of Description of a Device and Ports, given from a Provider.
......
......@@ -38,10 +38,11 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
import org.onlab.onos.store.cluster.messaging.ClusterMessage;
import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.onos.store.serializers.DistributedStoreSerializers;
import org.onlab.util.KryoPool;
import org.onlab.packet.ChassisId;
import org.onlab.util.KryoNamespace;
import org.onlab.util.NewConcurrentHashMap;
import org.slf4j.Logger;
......@@ -116,7 +117,7 @@ public class GossipDeviceStore
protected static final KryoSerializer SERIALIZER = new KryoSerializer() {
@Override
protected void setupKryoPool() {
serializerPool = KryoPool.newBuilder()
serializerPool = KryoNamespace.newBuilder()
.register(DistributedStoreSerializers.COMMON)
.register(InternalDeviceEvent.class, new InternalDeviceEventSerializer())
......@@ -390,6 +391,7 @@ public class GossipDeviceStore
List<PortDescription> portDescriptions) {
final Timestamp newTimestamp = deviceClockService.getTimestamp(deviceId);
log.info("timestamp for {} {}", deviceId, newTimestamp);
final Timestamped<List<PortDescription>> timestampedInput
= new Timestamped<>(portDescriptions, newTimestamp);
......@@ -515,12 +517,12 @@ public class GossipDeviceStore
Map<PortNumber, Port> ports,
Set<PortNumber> processed) {
List<DeviceEvent> events = new ArrayList<>();
Iterator<PortNumber> iterator = ports.keySet().iterator();
Iterator<Entry<PortNumber, Port>> iterator = ports.entrySet().iterator();
while (iterator.hasNext()) {
PortNumber portNumber = iterator.next();
Entry<PortNumber, Port> e = iterator.next();
PortNumber portNumber = e.getKey();
if (!processed.contains(portNumber)) {
events.add(new DeviceEvent(PORT_REMOVED, device,
ports.get(portNumber)));
events.add(new DeviceEvent(PORT_REMOVED, device, e.getValue()));
iterator.remove();
}
}
......@@ -745,6 +747,7 @@ public class GossipDeviceStore
String hwVersion = base.hwVersion();
String swVersion = base.swVersion();
String serialNumber = base.serialNumber();
ChassisId chassisId = base.chassisId();
DefaultAnnotations annotations = DefaultAnnotations.builder().build();
annotations = merge(annotations, base.annotations());
......@@ -762,7 +765,8 @@ public class GossipDeviceStore
}
return new DefaultDevice(primary, deviceId , type, manufacturer,
hwVersion, swVersion, serialNumber, annotations);
hwVersion, swVersion, serialNumber,
chassisId, annotations);
}
/**
......@@ -1136,7 +1140,7 @@ public class GossipDeviceStore
try {
unicastMessage(peer, DEVICE_ADVERTISE, ad);
} catch (IOException e) {
log.error("Failed to send anti-entropy advertisement", e);
log.debug("Failed to send anti-entropy advertisement to {}", peer);
return;
}
} catch (Exception e) {
......
package org.onlab.onos.store.device.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import org.apache.commons.lang3.concurrent.ConcurrentException;
import org.apache.commons.lang3.concurrent.ConcurrentInitializer;
import org.onlab.onos.net.device.DeviceDescription;
import org.onlab.onos.store.common.impl.Timestamped;
// FIXME: consider removing this class
public final class InitDeviceDescs
implements ConcurrentInitializer<DeviceDescriptions> {
private final Timestamped<DeviceDescription> deviceDesc;
public InitDeviceDescs(Timestamped<DeviceDescription> deviceDesc) {
this.deviceDesc = checkNotNull(deviceDesc);
}
@Override
public DeviceDescriptions get() throws ConcurrentException {
return new DeviceDescriptions(deviceDesc);
}
}
......@@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.device.DeviceDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
import com.google.common.base.MoreObjects;
......
......@@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.device.DeviceDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
......
......@@ -5,7 +5,7 @@ import java.util.List;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.device.PortDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
import com.google.common.base.MoreObjects;
......
......@@ -5,7 +5,7 @@ import java.util.List;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.device.PortDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
......
......@@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.device.PortDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
import com.google.common.base.MoreObjects;
......
......@@ -3,7 +3,7 @@ package org.onlab.onos.store.device.impl;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.device.PortDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
......
/**
* Implementation of device store using distributed distributed p2p synchronization protocol.
* Implementation of distributed device store using p2p synchronization protocol.
*/
package org.onlab.onos.store.device.impl;
......
package org.onlab.onos.store.flow;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Collection;
import java.util.Collections;
import org.onlab.onos.cluster.NodeId;
import com.google.common.base.Optional;
/**
* Class to represent placement information about Master/Backup copy.
*/
public final class ReplicaInfo {
private final Optional<NodeId> master;
private final Collection<NodeId> backups;
/**
* Creates a ReplicaInfo instance.
*
* @param master NodeId of the node where the master copy should be
* @param backups collection of NodeId, where backup copies should be placed
*/
public ReplicaInfo(NodeId master, Collection<NodeId> backups) {
this.master = Optional.fromNullable(master);
this.backups = checkNotNull(backups);
}
/**
* Returns the NodeId, if there is a Node where the master copy should be.
*
* @return NodeId, where the master copy should be placed
*/
public Optional<NodeId> master() {
return master;
}
/**
* Returns the collection of NodeId, where backup copies should be placed.
*
* @return collection of NodeId, where backup copies should be placed
*/
public Collection<NodeId> backups() {
return backups;
}
// for Serializer
private ReplicaInfo() {
this.master = Optional.absent();
this.backups = Collections.emptyList();
}
}
package org.onlab.onos.store.flow;
import static com.google.common.base.Preconditions.checkNotNull;
import org.onlab.onos.event.AbstractEvent;
import org.onlab.onos.net.DeviceId;
/**
* Describes a device replicainfo event.
*/
public class ReplicaInfoEvent extends AbstractEvent<ReplicaInfoEvent.Type, DeviceId> {
private final ReplicaInfo replicaInfo;
/**
* Types of Replica info event.
*/
public enum Type {
/**
* Event to notify that master placement should be changed.
*/
MASTER_CHANGED,
//
// BACKUPS_CHANGED?
}
/**
* Creates an event of a given type and for the specified device,
* and replica info.
*
* @param type replicainfo event type
* @param device event device subject
* @param replicaInfo replicainfo
*/
public ReplicaInfoEvent(Type type, DeviceId device, ReplicaInfo replicaInfo) {
super(type, device);
this.replicaInfo = checkNotNull(replicaInfo);
}
/**
* Returns the current replica information for the subject.
*
* @return replica information for the subject
*/
public ReplicaInfo replicaInfo() {
return replicaInfo;
};
}
package org.onlab.onos.store.flow;
import org.onlab.onos.event.EventListener;
/**
* Entity capable of receiving Replica placement information-related events.
*/
public interface ReplicaInfoEventListener extends EventListener<ReplicaInfoEvent> {
}
package org.onlab.onos.store.flow;
import org.onlab.onos.net.DeviceId;
/**
* Service to return where the replica should be placed.
*/
public interface ReplicaInfoService {
// returns where it should be.
/**
* Returns the placement information for given Device.
*
* @param deviceId identifier of the device
* @return placement information
*/
ReplicaInfo getReplicaInfoFor(DeviceId deviceId);
/**
* Adds the specified replica placement info change listener.
*
* @param listener the replica placement info change listener
*/
void addListener(ReplicaInfoEventListener listener);
/**
* Removes the specified replica placement info change listener.
*
* @param listener the replica placement info change listener
*/
void removeListener(ReplicaInfoEventListener listener);
}
......@@ -3,14 +3,20 @@ package org.onlab.onos.store.flow.impl;
import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
import static org.slf4j.LoggerFactory.getLogger;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.ApplicationId;
import org.onlab.onos.cluster.ClusterService;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.flow.DefaultFlowEntry;
import org.onlab.onos.net.flow.FlowEntry;
......@@ -20,7 +26,16 @@ import org.onlab.onos.net.flow.FlowRuleEvent;
import org.onlab.onos.net.flow.FlowRuleEvent.Type;
import org.onlab.onos.net.flow.FlowRuleStore;
import org.onlab.onos.net.flow.FlowRuleStoreDelegate;
import org.onlab.onos.net.flow.StoredFlowEntry;
import org.onlab.onos.store.AbstractStore;
import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
import org.onlab.onos.store.cluster.messaging.ClusterMessage;
import org.onlab.onos.store.cluster.messaging.ClusterMessageResponse;
import org.onlab.onos.store.flow.ReplicaInfo;
import org.onlab.onos.store.flow.ReplicaInfoService;
import org.onlab.onos.store.serializers.DistributedStoreSerializers;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.util.KryoNamespace;
import org.slf4j.Logger;
import com.google.common.collect.ArrayListMultimap;
......@@ -28,9 +43,8 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
/**
* Manages inventory of flow rules using trivial in-memory implementation.
* Manages inventory of flow rules using a distributed state management protocol.
*/
//FIXME I LIE. I AIN'T DISTRIBUTED
@Component(immediate = true)
@Service
public class DistributedFlowRuleStore
......@@ -40,12 +54,34 @@ public class DistributedFlowRuleStore
private final Logger log = getLogger(getClass());
// store entries as a pile of rules, no info about device tables
private final Multimap<DeviceId, FlowEntry> flowEntries =
ArrayListMultimap.<DeviceId, FlowEntry>create();
private final Multimap<DeviceId, StoredFlowEntry> flowEntries =
ArrayListMultimap.<DeviceId, StoredFlowEntry>create();
private final Multimap<Short, FlowRule> flowEntriesById =
ArrayListMultimap.<Short, FlowRule>create();
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private ReplicaInfoService replicaInfoManager;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private ClusterCommunicationService clusterCommunicator;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
private ClusterService clusterService;
protected static final KryoSerializer SERIALIZER = new KryoSerializer() {
@Override
protected void setupKryoPool() {
serializerPool = KryoNamespace.newBuilder()
.register(DistributedStoreSerializers.COMMON)
.build()
.populate(1);
}
};
// TODO: make this configurable
private static final long FLOW_RULE_STORE_TIMEOUT_MILLIS = 1000;
@Activate
public void activate() {
log.info("Started");
......@@ -64,7 +100,11 @@ public class DistributedFlowRuleStore
@Override
public synchronized FlowEntry getFlowEntry(FlowRule rule) {
for (FlowEntry f : flowEntries.get(rule.deviceId())) {
return getFlowEntryInternal(rule);
}
private synchronized StoredFlowEntry getFlowEntryInternal(FlowRule rule) {
for (StoredFlowEntry f : flowEntries.get(rule.deviceId())) {
if (f.equals(rule)) {
return f;
}
......@@ -74,7 +114,7 @@ public class DistributedFlowRuleStore
@Override
public synchronized Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
Collection<FlowEntry> rules = flowEntries.get(deviceId);
Collection<? extends FlowEntry> rules = flowEntries.get(deviceId);
if (rules == null) {
return Collections.emptyList();
}
......@@ -91,30 +131,96 @@ public class DistributedFlowRuleStore
}
@Override
public synchronized void storeFlowRule(FlowRule rule) {
FlowEntry f = new DefaultFlowEntry(rule);
DeviceId did = f.deviceId();
if (!flowEntries.containsEntry(did, f)) {
flowEntries.put(did, f);
flowEntriesById.put(rule.appId(), f);
public void storeFlowRule(FlowRule rule) {
ReplicaInfo replicaInfo = replicaInfoManager.getReplicaInfoFor(rule.deviceId());
if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
storeFlowEntryInternal(rule);
return;
}
ClusterMessage message = new ClusterMessage(
clusterService.getLocalNode().id(),
FlowStoreMessageSubjects.STORE_FLOW_RULE,
SERIALIZER.encode(rule));
try {
ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (IOException | TimeoutException e) {
// FIXME: throw a FlowStoreException
throw new RuntimeException(e);
}
}
private synchronized void storeFlowEntryInternal(FlowRule flowRule) {
StoredFlowEntry flowEntry = new DefaultFlowEntry(flowRule);
DeviceId deviceId = flowRule.deviceId();
// write to local copy.
if (!flowEntries.containsEntry(deviceId, flowEntry)) {
flowEntries.put(deviceId, flowEntry);
flowEntriesById.put(flowRule.appId(), flowEntry);
}
// write to backup.
// TODO: write to a hazelcast map.
}
@Override
public synchronized void deleteFlowRule(FlowRule rule) {
FlowEntry entry = getFlowEntry(rule);
ReplicaInfo replicaInfo = replicaInfoManager.getReplicaInfoFor(rule.deviceId());
if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
deleteFlowRuleInternal(rule);
return;
}
ClusterMessage message = new ClusterMessage(
clusterService.getLocalNode().id(),
FlowStoreMessageSubjects.DELETE_FLOW_RULE,
SERIALIZER.encode(rule));
try {
ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (IOException | TimeoutException e) {
// FIXME: throw a FlowStoreException
throw new RuntimeException(e);
}
}
private synchronized void deleteFlowRuleInternal(FlowRule flowRule) {
StoredFlowEntry entry = getFlowEntryInternal(flowRule);
if (entry == null) {
return;
}
entry.setState(FlowEntryState.PENDING_REMOVE);
// TODO: also update backup.
}
@Override
public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
public FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
ReplicaInfo replicaInfo = replicaInfoManager.getReplicaInfoFor(rule.deviceId());
if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
return addOrUpdateFlowRuleInternal(rule);
}
ClusterMessage message = new ClusterMessage(
clusterService.getLocalNode().id(),
FlowStoreMessageSubjects.ADD_OR_UPDATE_FLOW_RULE,
SERIALIZER.encode(rule));
try {
ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
return SERIALIZER.decode(response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
} catch (IOException | TimeoutException e) {
// FIXME: throw a FlowStoreException
throw new RuntimeException(e);
}
}
private synchronized FlowRuleEvent addOrUpdateFlowRuleInternal(FlowEntry rule) {
DeviceId did = rule.deviceId();
// check if this new rule is an update to an existing entry
FlowEntry stored = getFlowEntry(rule);
StoredFlowEntry stored = getFlowEntryInternal(rule);
if (stored != null) {
stored.setBytes(rule.bytes());
stored.setLife(rule.life());
......@@ -126,17 +232,42 @@ public class DistributedFlowRuleStore
return new FlowRuleEvent(Type.RULE_UPDATED, rule);
}
flowEntries.put(did, rule);
// TODO: Confirm if this behavior is correct. See SimpleFlowRuleStore
flowEntries.put(did, new DefaultFlowEntry(rule));
return null;
// TODO: also update backup.
}
@Override
public synchronized FlowRuleEvent removeFlowRule(FlowEntry rule) {
public FlowRuleEvent removeFlowRule(FlowEntry rule) {
ReplicaInfo replicaInfo = replicaInfoManager.getReplicaInfoFor(rule.deviceId());
if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
// bypass and handle it locally
return removeFlowRuleInternal(rule);
}
ClusterMessage message = new ClusterMessage(
clusterService.getLocalNode().id(),
FlowStoreMessageSubjects.REMOVE_FLOW_RULE,
SERIALIZER.encode(rule));
try {
ClusterMessageResponse response = clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
return SERIALIZER.decode(response.get(FLOW_RULE_STORE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
} catch (IOException | TimeoutException e) {
// FIXME: throw a FlowStoreException
throw new RuntimeException(e);
}
}
private synchronized FlowRuleEvent removeFlowRuleInternal(FlowEntry rule) {
// This is where one could mark a rule as removed and still keep it in the store.
if (flowEntries.remove(rule.deviceId(), rule)) {
return new FlowRuleEvent(RULE_REMOVED, rule);
} else {
return null;
}
// TODO: also update backup.
}
}
......
package org.onlab.onos.store.flow.impl;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
/**
* MessageSubjects used by DistributedFlowRuleStore peer-peer communication.
*/
public final class FlowStoreMessageSubjects {
private FlowStoreMessageSubjects() {}
public static final MessageSubject STORE_FLOW_RULE = new MessageSubject("peer-forward-store-flow-rule");
public static final MessageSubject DELETE_FLOW_RULE = new MessageSubject("peer-forward-delete-flow-rule");
public static final MessageSubject ADD_OR_UPDATE_FLOW_RULE =
new MessageSubject("peer-forward-add-or-update-flow-rule");
public static final MessageSubject REMOVE_FLOW_RULE = new MessageSubject("peer-forward-remove-flow-rule");
}
package org.onlab.onos.store.flow.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
import static org.onlab.onos.store.flow.ReplicaInfoEvent.Type.MASTER_CHANGED;
import java.util.Collections;
import java.util.List;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.event.AbstractListenerRegistry;
import org.onlab.onos.event.EventDeliveryService;
import org.onlab.onos.mastership.MastershipEvent;
import org.onlab.onos.mastership.MastershipListener;
import org.onlab.onos.mastership.MastershipService;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.store.flow.ReplicaInfo;
import org.onlab.onos.store.flow.ReplicaInfoEvent;
import org.onlab.onos.store.flow.ReplicaInfoEventListener;
import org.onlab.onos.store.flow.ReplicaInfoService;
import org.slf4j.Logger;
/**
* Manages replica placement information.
*/
@Component(immediate = true)
@Service
public class ReplicaInfoManager implements ReplicaInfoService {
private final Logger log = getLogger(getClass());
private final MastershipListener mastershipListener = new InternalMastershipListener();
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected EventDeliveryService eventDispatcher;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService mastershipService;
protected final AbstractListenerRegistry<ReplicaInfoEvent, ReplicaInfoEventListener>
listenerRegistry = new AbstractListenerRegistry<>();
@Activate
public void activate() {
eventDispatcher.addSink(ReplicaInfoEvent.class, listenerRegistry);
mastershipService.addListener(mastershipListener);
log.info("Started");
}
@Deactivate
public void deactivate() {
eventDispatcher.removeSink(ReplicaInfoEvent.class);
mastershipService.removeListener(mastershipListener);
log.info("Stopped");
}
@Override
public ReplicaInfo getReplicaInfoFor(DeviceId deviceId) {
// TODO: populate backup List when we reach the point we need them.
return new ReplicaInfo(mastershipService.getMasterFor(deviceId),
Collections.<NodeId>emptyList());
}
@Override
public void addListener(ReplicaInfoEventListener listener) {
listenerRegistry.addListener(checkNotNull(listener));
}
@Override
public void removeListener(ReplicaInfoEventListener listener) {
listenerRegistry.removeListener(checkNotNull(listener));
}
final class InternalMastershipListener implements MastershipListener {
@Override
public void event(MastershipEvent event) {
// TODO: distinguish stby list update, when MastershipService,
// start publishing them
final List<NodeId> standbyList = Collections.<NodeId>emptyList();
eventDispatcher.post(new ReplicaInfoEvent(MASTER_CHANGED,
event.subject(),
new ReplicaInfo(event.master(), standbyList)));
}
}
}
/**
* Implementation of the distributed flow rule store using p2p synchronization
* protocol.
*/
package org.onlab.onos.store.flow.impl;
package org.onlab.onos.store.host.impl;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.RandomUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
......@@ -12,6 +15,8 @@ import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.cluster.ClusterService;
import org.onlab.onos.cluster.ControllerNode;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.net.Annotations;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultHost;
......@@ -19,6 +24,7 @@ import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Host;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.HostLocation;
import org.onlab.onos.net.host.DefaultHostDescription;
import org.onlab.onos.net.host.HostClockService;
import org.onlab.onos.net.host.HostDescription;
import org.onlab.onos.net.host.HostEvent;
......@@ -32,22 +38,29 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
import org.onlab.onos.store.cluster.messaging.ClusterMessage;
import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
import org.onlab.onos.store.serializers.DistributedStoreSerializers;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.KryoPool;
import org.onlab.util.KryoNamespace;
import org.slf4j.Logger;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.onlab.onos.cluster.ControllerNodeToNodeId.toNodeId;
import static org.onlab.onos.net.host.HostEvent.Type.*;
import static org.onlab.util.Tools.namedThreads;
import static org.slf4j.LoggerFactory.getLogger;
//TODO: multi-provider, annotation not supported.
......@@ -86,26 +99,60 @@ public class GossipHostStore
private static final KryoSerializer SERIALIZER = new KryoSerializer() {
@Override
protected void setupKryoPool() {
serializerPool = KryoPool.newBuilder()
serializerPool = KryoNamespace.newBuilder()
.register(DistributedStoreSerializers.COMMON)
.register(InternalHostEvent.class)
.register(InternalHostRemovedEvent.class)
.register(HostFragmentId.class)
.register(HostAntiEntropyAdvertisement.class)
.build()
.populate(1);
}
};
private ScheduledExecutorService executor;
@Activate
public void activate() {
clusterCommunicator.addSubscriber(
GossipHostStoreMessageSubjects.HOST_UPDATED, new InternalHostEventListener());
GossipHostStoreMessageSubjects.HOST_UPDATED,
new InternalHostEventListener());
clusterCommunicator.addSubscriber(
GossipHostStoreMessageSubjects.HOST_REMOVED,
new InternalHostRemovedEventListener());
clusterCommunicator.addSubscriber(
GossipHostStoreMessageSubjects.HOST_REMOVED, new InternalHostRemovedEventListener());
GossipHostStoreMessageSubjects.HOST_ANTI_ENTROPY_ADVERTISEMENT,
new InternalHostAntiEntropyAdvertisementListener());
executor =
newSingleThreadScheduledExecutor(namedThreads("link-anti-entropy-%d"));
// TODO: Make these configurable
long initialDelaySec = 5;
long periodSec = 5;
// start anti-entropy thread
executor.scheduleAtFixedRate(new SendAdvertisementTask(),
initialDelaySec, periodSec, TimeUnit.SECONDS);
log.info("Started");
}
@Deactivate
public void deactivate() {
executor.shutdownNow();
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
log.error("Timeout during executor shutdown");
}
} catch (InterruptedException e) {
log.error("Error during executor shutdown", e);
}
hosts.clear();
removedHosts.clear();
locations.clear();
portAddresses.clear();
log.info("Stopped");
}
......@@ -153,7 +200,7 @@ public class GossipHostStore
descr.hwAddress(),
descr.vlan(),
new Timestamped<>(descr.location(), timestamp),
ImmutableSet.of(descr.ipAddress()));
ImmutableSet.copyOf(descr.ipAddress()));
hosts.put(hostId, newhost);
locations.put(descr.location(), newhost);
return new HostEvent(HOST_ADDED, newhost);
......@@ -169,12 +216,12 @@ public class GossipHostStore
return new HostEvent(HOST_MOVED, host);
}
if (host.ipAddresses().contains(descr.ipAddress())) {
if (host.ipAddresses().containsAll(descr.ipAddress())) {
return null;
}
Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses());
addresses.add(descr.ipAddress());
addresses.addAll(descr.ipAddress());
StoredHost updated = new StoredHost(providerId, host.id(),
host.mac(), host.vlan(),
host.location, addresses);
......@@ -381,6 +428,10 @@ public class GossipHostStore
public HostLocation location() {
return location.value();
}
public Timestamp timestamp() {
return location.timestamp();
}
}
private void notifyPeers(InternalHostRemovedEvent event) throws IOException {
......@@ -399,6 +450,16 @@ public class GossipHostStore
clusterCommunicator.broadcast(message);
}
private void unicastMessage(NodeId peer,
MessageSubject subject,
Object event) throws IOException {
ClusterMessage message = new ClusterMessage(
clusterService.getLocalNode().id(),
subject,
SERIALIZER.encode(event));
clusterCommunicator.unicast(message, peer);
}
private void notifyDelegateIfNotNull(HostEvent event) {
if (event != null) {
notifyDelegate(event);
......@@ -434,4 +495,165 @@ public class GossipHostStore
notifyDelegateIfNotNull(removeHostInternal(hostId, timestamp));
}
}
private final class SendAdvertisementTask implements Runnable {
@Override
public void run() {
if (Thread.currentThread().isInterrupted()) {
log.info("Interrupted, quitting");
return;
}
try {
final NodeId self = clusterService.getLocalNode().id();
Set<ControllerNode> nodes = clusterService.getNodes();
ImmutableList<NodeId> nodeIds = FluentIterable.from(nodes)
.transform(toNodeId())
.toList();
if (nodeIds.size() == 1 && nodeIds.get(0).equals(self)) {
log.debug("No other peers in the cluster.");
return;
}
NodeId peer;
do {
int idx = RandomUtils.nextInt(0, nodeIds.size());
peer = nodeIds.get(idx);
} while (peer.equals(self));
HostAntiEntropyAdvertisement ad = createAdvertisement();
if (Thread.currentThread().isInterrupted()) {
log.info("Interrupted, quitting");
return;
}
try {
unicastMessage(peer, GossipHostStoreMessageSubjects.HOST_ANTI_ENTROPY_ADVERTISEMENT, ad);
} catch (IOException e) {
log.debug("Failed to send anti-entropy advertisement to {}", peer);
return;
}
} catch (Exception e) {
// catch all Exception to avoid Scheduled task being suppressed.
log.error("Exception thrown while sending advertisement", e);
}
}
}
private HostAntiEntropyAdvertisement createAdvertisement() {
final NodeId self = clusterService.getLocalNode().id();
Map<HostFragmentId, Timestamp> timestamps = new HashMap<>(hosts.size());
Map<HostId, Timestamp> tombstones = new HashMap<>(removedHosts.size());
for (Entry<HostId, StoredHost> e : hosts.entrySet()) {
final HostId hostId = e.getKey();
final StoredHost hostInfo = e.getValue();
final ProviderId providerId = hostInfo.providerId();
timestamps.put(new HostFragmentId(hostId, providerId), hostInfo.timestamp());
}
for (Entry<HostId, Timestamped<Host>> e : removedHosts.entrySet()) {
tombstones.put(e.getKey(), e.getValue().timestamp());
}
return new HostAntiEntropyAdvertisement(self, timestamps, tombstones);
}
private synchronized void handleAntiEntropyAdvertisement(HostAntiEntropyAdvertisement ad) {
final NodeId sender = ad.sender();
for (Entry<HostId, StoredHost> host : hosts.entrySet()) {
// for each locally live Hosts...
final HostId hostId = host.getKey();
final StoredHost localHost = host.getValue();
final ProviderId providerId = localHost.providerId();
final HostFragmentId hostFragId = new HostFragmentId(hostId, providerId);
final Timestamp localLiveTimestamp = localHost.timestamp();
Timestamp remoteTimestamp = ad.timestamps().get(hostFragId);
if (remoteTimestamp == null) {
remoteTimestamp = ad.tombstones().get(hostId);
}
if (remoteTimestamp == null ||
localLiveTimestamp.compareTo(remoteTimestamp) > 0) {
// local is more recent, push
// TODO: annotation is lost
final HostDescription desc = new DefaultHostDescription(
localHost.mac(),
localHost.vlan(),
localHost.location(),
localHost.ipAddresses());
try {
unicastMessage(sender, GossipHostStoreMessageSubjects.HOST_UPDATED,
new InternalHostEvent(providerId, hostId, desc, localHost.timestamp()));
} catch (IOException e1) {
log.debug("Failed to send advertisement response", e1);
}
}
final Timestamp remoteDeadTimestamp = ad.tombstones().get(hostId);
if (remoteDeadTimestamp != null &&
remoteDeadTimestamp.compareTo(localLiveTimestamp) > 0) {
// sender has recent remove
notifyDelegateIfNotNull(removeHostInternal(hostId, remoteDeadTimestamp));
}
}
for (Entry<HostId, Timestamped<Host>> dead : removedHosts.entrySet()) {
// for each locally dead Hosts
final HostId hostId = dead.getKey();
final Timestamp localDeadTimestamp = dead.getValue().timestamp();
// TODO: pick proper ProviderId, when supporting multi-provider
final ProviderId providerId = dead.getValue().value().providerId();
final HostFragmentId hostFragId = new HostFragmentId(hostId, providerId);
final Timestamp remoteLiveTimestamp = ad.timestamps().get(hostFragId);
if (remoteLiveTimestamp != null &&
localDeadTimestamp.compareTo(remoteLiveTimestamp) > 0) {
// sender has zombie, push
try {
unicastMessage(sender, GossipHostStoreMessageSubjects.HOST_REMOVED,
new InternalHostRemovedEvent(hostId, localDeadTimestamp));
} catch (IOException e1) {
log.debug("Failed to send advertisement response", e1);
}
}
}
for (Entry<HostId, Timestamp> e : ad.tombstones().entrySet()) {
// for each remote tombstone advertisement...
final HostId hostId = e.getKey();
final Timestamp adRemoveTimestamp = e.getValue();
final StoredHost storedHost = hosts.get(hostId);
if (storedHost == null) {
continue;
}
if (adRemoveTimestamp.compareTo(storedHost.timestamp()) > 0) {
// sender has recent remove info, locally remove
notifyDelegateIfNotNull(removeHostInternal(hostId, adRemoveTimestamp));
}
}
}
private final class InternalHostAntiEntropyAdvertisementListener implements
ClusterMessageHandler {
@Override
public void handle(ClusterMessage message) {
log.debug("Received Host Anti-Entropy advertisement from peer: {}", message.sender());
HostAntiEntropyAdvertisement advertisement = SERIALIZER.decode(message.payload());
handleAntiEntropyAdvertisement(advertisement);
}
}
}
......
......@@ -4,6 +4,11 @@ import org.onlab.onos.store.cluster.messaging.MessageSubject;
public final class GossipHostStoreMessageSubjects {
private GossipHostStoreMessageSubjects() {}
public static final MessageSubject HOST_UPDATED = new MessageSubject("peer-host-updated");
public static final MessageSubject HOST_REMOVED = new MessageSubject("peer-host-removed");
public static final MessageSubject HOST_UPDATED
= new MessageSubject("peer-host-updated");
public static final MessageSubject HOST_REMOVED
= new MessageSubject("peer-host-removed");
public static final MessageSubject HOST_ANTI_ENTROPY_ADVERTISEMENT
= new MessageSubject("host-enti-entropy-advertisement");;
}
......
package org.onlab.onos.store.host.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Map;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.net.HostId;
import org.onlab.onos.store.Timestamp;
/**
* Host AE Advertisement message.
*/
public final class HostAntiEntropyAdvertisement {
private final NodeId sender;
private final Map<HostFragmentId, Timestamp> timestamps;
private final Map<HostId, Timestamp> tombstones;
public HostAntiEntropyAdvertisement(NodeId sender,
Map<HostFragmentId, Timestamp> timestamps,
Map<HostId, Timestamp> tombstones) {
this.sender = checkNotNull(sender);
this.timestamps = checkNotNull(timestamps);
this.tombstones = checkNotNull(tombstones);
}
public NodeId sender() {
return sender;
}
public Map<HostFragmentId, Timestamp> timestamps() {
return timestamps;
}
public Map<HostId, Timestamp> tombstones() {
return tombstones;
}
// For serializer
@SuppressWarnings("unused")
private HostAntiEntropyAdvertisement() {
this.sender = null;
this.timestamps = null;
this.tombstones = null;
}
}
package org.onlab.onos.store.host.impl;
import java.util.Objects;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.provider.ProviderId;
import com.google.common.base.MoreObjects;
/**
* Identifier for HostDescription from a Provider.
*/
public final class HostFragmentId {
public final ProviderId providerId;
public final HostId hostId;
public HostFragmentId(HostId hostId, ProviderId providerId) {
this.providerId = providerId;
this.hostId = hostId;
}
public HostId hostId() {
return hostId;
}
public ProviderId providerId() {
return providerId;
}
@Override
public int hashCode() {
return Objects.hash(providerId, hostId);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof HostFragmentId)) {
return false;
}
HostFragmentId that = (HostFragmentId) obj;
return Objects.equals(this.hostId, that.hostId) &&
Objects.equals(this.providerId, that.providerId);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("providerId", providerId)
.add("hostId", hostId)
.toString();
}
// for serializer
@SuppressWarnings("unused")
private HostFragmentId() {
this.providerId = null;
this.hostId = null;
}
}
/**
* Implementation of the distributed host store using p2p synchronization protocol.
*/
package org.onlab.onos.store.host.impl;
package org.onlab.onos.store.common.impl;
package org.onlab.onos.store.impl;
import static com.google.common.base.Preconditions.checkNotNull;
......@@ -58,12 +58,12 @@ public final class Timestamped<T> {
}
/**
* Tests if this timestamp is newer thatn the specified timestamp.
* @param timestamp to compare agains
* Tests if this timestamp is newer than the specified timestamp.
* @param other timestamp to compare against
* @return true if this instance is newer
*/
public boolean isNewer(Timestamp timestamp) {
return this.timestamp.compareTo(checkNotNull(timestamp)) > 0;
public boolean isNewer(Timestamp other) {
return this.timestamp.compareTo(checkNotNull(other)) > 0;
}
@Override
......
package org.onlab.onos.store.link.impl;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
......@@ -27,7 +26,6 @@ import org.onlab.onos.net.Link;
import org.onlab.onos.net.SparseAnnotations;
import org.onlab.onos.net.Link.Type;
import org.onlab.onos.net.LinkKey;
import org.onlab.onos.net.Provided;
import org.onlab.onos.net.device.DeviceClockService;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.link.LinkDescription;
......@@ -41,10 +39,10 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
import org.onlab.onos.store.cluster.messaging.ClusterMessage;
import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
import org.onlab.onos.store.serializers.DistributedStoreSerializers;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.util.KryoPool;
import org.onlab.util.KryoNamespace;
import org.slf4j.Logger;
import java.io.IOException;
......@@ -70,7 +68,9 @@ import static org.onlab.onos.net.link.LinkEvent.Type.*;
import static org.onlab.util.Tools.namedThreads;
import static org.slf4j.LoggerFactory.getLogger;
import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.notNull;
import static org.onlab.onos.store.link.impl.GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT;
/**
* Manages inventory of infrastructure links in distributed data store
......@@ -110,7 +110,7 @@ public class GossipLinkStore
private static final KryoSerializer SERIALIZER = new KryoSerializer() {
@Override
protected void setupKryoPool() {
serializerPool = KryoPool.newBuilder()
serializerPool = KryoNamespace.newBuilder()
.register(DistributedStoreSerializers.COMMON)
.register(InternalLinkEvent.class)
.register(InternalLinkRemovedEvent.class)
......@@ -239,9 +239,9 @@ public class GossipLinkStore
LinkKey key = linkKey(linkDescription.src(), linkDescription.dst());
final LinkEvent event;
final Timestamped<LinkDescription> mergedDesc;
synchronized (getLinkDescriptions(key)) {
synchronized (getOrCreateLinkDescriptions(key)) {
event = createOrUpdateLinkInternal(providerId, deltaDesc);
mergedDesc = getLinkDescriptions(key).get(providerId);
mergedDesc = getOrCreateLinkDescriptions(key).get(providerId);
}
if (event != null) {
......@@ -265,7 +265,7 @@ public class GossipLinkStore
LinkKey key = linkKey(linkDescription.value().src(),
linkDescription.value().dst());
Map<ProviderId, Timestamped<LinkDescription>> descs = getLinkDescriptions(key);
Map<ProviderId, Timestamped<LinkDescription>> descs = getOrCreateLinkDescriptions(key);
synchronized (descs) {
// if the link was previously removed, we should proceed if and
......@@ -296,7 +296,7 @@ public class GossipLinkStore
ProviderId providerId,
Timestamped<LinkDescription> linkDescription) {
// merge existing attributes and merge
// merge existing annotations
Timestamped<LinkDescription> existingLinkDescription = descs.get(providerId);
if (existingLinkDescription != null && existingLinkDescription.isNewer(linkDescription)) {
return null;
......@@ -360,7 +360,14 @@ public class GossipLinkStore
final LinkKey key = linkKey(src, dst);
DeviceId dstDeviceId = dst.deviceId();
Timestamp timestamp = deviceClockService.getTimestamp(dstDeviceId);
Timestamp timestamp = null;
try {
timestamp = deviceClockService.getTimestamp(dstDeviceId);
} catch (IllegalStateException e) {
//there are times when this is called before mastership
// handoff correctly completes.
return null;
}
LinkEvent event = removeLinkInternal(key, timestamp);
......@@ -377,14 +384,54 @@ public class GossipLinkStore
return event;
}
private static Timestamped<LinkDescription> getPrimaryDescription(
Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) {
synchronized (linkDescriptions) {
for (Entry<ProviderId, Timestamped<LinkDescription>>
e : linkDescriptions.entrySet()) {
if (!e.getKey().isAncillary()) {
return e.getValue();
}
}
}
return null;
}
// TODO: consider slicing out as Timestamp utils
/**
* Checks is timestamp is more recent than timestamped object.
*
* @param timestamp to check if this is more recent then other
* @param timestamped object to be tested against
* @return true if {@code timestamp} is more recent than {@code timestamped}
* or {@code timestamped is null}
*/
private static boolean isMoreRecent(Timestamp timestamp, Timestamped<?> timestamped) {
checkNotNull(timestamp);
if (timestamped == null) {
return true;
}
return timestamp.compareTo(timestamped.timestamp()) > 0;
}
private LinkEvent removeLinkInternal(LinkKey key, Timestamp timestamp) {
Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions =
getLinkDescriptions(key);
Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions
= getOrCreateLinkDescriptions(key);
synchronized (linkDescriptions) {
if (linkDescriptions.isEmpty()) {
// never seen such link before. keeping timestamp for record
removedLinks.put(key, timestamp);
return null;
}
// accept removal request if given timestamp is newer than
// the latest Timestamp from Primary provider
ProviderId primaryProviderId = pickPrimaryProviderId(linkDescriptions);
if (linkDescriptions.get(primaryProviderId).isNewer(timestamp)) {
Timestamped<LinkDescription> prim = getPrimaryDescription(linkDescriptions);
if (!isMoreRecent(timestamp, prim)) {
// outdated remove request, ignore
return null;
}
removedLinks.put(key, timestamp);
......@@ -406,12 +453,13 @@ public class GossipLinkStore
/**
* @return primary ProviderID, or randomly chosen one if none exists
*/
private ProviderId pickPrimaryProviderId(
private static ProviderId pickBaseProviderId(
Map<ProviderId, Timestamped<LinkDescription>> linkDescriptions) {
ProviderId fallBackPrimary = null;
for (Entry<ProviderId, Timestamped<LinkDescription>> e : linkDescriptions.entrySet()) {
if (!e.getKey().isAncillary()) {
// found primary
return e.getKey();
} else if (fallBackPrimary == null) {
// pick randomly as a fallback in case there is no primary
......@@ -421,9 +469,10 @@ public class GossipLinkStore
return fallBackPrimary;
}
// Guarded by linkDescs value (=locking each Link)
private Link composeLink(Map<ProviderId, Timestamped<LinkDescription>> descs) {
ProviderId primaryProviderId = pickPrimaryProviderId(descs);
Timestamped<LinkDescription> base = descs.get(primaryProviderId);
ProviderId baseProviderId = pickBaseProviderId(descs);
Timestamped<LinkDescription> base = descs.get(baseProviderId);
ConnectPoint src = base.value().src();
ConnectPoint dst = base.value().dst();
......@@ -432,7 +481,7 @@ public class GossipLinkStore
annotations = merge(annotations, base.value().annotations());
for (Entry<ProviderId, Timestamped<LinkDescription>> e : descs.entrySet()) {
if (primaryProviderId.equals(e.getKey())) {
if (baseProviderId.equals(e.getKey())) {
continue;
}
......@@ -445,10 +494,10 @@ public class GossipLinkStore
annotations = merge(annotations, e.getValue().value().annotations());
}
return new DefaultLink(primaryProviderId , src, dst, type, annotations);
return new DefaultLink(baseProviderId, src, dst, type, annotations);
}
private Map<ProviderId, Timestamped<LinkDescription>> getLinkDescriptions(LinkKey key) {
private Map<ProviderId, Timestamped<LinkDescription>> getOrCreateLinkDescriptions(LinkKey key) {
Map<ProviderId, Timestamped<LinkDescription>> r;
r = linkDescs.get(key);
if (r != null) {
......@@ -464,11 +513,11 @@ public class GossipLinkStore
}
}
private Timestamped<LinkDescription> getLinkDescription(LinkKey key, ProviderId providerId) {
return getLinkDescriptions(key).get(providerId);
}
private final Function<LinkKey, Link> lookupLink = new LookupLink();
/**
* Returns a Function to lookup Link instance using LinkKey from cache.
* @return
*/
private Function<LinkKey, Link> lookupLink() {
return lookupLink;
}
......@@ -476,21 +525,12 @@ public class GossipLinkStore
private final class LookupLink implements Function<LinkKey, Link> {
@Override
public Link apply(LinkKey input) {
if (input == null) {
return null;
} else {
return links.get(input);
}
}
private static final class IsPrimary implements Predicate<Provided> {
private static final Predicate<Provided> IS_PRIMARY = new IsPrimary();
public static final Predicate<Provided> isPrimary() {
return IS_PRIMARY;
}
@Override
public boolean apply(Provided input) {
return !input.providerId().isAncillary();
}
}
private void notifyDelegateIfNotNull(LinkEvent event) {
......@@ -499,7 +539,6 @@ public class GossipLinkStore
}
}
// TODO: should we be throwing exception?
private void broadcastMessage(MessageSubject subject, Object event) throws IOException {
ClusterMessage message = new ClusterMessage(
clusterService.getLocalNode().id(),
......@@ -508,17 +547,12 @@ public class GossipLinkStore
clusterCommunicator.broadcast(message);
}
// TODO: should we be throwing exception?
private void unicastMessage(NodeId recipient, MessageSubject subject, Object event) {
try {
private void unicastMessage(NodeId recipient, MessageSubject subject, Object event) throws IOException {
ClusterMessage message = new ClusterMessage(
clusterService.getLocalNode().id(),
subject,
SERIALIZER.encode(event));
clusterCommunicator.unicast(message, recipient);
} catch (IOException e) {
log.error("Failed to send a {} message to {}", subject.value(), recipient);
}
}
private void notifyPeers(InternalLinkEvent event) throws IOException {
......@@ -529,12 +563,22 @@ public class GossipLinkStore
broadcastMessage(GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
}
// notify peer, silently ignoring error
private void notifyPeer(NodeId peer, InternalLinkEvent event) {
try {
unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_UPDATE, event);
} catch (IOException e) {
log.debug("Failed to notify peer {} with message {}", peer, event);
}
}
// notify peer, silently ignoring error
private void notifyPeer(NodeId peer, InternalLinkRemovedEvent event) {
try {
unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_REMOVED, event);
} catch (IOException e) {
log.debug("Failed to notify peer {} with message {}", peer, event);
}
}
private final class SendAdvertisementTask implements Runnable {
......@@ -573,9 +617,9 @@ public class GossipLinkStore
}
try {
unicastMessage(peer, GossipLinkStoreMessageSubjects.LINK_ANTI_ENTROPY_ADVERTISEMENT, ad);
} catch (Exception e) {
log.error("Failed to send anti-entropy advertisement", e);
unicastMessage(peer, LINK_ANTI_ENTROPY_ADVERTISEMENT, ad);
} catch (IOException e) {
log.debug("Failed to send anti-entropy advertisement to {}", peer);
return;
}
} catch (Exception e) {
......@@ -608,42 +652,75 @@ public class GossipLinkStore
return new LinkAntiEntropyAdvertisement(self, linkTimestamps, linkTombstones);
}
private void handleAntiEntropyAdvertisement(LinkAntiEntropyAdvertisement advertisement) {
private void handleAntiEntropyAdvertisement(LinkAntiEntropyAdvertisement ad) {
final NodeId sender = ad.sender();
boolean localOutdated = false;
NodeId peer = advertisement.sender();
for (Entry<LinkKey, Map<ProviderId, Timestamped<LinkDescription>>>
l : linkDescs.entrySet()) {
Map<LinkFragmentId, Timestamp> linkTimestamps = advertisement.linkTimestamps();
Map<LinkKey, Timestamp> linkTombstones = advertisement.linkTombstones();
for (Map.Entry<LinkFragmentId, Timestamp> entry : linkTimestamps.entrySet()) {
LinkFragmentId linkFragmentId = entry.getKey();
Timestamp peerTimestamp = entry.getValue();
final LinkKey key = l.getKey();
final Map<ProviderId, Timestamped<LinkDescription>> link = l.getValue();
synchronized (link) {
Timestamp localLatest = removedLinks.get(key);
LinkKey key = linkFragmentId.linkKey();
ProviderId providerId = linkFragmentId.providerId();
for (Entry<ProviderId, Timestamped<LinkDescription>> p : link.entrySet()) {
final ProviderId providerId = p.getKey();
final Timestamped<LinkDescription> pDesc = p.getValue();
Timestamped<LinkDescription> linkDescription = getLinkDescription(key, providerId);
if (linkDescription.isNewer(peerTimestamp)) {
final LinkFragmentId fragId = new LinkFragmentId(key, providerId);
// remote
Timestamp remoteTimestamp = ad.linkTimestamps().get(fragId);
if (remoteTimestamp == null) {
remoteTimestamp = ad.linkTombstones().get(key);
}
if (remoteTimestamp == null ||
pDesc.isNewer(remoteTimestamp)) {
// I have more recent link description. update peer.
notifyPeer(peer, new InternalLinkEvent(providerId, linkDescription));
notifyPeer(sender, new InternalLinkEvent(providerId, pDesc));
} else {
final Timestamp remoteLive = ad.linkTimestamps().get(fragId);
if (remoteLive != null &&
remoteLive.compareTo(pDesc.timestamp()) > 0) {
// I have something outdated
localOutdated = true;
}
}
// else TODO: Peer has more recent link description. request it.
Timestamp linkRemovedTimestamp = removedLinks.get(key);
if (linkRemovedTimestamp != null && linkRemovedTimestamp.compareTo(peerTimestamp) > 0) {
// peer has a zombie link. update peer.
notifyPeer(peer, new InternalLinkRemovedEvent(key, linkRemovedTimestamp));
// search local latest along the way
if (localLatest == null ||
pDesc.isNewer(localLatest)) {
localLatest = pDesc.timestamp();
}
}
// Tests if remote remove is more recent then local latest.
final Timestamp remoteRemove = ad.linkTombstones().get(key);
if (remoteRemove != null) {
if (localLatest != null &&
localLatest.compareTo(remoteRemove) < 0) {
// remote remove is more recent
notifyDelegateIfNotNull(removeLinkInternal(key, remoteRemove));
}
}
}
}
for (Map.Entry<LinkKey, Timestamp> entry : linkTombstones.entrySet()) {
LinkKey key = entry.getKey();
Timestamp peerTimestamp = entry.getValue();
ProviderId primaryProviderId = pickPrimaryProviderId(getLinkDescriptions(key));
if (primaryProviderId != null) {
if (!getLinkDescription(key, primaryProviderId).isNewer(peerTimestamp)) {
notifyDelegateIfNotNull(removeLinkInternal(key, peerTimestamp));
// populate remove info if not known locally
for (Entry<LinkKey, Timestamp> remoteRm : ad.linkTombstones().entrySet()) {
final LinkKey key = remoteRm.getKey();
final Timestamp remoteRemove = remoteRm.getValue();
// relying on removeLinkInternal to ignore stale info
notifyDelegateIfNotNull(removeLinkInternal(key, remoteRemove));
}
if (localOutdated) {
// send back advertisement to speed up convergence
try {
unicastMessage(sender, LINK_ANTI_ENTROPY_ADVERTISEMENT,
createAdvertisement());
} catch (IOException e) {
log.debug("Failed to send back active advertisement");
}
}
}
......@@ -652,7 +729,7 @@ public class GossipLinkStore
@Override
public void handle(ClusterMessage message) {
log.info("Received link event from peer: {}", message.sender());
log.trace("Received link event from peer: {}", message.sender());
InternalLinkEvent event = (InternalLinkEvent) SERIALIZER.decode(message.payload());
ProviderId providerId = event.providerId();
......@@ -666,7 +743,7 @@ public class GossipLinkStore
@Override
public void handle(ClusterMessage message) {
log.info("Received link removed event from peer: {}", message.sender());
log.trace("Received link removed event from peer: {}", message.sender());
InternalLinkRemovedEvent event = (InternalLinkRemovedEvent) SERIALIZER.decode(message.payload());
LinkKey linkKey = event.linkKey();
......
......@@ -4,7 +4,7 @@ import com.google.common.base.MoreObjects;
import org.onlab.onos.net.link.LinkDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.Timestamped;
/**
* Information published by GossipDeviceStore to notify peers of a device
......
/**
* Implementation of link store using distributed p2p synchronization protocol.
* Implementation of distributed link store using p2p synchronization protocol.
*/
package org.onlab.onos.store.link.impl;
......
package org.onlab.onos.store.serializers;
import org.onlab.onos.store.common.impl.Timestamped;
import org.onlab.onos.store.impl.MastershipBasedTimestamp;
import org.onlab.onos.store.impl.Timestamped;
import org.onlab.onos.store.impl.WallClockTimestamp;
import org.onlab.util.KryoPool;
import org.onlab.util.KryoNamespace;
public final class DistributedStoreSerializers {
/**
* KryoPool which can serialize ON.lab misc classes.
* KryoNamespace which can serialize ON.lab misc classes.
*/
public static final KryoPool COMMON = KryoPool.newBuilder()
.register(KryoPoolUtil.API)
public static final KryoNamespace COMMON = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
.register(Timestamped.class)
.register(MastershipBasedTimestamp.class, new MastershipBasedTimestampSerializer())
.register(WallClockTimestamp.class)
......
/**
* Implementation of distributed topology store using p2p synchronization protocol.
*/
package org.onlab.onos.store.topology.impl;
......@@ -53,6 +53,7 @@ import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
import org.onlab.onos.store.cluster.messaging.ClusterMessage;
import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
import org.onlab.onos.store.cluster.messaging.MessageSubject;
import org.onlab.packet.ChassisId;
import org.onlab.packet.IpPrefix;
import com.google.common.collect.Iterables;
......@@ -74,6 +75,7 @@ public class GossipDeviceStoreTest {
private static final String SW1 = "3.8.1";
private static final String SW2 = "3.9.5";
private static final String SN = "43311-12345";
private static final ChassisId CID = new ChassisId();
private static final PortNumber P1 = PortNumber.portNumber(1);
private static final PortNumber P2 = PortNumber.portNumber(2);
......@@ -158,7 +160,7 @@ public class GossipDeviceStoreTest {
SparseAnnotations... annotations) {
DeviceDescription description =
new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
HW, swVersion, SN, annotations);
HW, swVersion, SN, CID, annotations);
reset(clusterCommunicator);
try {
expect(clusterCommunicator.broadcast(anyObject(ClusterMessage.class)))
......@@ -175,7 +177,7 @@ public class GossipDeviceStoreTest {
SparseAnnotations... annotations) {
DeviceDescription description =
new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
HW, swVersion, SN, annotations);
HW, swVersion, SN, CID, annotations);
deviceStore.createOrUpdateDevice(PIDA, deviceId, description);
}
......@@ -315,7 +317,7 @@ public class GossipDeviceStoreTest {
public final void testCreateOrUpdateDevice() throws IOException {
DeviceDescription description =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW1, SN);
HW, SW1, SN, CID);
Capture<ClusterMessage> bcast = new Capture<>();
resetCommunicatorExpectingSingleBroadcast(bcast);
......@@ -328,7 +330,7 @@ public class GossipDeviceStoreTest {
DeviceDescription description2 =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW2, SN);
HW, SW2, SN, CID);
resetCommunicatorExpectingSingleBroadcast(bcast);
DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
assertEquals(DEVICE_UPDATED, event2.type());
......@@ -346,7 +348,7 @@ public class GossipDeviceStoreTest {
// add
DeviceDescription description =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW1, SN, A2);
HW, SW1, SN, CID, A2);
Capture<ClusterMessage> bcast = new Capture<>();
resetCommunicatorExpectingSingleBroadcast(bcast);
......@@ -362,7 +364,7 @@ public class GossipDeviceStoreTest {
// update from primary
DeviceDescription description2 =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW2, SN, A1);
HW, SW2, SN, CID, A1);
resetCommunicatorExpectingSingleBroadcast(bcast);
DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
......@@ -392,7 +394,7 @@ public class GossipDeviceStoreTest {
// But, Ancillary annotations will be in effect
DeviceDescription description3 =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW1, SN, A2_2);
HW, SW1, SN, CID, A2_2);
resetCommunicatorExpectingSingleBroadcast(bcast);
DeviceEvent event3 = deviceStore.createOrUpdateDevice(PIDA, DID1, description3);
......@@ -775,7 +777,7 @@ public class GossipDeviceStoreTest {
DeviceDescription description =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW1, SN);
HW, SW1, SN, CID);
deviceStore.setDelegate(checkAdd);
deviceStore.createOrUpdateDevice(PID, DID1, description);
assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
......@@ -783,7 +785,7 @@ public class GossipDeviceStoreTest {
DeviceDescription description2 =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW2, SN);
HW, SW2, SN, CID);
deviceStore.unsetDelegate(checkAdd);
deviceStore.setDelegate(checkUpdate);
deviceStore.createOrUpdateDevice(PID, DID1, description2);
......
package org.onlab.onos.store.flow.impl;
import static com.google.common.base.Preconditions.checkState;
import static org.junit.Assert.*;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.event.AbstractListenerRegistry;
import org.onlab.onos.event.DefaultEventSinkRegistry;
import org.onlab.onos.event.Event;
import org.onlab.onos.event.EventDeliveryService;
import org.onlab.onos.event.EventSink;
import org.onlab.onos.mastership.MastershipEvent;
import org.onlab.onos.mastership.MastershipEvent.Type;
import org.onlab.onos.mastership.MastershipListener;
import org.onlab.onos.mastership.MastershipService;
import org.onlab.onos.mastership.MastershipServiceAdapter;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.store.flow.ReplicaInfo;
import org.onlab.onos.store.flow.ReplicaInfoEvent;
import org.onlab.onos.store.flow.ReplicaInfoEventListener;
import org.onlab.onos.store.flow.ReplicaInfoService;
import com.google.common.base.Optional;
import com.google.common.collect.Maps;
public class ReplicaInfoManagerTest {
private static final DeviceId DID1 = DeviceId.deviceId("of:1");
private static final DeviceId DID2 = DeviceId.deviceId("of:2");
private static final NodeId NID1 = new NodeId("foo");
private ReplicaInfoManager mgr;
private ReplicaInfoService service;
private AbstractListenerRegistry<MastershipEvent, MastershipListener>
mastershipListenerRegistry;
private TestEventDispatcher eventDispatcher;
@Before
public void setUp() throws Exception {
mastershipListenerRegistry = new AbstractListenerRegistry<>();
mgr = new ReplicaInfoManager();
service = mgr;
eventDispatcher = new TestEventDispatcher();
mgr.eventDispatcher = eventDispatcher;
mgr.mastershipService = new TestMastershipService();
// register dummy mastership event source
mgr.eventDispatcher.addSink(MastershipEvent.class, mastershipListenerRegistry);
mgr.activate();
}
@After
public void tearDown() throws Exception {
mgr.deactivate();
}
@Test
public void testGetReplicaInfoFor() {
ReplicaInfo info1 = service.getReplicaInfoFor(DID1);
assertEquals(Optional.of(NID1), info1.master());
// backups are always empty for now
assertEquals(Collections.emptyList(), info1.backups());
ReplicaInfo info2 = service.getReplicaInfoFor(DID2);
assertEquals("There's no master", Optional.absent(), info2.master());
// backups are always empty for now
assertEquals(Collections.emptyList(), info2.backups());
}
@Test
public void testReplicaInfoEvent() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
service.addListener(new MasterNodeCheck(latch, DID1, NID1));
// fake MastershipEvent
eventDispatcher.post(new MastershipEvent(Type.MASTER_CHANGED, DID1, NID1));
assertTrue(latch.await(1, TimeUnit.SECONDS));
}
private final class MasterNodeCheck implements ReplicaInfoEventListener {
private final CountDownLatch latch;
private Optional<NodeId> expectedMaster;
private DeviceId expectedDevice;
MasterNodeCheck(CountDownLatch latch, DeviceId did,
NodeId nid) {
this.latch = latch;
this.expectedMaster = Optional.fromNullable(nid);
this.expectedDevice = did;
}
@Override
public void event(ReplicaInfoEvent event) {
assertEquals(expectedDevice, event.subject());
assertEquals(expectedMaster, event.replicaInfo().master());
// backups are always empty for now
assertEquals(Collections.emptyList(), event.replicaInfo().backups());
latch.countDown();
}
}
private final class TestMastershipService
extends MastershipServiceAdapter
implements MastershipService {
private Map<DeviceId, NodeId> masters;
TestMastershipService() {
masters = Maps.newHashMap();
masters.put(DID1, NID1);
// DID2 has no master
}
@Override
public NodeId getMasterFor(DeviceId deviceId) {
return masters.get(deviceId);
}
@Override
public void addListener(MastershipListener listener) {
mastershipListenerRegistry.addListener(listener);
}
@Override
public void removeListener(MastershipListener listener) {
mastershipListenerRegistry.removeListener(listener);
}
}
// code clone
/**
* Implements event delivery system that delivers events synchronously, or
* in-line with the post method invocation.
*/
private static class TestEventDispatcher extends DefaultEventSinkRegistry
implements EventDeliveryService {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void post(Event event) {
EventSink sink = getSink(event.getClass());
checkState(sink != null, "No sink for event %s", event);
sink.process(event);
}
}
}
......@@ -7,7 +7,7 @@ import java.nio.ByteBuffer;
import org.junit.Test;
import org.onlab.onos.store.Timestamp;
import org.onlab.onos.store.serializers.MastershipBasedTimestampSerializer;
import org.onlab.util.KryoPool;
import org.onlab.util.KryoNamespace;
import com.google.common.testing.EqualsTester;
......@@ -63,7 +63,7 @@ public class MastershipBasedTimestampTest {
@Test
public final void testKryoSerializable() {
final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
final KryoPool kryos = KryoPool.newBuilder()
final KryoNamespace kryos = KryoNamespace.newBuilder()
.register(MastershipBasedTimestamp.class)
.build();
......@@ -79,7 +79,7 @@ public class MastershipBasedTimestampTest {
@Test
public final void testKryoSerializableWithHandcraftedSerializer() {
final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
final KryoPool kryos = KryoPool.newBuilder()
final KryoNamespace kryos = KryoNamespace.newBuilder()
.register(MastershipBasedTimestamp.class, new MastershipBasedTimestampSerializer())
.build();
......
package org.onlab.onos.store.common.impl;
package org.onlab.onos.store.impl;
import static org.junit.Assert.*;
......@@ -6,8 +6,7 @@ import java.nio.ByteBuffer;
import org.junit.Test;
import org.onlab.onos.store.Timestamp;
import org.onlab.onos.store.impl.MastershipBasedTimestamp;
import org.onlab.util.KryoPool;
import org.onlab.util.KryoNamespace;
import com.google.common.testing.EqualsTester;
......@@ -78,7 +77,7 @@ public class TimestampedTest {
@Test
public final void testKryoSerializable() {
final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
final KryoPool kryos = KryoPool.newBuilder()
final KryoNamespace kryos = KryoNamespace.newBuilder()
.register(Timestamped.class,
MastershipBasedTimestamp.class)
.build();
......
......@@ -6,7 +6,7 @@ import java.nio.ByteBuffer;
import org.junit.Test;
import org.onlab.onos.store.Timestamp;
import org.onlab.util.KryoPool;
import org.onlab.util.KryoNamespace;
import com.google.common.testing.EqualsTester;
......@@ -30,7 +30,7 @@ public class WallClockTimestampTest {
public final void testKryoSerializable() {
WallClockTimestamp ts1 = new WallClockTimestamp();
final ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
final KryoPool kryos = KryoPool.newBuilder()
final KryoNamespace kryos = KryoNamespace.newBuilder()
.register(WallClockTimestamp.class)
.build();
......
......@@ -19,10 +19,6 @@
<dependencies>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-api</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-core-serializers</artifactId>
<version>${project.version}</version>
</dependency>
......@@ -38,23 +34,6 @@
<scope>test</scope>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
......
......@@ -2,6 +2,9 @@ package org.onlab.onos.store.mastership.impl;
import static org.onlab.onos.mastership.MastershipEvent.Type.MASTER_CHANGED;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
......@@ -20,11 +23,18 @@ import org.onlab.onos.mastership.MastershipTerm;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.MastershipRole;
import org.onlab.onos.store.common.AbstractHazelcastStore;
import org.onlab.onos.store.common.SMap;
import org.onlab.onos.store.serializers.KryoNamespaces;
import org.onlab.onos.store.serializers.KryoSerializer;
import org.onlab.util.KryoNamespace;
import com.google.common.collect.ImmutableSet;
import com.hazelcast.core.ILock;
import com.hazelcast.core.IMap;
import com.hazelcast.core.MultiMap;
import com.hazelcast.core.EntryEvent;
import com.hazelcast.core.EntryListener;
import com.hazelcast.core.IAtomicLong;
import com.hazelcast.core.MapEvent;
import static org.onlab.onos.net.MastershipRole.*;
/**
* Distributed implementation of the mastership store. The store is
......@@ -36,36 +46,42 @@ public class DistributedMastershipStore
extends AbstractHazelcastStore<MastershipEvent, MastershipStoreDelegate>
implements MastershipStore {
//arbitrary lock name
private static final String LOCK = "lock";
//initial term/TTL value
private static final Integer INIT = 0;
//devices to masters
protected IMap<byte[], byte[]> masters;
//device to node roles
protected SMap<DeviceId, RoleValue> roleMap;
//devices to terms
protected IMap<byte[], Integer> terms;
protected SMap<DeviceId, Integer> terms;
//last-known cluster size, used for tie-breaking when partitioning occurs
protected IAtomicLong clusterSize;
//re-election related, disjoint-set structures:
//device-nodes multiset of available nodes
protected MultiMap<byte[], byte[]> standbys;
//device-nodes multiset for nodes that have given up on device
protected MultiMap<byte[], byte[]> unusable;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ClusterService clusterService;
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
@Activate
public void activate() {
super.activate();
masters = theInstance.getMap("masters");
terms = theInstance.getMap("terms");
standbys = theInstance.getMultiMap("backups");
unusable = theInstance.getMultiMap("unusable");
this.serializer = new KryoSerializer() {
@Override
protected void setupKryoPool() {
serializerPool = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
masters.addEntryListener(new RemoteMasterShipEventHandler(), true);
.register(RoleValue.class, new RoleValueSerializer())
.build()
.populate(1);
}
};
roleMap = new SMap(theInstance.getMap("nodeRoles"), this.serializer);
terms = new SMap(theInstance.getMap("terms"), this.serializer);
clusterSize = theInstance.getAtomicLong("clustersize");
roleMap.addEntryListener((new RemoteMasterShipEventHandler()), true);
log.info("Started");
}
......@@ -77,12 +93,9 @@ implements MastershipStore {
@Override
public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
byte[] did = serialize(deviceId);
byte[] nid = serialize(nodeId);
NodeId current = deserialize(masters.get(did));
NodeId current = getNode(MASTER, deviceId);
if (current == null) {
if (standbys.containsEntry(did, nid)) {
if (isRole(STANDBY, nodeId, deviceId)) {
//was previously standby, or set to standby from master
return MastershipRole.STANDBY;
} else {
......@@ -101,55 +114,79 @@ implements MastershipStore {
@Override
public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) {
byte [] did = serialize(deviceId);
byte [] nid = serialize(nodeId);
ILock lock = theInstance.getLock(LOCK);
lock.lock();
try {
MastershipRole role = getRole(nodeId, deviceId);
roleMap.lock(deviceId);
try {
RoleValue rv = getRoleValue(deviceId);
switch (role) {
case MASTER:
//reinforce mastership
evict(nid, did);
rv.reassign(nodeId, STANDBY, NONE);
roleMap.put(deviceId, rv);
return null;
case STANDBY:
//make current master standby
byte [] current = masters.get(did);
NodeId current = rv.get(MASTER);
if (current != null) {
backup(current, did);
//backup and replace current master
rv.reassign(nodeId, NONE, STANDBY);
rv.replace(current, nodeId, MASTER);
} else {
//no master before so just add.
rv.add(MASTER, nodeId);
}
//assign specified node as new master
masters.put(did, nid);
evict(nid, did);
updateTerm(did);
rv.reassign(nodeId, STANDBY, NONE);
roleMap.put(deviceId, rv);
updateTerm(deviceId);
return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId);
case NONE:
masters.put(did, nid);
evict(nid, did);
updateTerm(did);
rv.add(MASTER, nodeId);
rv.reassign(nodeId, STANDBY, NONE);
roleMap.put(deviceId, rv);
updateTerm(deviceId);
return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId);
default:
log.warn("unknown Mastership Role {}", role);
return null;
}
} finally {
lock.unlock();
roleMap.unlock(deviceId);
}
}
@Override
public NodeId getMaster(DeviceId deviceId) {
return deserialize(masters.get(serialize(deviceId)));
return getNode(MASTER, deviceId);
}
@Override
public List<NodeId> getNodes(DeviceId deviceId) {
List<NodeId> nodes = new LinkedList<>();
//add current master to head - if there is one.
roleMap.lock(deviceId);
try {
RoleValue rv = getRoleValue(deviceId);
NodeId master = rv.get(MASTER);
if (master != null) {
nodes.add(master);
}
//We ignore NONE nodes.
nodes.addAll(rv.nodesOfRole(STANDBY));
return Collections.unmodifiableList(nodes);
} finally {
roleMap.unlock(deviceId);
}
}
@Override
public Set<DeviceId> getDevices(NodeId nodeId) {
ImmutableSet.Builder<DeviceId> builder = ImmutableSet.builder();
for (Map.Entry<byte[], byte[]> entry : masters.entrySet()) {
if (nodeId.equals(deserialize(entry.getValue()))) {
builder.add((DeviceId) deserialize(entry.getKey()));
for (Map.Entry<DeviceId, RoleValue> el : roleMap.entrySet()) {
if (nodeId.equals(el.getValue().get(MASTER))) {
builder.add(el.getKey());
}
}
......@@ -159,26 +196,28 @@ implements MastershipStore {
@Override
public MastershipRole requestRole(DeviceId deviceId) {
NodeId local = clusterService.getLocalNode().id();
byte [] did = serialize(deviceId);
byte [] lnid = serialize(local);
ILock lock = theInstance.getLock(LOCK);
lock.lock();
roleMap.lock(deviceId);
try {
RoleValue rv = getRoleValue(deviceId);
MastershipRole role = getRole(local, deviceId);
switch (role) {
case MASTER:
evict(lnid, did);
rv.reassign(local, STANDBY, NONE);
roleMap.put(deviceId, rv);
break;
case STANDBY:
backup(lnid, did);
terms.putIfAbsent(did, INIT);
rv.reassign(local, NONE, STANDBY);
roleMap.put(deviceId, rv);
terms.putIfAbsent(deviceId, INIT);
break;
case NONE:
//claim mastership
masters.put(did, lnid);
evict(lnid, did);
updateTerm(did);
rv.add(MASTER, local);
rv.reassign(local, STANDBY, NONE);
roleMap.put(deviceId, rv);
updateTerm(deviceId);
role = MastershipRole.MASTER;
break;
default:
......@@ -186,152 +225,178 @@ implements MastershipStore {
}
return role;
} finally {
lock.unlock();
roleMap.unlock(deviceId);
}
}
@Override
public MastershipTerm getTermFor(DeviceId deviceId) {
byte[] did = serialize(deviceId);
if ((masters.get(did) == null) ||
(terms.get(did) == null)) {
RoleValue rv = getRoleValue(deviceId);
if ((rv.get(MASTER) == null) || (terms.get(deviceId) == null)) {
return null;
}
return MastershipTerm.of(
(NodeId) deserialize(masters.get(did)), terms.get(did));
return MastershipTerm.of(rv.get(MASTER), terms.get(deviceId));
}
@Override
public MastershipEvent setStandby(NodeId nodeId, DeviceId deviceId) {
byte [] did = serialize(deviceId);
byte [] nid = serialize(nodeId);
MastershipEvent event = null;
ILock lock = theInstance.getLock(LOCK);
lock.lock();
roleMap.lock(deviceId);
try {
RoleValue rv = getRoleValue(deviceId);
MastershipRole role = getRole(nodeId, deviceId);
switch (role) {
case MASTER:
event = reelect(nodeId, deviceId);
backup(nid, did);
break;
event = reelect(nodeId, deviceId, rv);
//fall through to reinforce role
case STANDBY:
//fall through to reinforce role
case NONE:
backup(nid, did);
rv.reassign(nodeId, NONE, STANDBY);
roleMap.put(deviceId, rv);
break;
default:
log.warn("unknown Mastership Role {}", role);
}
return event;
} finally {
lock.unlock();
roleMap.unlock(deviceId);
}
}
@Override
public MastershipEvent relinquishRole(NodeId nodeId, DeviceId deviceId) {
byte [] did = serialize(deviceId);
byte [] nid = serialize(nodeId);
MastershipEvent event = null;
ILock lock = theInstance.getLock(LOCK);
lock.lock();
roleMap.lock(deviceId);
try {
RoleValue rv = getRoleValue(deviceId);
MastershipRole role = getRole(nodeId, deviceId);
switch (role) {
case MASTER:
event = reelect(nodeId, deviceId);
evict(nid, did);
break;
event = reelect(nodeId, deviceId, rv);
//fall through to reinforce relinquishment
case STANDBY:
//fall through to reinforce relinquishment
case NONE:
evict(nid, did);
rv.reassign(nodeId, STANDBY, NONE);
roleMap.put(deviceId, rv);
break;
default:
log.warn("unknown Mastership Role {}", role);
}
return event;
} finally {
lock.unlock();
roleMap.unlock(deviceId);
}
}
//helper to fetch a new master candidate for a given device.
private MastershipEvent reelect(NodeId current, DeviceId deviceId) {
byte [] did = serialize(deviceId);
byte [] nid = serialize(current);
private MastershipEvent reelect(
NodeId current, DeviceId deviceId, RoleValue rv) {
//if this is an queue it'd be neater.
byte [] backup = null;
for (byte [] n : standbys.get(serialize(deviceId))) {
if (!current.equals(deserialize(n))) {
NodeId backup = null;
for (NodeId n : rv.nodesOfRole(STANDBY)) {
if (!current.equals(n)) {
backup = n;
break;
}
}
if (backup == null) {
masters.remove(did, nid);
log.info("{} giving up and going to NONE for {}", current, deviceId);
rv.remove(MASTER, current);
roleMap.put(deviceId, rv);
return null;
} else {
masters.put(did, backup);
evict(backup, did);
Integer term = terms.get(did);
terms.put(did, ++term);
return new MastershipEvent(
MASTER_CHANGED, deviceId, (NodeId) deserialize(backup));
log.info("{} trying to pass mastership for {} to {}", current, deviceId, backup);
rv.replace(current, backup, MASTER);
rv.reassign(backup, STANDBY, NONE);
roleMap.put(deviceId, rv);
Integer term = terms.get(deviceId);
terms.put(deviceId, ++term);
return new MastershipEvent(MASTER_CHANGED, deviceId, backup);
}
}
//adds node to pool(s) of backups and moves them from unusable.
private void backup(byte [] nodeId, byte [] deviceId) {
if (!standbys.containsEntry(deviceId, nodeId)) {
standbys.put(deviceId, nodeId);
}
if (unusable.containsEntry(deviceId, nodeId)) {
unusable.remove(deviceId, nodeId);
//return the RoleValue structure for a device, or create one
private RoleValue getRoleValue(DeviceId deviceId) {
RoleValue value = roleMap.get(deviceId);
if (value == null) {
value = new RoleValue();
roleMap.put(deviceId, value);
}
return value;
}
//adds node to unusable and evicts it from backup pool.
private void evict(byte [] nodeId, byte [] deviceId) {
if (!unusable.containsEntry(deviceId, nodeId)) {
unusable.put(deviceId, nodeId);
//get first applicable node out of store-unique structure.
private NodeId getNode(MastershipRole role, DeviceId deviceId) {
RoleValue value = roleMap.get(deviceId);
if (value != null) {
return value.get(role);
}
return null;
}
if (standbys.containsEntry(deviceId, nodeId)) {
standbys.remove(deviceId, nodeId);
//check if node is a certain role given a device
private boolean isRole(
MastershipRole role, NodeId nodeId, DeviceId deviceId) {
RoleValue value = roleMap.get(deviceId);
if (value != null) {
return value.contains(role, nodeId);
}
return false;
}
//adds or updates term information.
private void updateTerm(byte [] deviceId) {
private void updateTerm(DeviceId deviceId) {
terms.lock(deviceId);
try {
Integer term = terms.get(deviceId);
if (term == null) {
terms.put(deviceId, INIT);
} else {
terms.put(deviceId, ++term);
}
} finally {
terms.unlock(deviceId);
}
}
private class RemoteMasterShipEventHandler extends RemoteEventHandler<DeviceId, NodeId> {
private class RemoteMasterShipEventHandler implements EntryListener<DeviceId, RoleValue> {
@Override
public void entryAdded(EntryEvent<DeviceId, RoleValue> event) {
}
@Override
public void entryRemoved(EntryEvent<DeviceId, RoleValue> event) {
}
@Override
public void entryUpdated(EntryEvent<DeviceId, RoleValue> event) {
NodeId myId = clusterService.getLocalNode().id();
NodeId node = event.getValue().get(MASTER);
if (myId.equals(node)) {
// XXX or do we just let it get sent and caught by ourself?
return;
}
notifyDelegate(new MastershipEvent(
MASTER_CHANGED, event.getKey(), event.getValue().get(MASTER)));
}
@Override
protected void onAdd(DeviceId deviceId, NodeId nodeId) {
notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, nodeId));
public void entryEvicted(EntryEvent<DeviceId, RoleValue> event) {
}
@Override
protected void onRemove(DeviceId deviceId, NodeId nodeId) {
//notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, nodeId));
public void mapEvicted(MapEvent event) {
}
@Override
protected void onUpdate(DeviceId deviceId, NodeId oldNodeId, NodeId nodeId) {
//only addition indicates a change in mastership
//notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, nodeId));
public void mapCleared(MapEvent event) {
}
}
......
package org.onlab.onos.store.mastership.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.net.MastershipRole;
/**
* A structure that holds node mastership roles associated with a
* {@link DeviceId}. This structure needs to be locked through IMap.
*/
public class RoleValue {
protected Map<MastershipRole, List<NodeId>> value = new HashMap<>();
public RoleValue() {
value.put(MastershipRole.MASTER, new LinkedList<NodeId>());
value.put(MastershipRole.STANDBY, new LinkedList<NodeId>());
value.put(MastershipRole.NONE, new LinkedList<NodeId>());
}
public Map<MastershipRole, List<NodeId>> value() {
return Collections.unmodifiableMap(value);
}
public List<NodeId> nodesOfRole(MastershipRole type) {
return value.get(type);
}
public NodeId get(MastershipRole type) {
return value.get(type).isEmpty() ? null : value.get(type).get(0);
}
public boolean contains(MastershipRole type, NodeId nodeId) {
return value.get(type).contains(nodeId);
}
/**
* Associates a node to a certain role.
*
* @param type the role
* @param nodeId the node ID of the node to associate
*/
public void add(MastershipRole type, NodeId nodeId) {
List<NodeId> nodes = value.get(type);
if (!nodes.contains(nodeId)) {
nodes.add(nodeId);
}
}
/**
* Removes a node from a certain role.
*
* @param type the role
* @param nodeId the ID of the node to remove
* @return
*/
public boolean remove(MastershipRole type, NodeId nodeId) {
List<NodeId> nodes = value.get(type);
if (!nodes.isEmpty()) {
return nodes.remove(nodeId);
} else {
return false;
}
}
/**
* Reassigns a node from one role to another. If the node was not of the
* old role, it will still be assigned the new role.
*
* @param nodeId the Node ID of node changing roles
* @param from the old role
* @param to the new role
*/
// might want to add anyways as default behavior
public void reassign(NodeId nodeId, MastershipRole from, MastershipRole to) {
remove(from, nodeId);
add(to, nodeId);
}
/**
* Replaces a node in one role with another node. Even if there is no node to
* replace, the new node is associated to the role.
*
* @param from the old NodeId to replace
* @param to the new NodeId
* @param type the role associated with the old NodeId
*/
// might want to add anyways as default behavior
public void replace(NodeId from, NodeId to, MastershipRole type) {
remove(type, from);
add(type, to);
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
for (Map.Entry<MastershipRole, List<NodeId>> el : value.entrySet()) {
builder.append(el.getKey().toString()).append(": [");
for (NodeId n : el.getValue()) {
builder.append(n);
}
builder.append("]\n");
}
return builder.toString();
}
}
package org.onlab.onos.store.mastership.impl;
import java.util.List;
import java.util.Map;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.net.MastershipRole;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
/**
* Serializer for RoleValues used by {@link DistributedMastershipStore}.
*/
public class RoleValueSerializer extends Serializer<RoleValue> {
//RoleValues are assumed to hold a Map of MastershipRoles (an enum)
//to a List of NodeIds.
@Override
public RoleValue read(Kryo kryo, Input input, Class<RoleValue> type) {
RoleValue rv = new RoleValue();
int size = input.readInt();
for (int i = 0; i < size; i++) {
MastershipRole role = MastershipRole.values()[input.readInt()];
int s = input.readInt();
for (int j = 0; j < s; j++) {
rv.add(role, new NodeId(input.readString()));
}
}
return rv;
}
@Override
public void write(Kryo kryo, Output output, RoleValue type) {
output.writeInt(type.value().size());
for (Map.Entry<MastershipRole, List<NodeId>> el :
type.value().entrySet()) {
output.writeInt(el.getKey().ordinal());
List<NodeId> nodes = el.getValue();
output.writeInt(nodes.size());
for (NodeId n : nodes) {
output.writeString(n.toString());
}
}
}
}
......@@ -27,6 +27,7 @@ import org.onlab.onos.mastership.MastershipStoreDelegate;
import org.onlab.onos.mastership.MastershipTerm;
import org.onlab.onos.mastership.MastershipEvent.Type;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.MastershipRole;
import org.onlab.onos.store.common.StoreManager;
import org.onlab.onos.store.common.StoreService;
import org.onlab.onos.store.common.TestStoreManager;
......@@ -101,7 +102,7 @@ public class DistributedMastershipStoreTest {
@Test
public void getMaster() {
assertTrue("wrong store state:", dms.masters.isEmpty());
assertTrue("wrong store state:", dms.roleMap.isEmpty());
testStore.put(DID1, N1, true, false, false);
assertEquals("wrong master:", N1, dms.getMaster(DID1));
......@@ -110,12 +111,11 @@ public class DistributedMastershipStoreTest {
@Test
public void getDevices() {
assertTrue("wrong store state:", dms.masters.isEmpty());
assertTrue("wrong store state:", dms.roleMap.isEmpty());
testStore.put(DID1, N1, true, false, false);
testStore.put(DID2, N1, true, false, false);
testStore.put(DID3, N2, true, false, false);
assertEquals("wrong devices",
Sets.newHashSet(DID1, DID2), dms.getDevices(N1));
}
......@@ -161,7 +161,7 @@ public class DistributedMastershipStoreTest {
assertEquals("wrong event:", Type.MASTER_CHANGED, dms.setMaster(N2, DID2).type());
assertEquals("wrong term", MastershipTerm.of(N2, 0), dms.getTermFor(DID2));
//disconnect and reconnect - sign of failing re-election or single-instance channel
testStore.reset(true, false, false);
dms.roleMap.clear();
dms.setMaster(N2, DID2);
assertEquals("wrong term", MastershipTerm.of(N2, 1), dms.getTermFor(DID2));
}
......@@ -191,13 +191,15 @@ public class DistributedMastershipStoreTest {
assertEquals("wrong role for node:", NONE, dms.getRole(N2, DID1));
assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID1));
assertEquals("wrong number of retired nodes", 2, dms.unusable.size());
assertEquals("wrong number of retired nodes", 2,
dms.roleMap.get(DID1).nodesOfRole(NONE).size());
//bring nodes back
assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1));
testStore.setCurrent(CN1);
assertEquals("wrong role for NONE:", STANDBY, dms.requestRole(DID1));
assertEquals("wrong number of backup nodes", 1, dms.standbys.size());
assertEquals("wrong number of backup nodes", 1,
dms.roleMap.get(DID1).nodesOfRole(STANDBY).size());
//NONE - nothing happens
assertNull("wrong event:", dms.relinquishRole(N1, DID2));
......@@ -238,55 +240,44 @@ public class DistributedMastershipStoreTest {
//helper to populate master/backup structures
public void put(DeviceId dev, NodeId node,
boolean master, boolean backup, boolean term) {
byte [] n = serialize(node);
byte [] d = serialize(dev);
RoleValue rv = dms.roleMap.get(dev);
if (rv == null) {
rv = new RoleValue();
}
if (master) {
dms.masters.put(d, n);
dms.unusable.put(d, n);
dms.standbys.remove(d, n);
rv.add(MASTER, node);
rv.reassign(node, STANDBY, NONE);
}
if (backup) {
dms.standbys.put(d, n);
dms.masters.remove(d, n);
dms.unusable.remove(d, n);
rv.add(STANDBY, node);
rv.remove(MASTER, node);
rv.remove(NONE, node);
}
if (term) {
dms.terms.put(d, 0);
dms.terms.put(dev, 0);
}
dms.roleMap.put(dev, rv);
}
//a dumb utility function.
public void dump() {
System.out.println("standbys");
for (Map.Entry<byte [], byte []> e : standbys.entrySet()) {
System.out.println(deserialize(e.getKey()) + ":" + deserialize(e.getValue()));
}
System.out.println("unusable");
for (Map.Entry<byte [], byte []> e : unusable.entrySet()) {
System.out.println(deserialize(e.getKey()) + ":" + deserialize(e.getValue()));
}
for (Map.Entry<DeviceId, RoleValue> el : dms.roleMap.entrySet()) {
System.out.println("DID: " + el.getKey());
for (MastershipRole role : MastershipRole.values()) {
System.out.println("\t" + role.toString() + ":");
for (NodeId n : el.getValue().nodesOfRole(role)) {
System.out.println("\t\t" + n);
}
//clears structures
public void reset(boolean store, boolean backup, boolean term) {
if (store) {
dms.masters.clear();
dms.unusable.clear();
}
if (backup) {
dms.standbys.clear();
}
if (term) {
dms.terms.clear();
}
}
//increment term for a device
public void increment(DeviceId dev) {
Integer t = dms.terms.get(serialize(dev));
Integer t = dms.terms.get(dev);
if (t != null) {
dms.terms.put(serialize(dev), ++t);
dms.terms.put(dev, ++t);
}
}
......
package org.onlab.onos.store.mastership.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.onlab.onos.net.MastershipRole.*;
import org.junit.Test;
import org.onlab.onos.cluster.NodeId;
import com.google.common.collect.Sets;
public class RoleValueTest {
private static final RoleValue RV = new RoleValue();
private static final NodeId NID1 = new NodeId("node1");
private static final NodeId NID2 = new NodeId("node2");
private static final NodeId NID3 = new NodeId("node3");
@Test
public void add() {
assertEquals("faulty initialization: ", 3, RV.value.size());
RV.add(MASTER, NID1);
RV.add(STANDBY, NID2);
RV.add(STANDBY, NID3);
assertEquals("wrong nodeID: ", NID1, RV.get(MASTER));
assertTrue("wrong nodeIDs: ",
Sets.newHashSet(NID3, NID2).containsAll(RV.nodesOfRole(STANDBY)));
}
}
......@@ -19,34 +19,13 @@
<dependencies>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-api</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-core-serializers</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
......
package org.onlab.onos.store.device.impl;
import static com.google.common.base.Predicates.notNull;
import com.google.common.base.Optional;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.hazelcast.core.IMap;
import com.hazelcast.core.ISet;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.net.DefaultDevice;
import org.onlab.onos.net.DefaultPort;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.device.DeviceDescription;
import org.onlab.onos.net.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceStore;
import org.onlab.onos.net.device.DeviceStoreDelegate;
import org.onlab.onos.net.device.PortDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.AbsentInvalidatingLoadingCache;
import org.onlab.onos.store.common.AbstractHazelcastStore;
import org.onlab.onos.store.common.OptionalCacheLoader;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.cache.CacheBuilder.newBuilder;
import static org.onlab.onos.net.device.DeviceEvent.Type.*;
import static org.slf4j.LoggerFactory.getLogger;
//TODO: Add support for multiple provider and annotations
/**
* Manages inventory of infrastructure devices using Hazelcast-backed map.
*/
@Component(immediate = true)
@Service
public class DistributedDeviceStore
extends AbstractHazelcastStore<DeviceEvent, DeviceStoreDelegate>
implements DeviceStore {
private final Logger log = getLogger(getClass());
public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
// private IMap<DeviceId, DefaultDevice> cache;
private IMap<byte[], byte[]> rawDevices;
private LoadingCache<DeviceId, Optional<DefaultDevice>> devices;
// private ISet<DeviceId> availableDevices;
private ISet<byte[]> availableDevices;
// TODO DevicePorts is very inefficient consider restructuring.
// private IMap<DeviceId, Map<PortNumber, Port>> devicePorts;
private IMap<byte[], byte[]> rawDevicePorts;
private LoadingCache<DeviceId, Optional<Map<PortNumber, Port>>> devicePorts;
private String devicesListener;
private String portsListener;
@Override
@Activate
public void activate() {
super.activate();
// IMap event handler needs value
final boolean includeValue = true;
// TODO decide on Map name scheme to avoid collision
rawDevices = theInstance.getMap("devices");
final OptionalCacheLoader<DeviceId, DefaultDevice> deviceLoader
= new OptionalCacheLoader<>(serializer, rawDevices);
devices = new AbsentInvalidatingLoadingCache<>(newBuilder().build(deviceLoader));
// refresh/populate cache based on notification from other instance
devicesListener = rawDevices.addEntryListener(new RemoteDeviceEventHandler(devices), includeValue);
// TODO cache availableDevices
availableDevices = theInstance.getSet("availableDevices");
rawDevicePorts = theInstance.getMap("devicePorts");
final OptionalCacheLoader<DeviceId, Map<PortNumber, Port>> devicePortLoader
= new OptionalCacheLoader<>(serializer, rawDevicePorts);
devicePorts = new AbsentInvalidatingLoadingCache<>(newBuilder().build(devicePortLoader));
// refresh/populate cache based on notification from other instance
portsListener = rawDevicePorts.addEntryListener(new RemotePortEventHandler(devicePorts), includeValue);
loadDeviceCache();
loadDevicePortsCache();
log.info("Started");
}
@Deactivate
public void deactivate() {
rawDevicePorts.removeEntryListener(portsListener);
rawDevices.removeEntryListener(devicesListener);
log.info("Stopped");
}
@Override
public int getDeviceCount() {
return devices.asMap().size();
}
@Override
public Iterable<Device> getDevices() {
// TODO builder v.s. copyOf. Guava semms to be using copyOf?
Builder<Device> builder = ImmutableSet.builder();
for (Optional<DefaultDevice> e : devices.asMap().values()) {
if (e.isPresent()) {
builder.add(e.get());
}
}
return builder.build();
}
private void loadDeviceCache() {
for (byte[] keyBytes : rawDevices.keySet()) {
final DeviceId id = deserialize(keyBytes);
devices.refresh(id);
}
}
private void loadDevicePortsCache() {
for (byte[] keyBytes : rawDevicePorts.keySet()) {
final DeviceId id = deserialize(keyBytes);
devicePorts.refresh(id);
}
}
@Override
public Device getDevice(DeviceId deviceId) {
// TODO revisit if ignoring exception is safe.
return devices.getUnchecked(deviceId).orNull();
}
@Override
public DeviceEvent createOrUpdateDevice(ProviderId providerId, DeviceId deviceId,
DeviceDescription deviceDescription) {
DefaultDevice device = devices.getUnchecked(deviceId).orNull();
if (device == null) {
return createDevice(providerId, deviceId, deviceDescription);
}
return updateDevice(providerId, device, deviceDescription);
}
// Creates the device and returns the appropriate event if necessary.
private DeviceEvent createDevice(ProviderId providerId, DeviceId deviceId,
DeviceDescription desc) {
DefaultDevice device = new DefaultDevice(providerId, deviceId, desc.type(),
desc.manufacturer(),
desc.hwVersion(), desc.swVersion(),
desc.serialNumber());
synchronized (this) {
final byte[] deviceIdBytes = serialize(deviceId);
rawDevices.put(deviceIdBytes, serialize(device));
devices.put(deviceId, Optional.of(device));
availableDevices.add(deviceIdBytes);
}
return new DeviceEvent(DEVICE_ADDED, device, null);
}
// Updates the device and returns the appropriate event if necessary.
private DeviceEvent updateDevice(ProviderId providerId, DefaultDevice device,
DeviceDescription desc) {
// We allow only certain attributes to trigger update
if (!Objects.equals(device.hwVersion(), desc.hwVersion()) ||
!Objects.equals(device.swVersion(), desc.swVersion())) {
DefaultDevice updated = new DefaultDevice(providerId, device.id(),
desc.type(),
desc.manufacturer(),
desc.hwVersion(),
desc.swVersion(),
desc.serialNumber());
synchronized (this) {
final byte[] deviceIdBytes = serialize(device.id());
rawDevices.put(deviceIdBytes, serialize(updated));
devices.put(device.id(), Optional.of(updated));
availableDevices.add(serialize(device.id()));
}
return new DeviceEvent(DeviceEvent.Type.DEVICE_UPDATED, updated, null);
}
// Otherwise merely attempt to change availability
synchronized (this) {
boolean added = availableDevices.add(serialize(device.id()));
return !added ? null :
new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
}
}
@Override
public DeviceEvent markOffline(DeviceId deviceId) {
synchronized (this) {
Device device = devices.getUnchecked(deviceId).orNull();
boolean removed = device != null && availableDevices.remove(serialize(deviceId));
return !removed ? null :
new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device, null);
}
}
@Override
public List<DeviceEvent> updatePorts(ProviderId providerId, DeviceId deviceId,
List<PortDescription> portDescriptions) {
List<DeviceEvent> events = new ArrayList<>();
synchronized (this) {
Device device = devices.getUnchecked(deviceId).orNull();
checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
Map<PortNumber, Port> ports = getPortMap(deviceId);
// Add new ports
Set<PortNumber> processed = new HashSet<>();
for (PortDescription portDescription : portDescriptions) {
Port port = ports.get(portDescription.portNumber());
events.add(port == null ?
createPort(device, portDescription, ports) :
updatePort(device, port, portDescription, ports));
processed.add(portDescription.portNumber());
}
updatePortMap(deviceId, ports);
events.addAll(pruneOldPorts(device, ports, processed));
}
return FluentIterable.from(events).filter(notNull()).toList();
}
// Creates a new port based on the port description adds it to the map and
// Returns corresponding event.
//@GuardedBy("this")
private DeviceEvent createPort(Device device, PortDescription portDescription,
Map<PortNumber, Port> ports) {
DefaultPort port = new DefaultPort(device, portDescription.portNumber(),
portDescription.isEnabled());
ports.put(port.number(), port);
updatePortMap(device.id(), ports);
return new DeviceEvent(PORT_ADDED, device, port);
}
// Checks if the specified port requires update and if so, it replaces the
// existing entry in the map and returns corresponding event.
//@GuardedBy("this")
private DeviceEvent updatePort(Device device, Port port,
PortDescription portDescription,
Map<PortNumber, Port> ports) {
if (port.isEnabled() != portDescription.isEnabled()) {
DefaultPort updatedPort =
new DefaultPort(device, portDescription.portNumber(),
portDescription.isEnabled());
ports.put(port.number(), updatedPort);
updatePortMap(device.id(), ports);
return new DeviceEvent(PORT_UPDATED, device, updatedPort);
}
return null;
}
// Prunes the specified list of ports based on which ports are in the
// processed list and returns list of corresponding events.
//@GuardedBy("this")
private List<DeviceEvent> pruneOldPorts(Device device,
Map<PortNumber, Port> ports,
Set<PortNumber> processed) {
List<DeviceEvent> events = new ArrayList<>();
Iterator<PortNumber> iterator = ports.keySet().iterator();
while (iterator.hasNext()) {
PortNumber portNumber = iterator.next();
if (!processed.contains(portNumber)) {
events.add(new DeviceEvent(PORT_REMOVED, device,
ports.get(portNumber)));
iterator.remove();
}
}
if (!events.isEmpty()) {
updatePortMap(device.id(), ports);
}
return events;
}
// Gets the map of ports for the specified device; if one does not already
// exist, it creates and registers a new one.
// WARN: returned value is a copy, changes made to the Map
// needs to be written back using updatePortMap
//@GuardedBy("this")
private Map<PortNumber, Port> getPortMap(DeviceId deviceId) {
Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
if (ports == null) {
ports = new HashMap<>();
// this probably is waste of time in most cases.
updatePortMap(deviceId, ports);
}
return ports;
}
//@GuardedBy("this")
private void updatePortMap(DeviceId deviceId, Map<PortNumber, Port> ports) {
rawDevicePorts.put(serialize(deviceId), serialize(ports));
devicePorts.put(deviceId, Optional.of(ports));
}
@Override
public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
PortDescription portDescription) {
synchronized (this) {
Device device = devices.getUnchecked(deviceId).orNull();
checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
Map<PortNumber, Port> ports = getPortMap(deviceId);
Port port = ports.get(portDescription.portNumber());
return updatePort(device, port, portDescription, ports);
}
}
@Override
public List<Port> getPorts(DeviceId deviceId) {
Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
return ports == null ? Collections.<Port>emptyList() : ImmutableList.copyOf(ports.values());
}
@Override
public Port getPort(DeviceId deviceId, PortNumber portNumber) {
Map<PortNumber, Port> ports = devicePorts.getUnchecked(deviceId).orNull();
return ports == null ? null : ports.get(portNumber);
}
@Override
public boolean isAvailable(DeviceId deviceId) {
return availableDevices.contains(serialize(deviceId));
}
@Override
public DeviceEvent removeDevice(DeviceId deviceId) {
synchronized (this) {
byte[] deviceIdBytes = serialize(deviceId);
// TODO conditional remove?
Device device = deserialize(rawDevices.remove(deviceIdBytes));
devices.invalidate(deviceId);
return device == null ? null :
new DeviceEvent(DEVICE_REMOVED, device, null);
}
}
private class RemoteDeviceEventHandler extends RemoteCacheEventHandler<DeviceId, DefaultDevice> {
public RemoteDeviceEventHandler(LoadingCache<DeviceId, Optional<DefaultDevice>> cache) {
super(cache);
}
@Override
protected void onAdd(DeviceId deviceId, DefaultDevice device) {
notifyDelegate(new DeviceEvent(DEVICE_ADDED, device));
}
@Override
protected void onRemove(DeviceId deviceId, DefaultDevice device) {
notifyDelegate(new DeviceEvent(DEVICE_REMOVED, device));
}
@Override
protected void onUpdate(DeviceId deviceId, DefaultDevice oldDevice, DefaultDevice device) {
notifyDelegate(new DeviceEvent(DEVICE_UPDATED, device));
}
}
private class RemotePortEventHandler extends RemoteCacheEventHandler<DeviceId, Map<PortNumber, Port>> {
public RemotePortEventHandler(LoadingCache<DeviceId, Optional<Map<PortNumber, Port>>> cache) {
super(cache);
}
@Override
protected void onAdd(DeviceId deviceId, Map<PortNumber, Port> ports) {
// notifyDelegate(new DeviceEvent(PORT_ADDED, getDevice(deviceId)));
}
@Override
protected void onRemove(DeviceId deviceId, Map<PortNumber, Port> ports) {
// notifyDelegate(new DeviceEvent(PORT_REMOVED, getDevice(deviceId)));
}
@Override
protected void onUpdate(DeviceId deviceId, Map<PortNumber, Port> oldPorts, Map<PortNumber, Port> ports) {
// notifyDelegate(new DeviceEvent(PORT_UPDATED, getDevice(deviceId)));
}
}
// TODO cache serialized DeviceID if we suffer from serialization cost
}
package org.onlab.onos.store.device.impl;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.mastership.MastershipTerm;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.device.DeviceClockProviderService;
// FIXME: Code clone in onos-core-trivial, onos-core-hz-net
/**
* Dummy implementation of {@link DeviceClockProviderService}.
*/
@Component(immediate = true)
@Service
public class NoOpClockProviderService implements DeviceClockProviderService {
@Override
public void setMastershipTerm(DeviceId deviceId, MastershipTerm term) {
}
}
package org.onlab.onos.store.flow.impl;
import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.Collection;
import java.util.Collections;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.ApplicationId;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.flow.DefaultFlowEntry;
import org.onlab.onos.net.flow.FlowEntry;
import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
import org.onlab.onos.net.flow.FlowRule;
import org.onlab.onos.net.flow.FlowRuleEvent;
import org.onlab.onos.net.flow.FlowRuleEvent.Type;
import org.onlab.onos.net.flow.FlowRuleStore;
import org.onlab.onos.net.flow.FlowRuleStoreDelegate;
import org.onlab.onos.store.AbstractStore;
import org.slf4j.Logger;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
/**
* Manages inventory of flow rules using trivial in-memory implementation.
*/
//FIXME I LIE. I AIN'T DISTRIBUTED
@Component(immediate = true)
@Service
public class DistributedFlowRuleStore
extends AbstractStore<FlowRuleEvent, FlowRuleStoreDelegate>
implements FlowRuleStore {
private final Logger log = getLogger(getClass());
// store entries as a pile of rules, no info about device tables
private final Multimap<DeviceId, FlowEntry> flowEntries =
ArrayListMultimap.<DeviceId, FlowEntry>create();
private final Multimap<Short, FlowRule> flowEntriesById =
ArrayListMultimap.<Short, FlowRule>create();
@Activate
public void activate() {
log.info("Started");
}
@Deactivate
public void deactivate() {
log.info("Stopped");
}
@Override
public int getFlowRuleCount() {
return flowEntries.size();
}
@Override
public synchronized FlowEntry getFlowEntry(FlowRule rule) {
for (FlowEntry f : flowEntries.get(rule.deviceId())) {
if (f.equals(rule)) {
return f;
}
}
return null;
}
@Override
public synchronized Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
Collection<FlowEntry> rules = flowEntries.get(deviceId);
if (rules == null) {
return Collections.emptyList();
}
return ImmutableSet.copyOf(rules);
}
@Override
public synchronized Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId) {
Collection<FlowRule> rules = flowEntriesById.get(appId.id());
if (rules == null) {
return Collections.emptyList();
}
return ImmutableSet.copyOf(rules);
}
@Override
public synchronized void storeFlowRule(FlowRule rule) {
FlowEntry f = new DefaultFlowEntry(rule);
DeviceId did = f.deviceId();
if (!flowEntries.containsEntry(did, f)) {
flowEntries.put(did, f);
flowEntriesById.put(rule.appId(), f);
}
}
@Override
public synchronized void deleteFlowRule(FlowRule rule) {
FlowEntry entry = getFlowEntry(rule);
if (entry == null) {
return;
}
entry.setState(FlowEntryState.PENDING_REMOVE);
}
@Override
public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
DeviceId did = rule.deviceId();
// check if this new rule is an update to an existing entry
FlowEntry stored = getFlowEntry(rule);
if (stored != null) {
stored.setBytes(rule.bytes());
stored.setLife(rule.life());
stored.setPackets(rule.packets());
if (stored.state() == FlowEntryState.PENDING_ADD) {
stored.setState(FlowEntryState.ADDED);
return new FlowRuleEvent(Type.RULE_ADDED, rule);
}
return new FlowRuleEvent(Type.RULE_UPDATED, rule);
}
flowEntries.put(did, rule);
return null;
}
@Override
public synchronized FlowRuleEvent removeFlowRule(FlowEntry rule) {
// This is where one could mark a rule as removed and still keep it in the store.
if (flowEntries.remove(rule.deviceId(), rule)) {
return new FlowRuleEvent(RULE_REMOVED, rule);
} else {
return null;
}
}
}
package org.onlab.onos.store.host.impl;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.net.Annotations;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultHost;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Host;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.HostLocation;
import org.onlab.onos.net.host.HostDescription;
import org.onlab.onos.net.host.HostEvent;
import org.onlab.onos.net.host.HostStore;
import org.onlab.onos.net.host.HostStoreDelegate;
import org.onlab.onos.net.host.PortAddresses;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.AbstractStore;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.slf4j.Logger;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static org.onlab.onos.net.host.HostEvent.Type.*;
import static org.slf4j.LoggerFactory.getLogger;
/**
* TEMPORARY: Manages inventory of end-station hosts using distributed
* structures implementation.
*/
//FIXME: I LIE I AM NOT DISTRIBUTED
@Component(immediate = true)
@Service
public class DistributedHostStore
extends AbstractStore<HostEvent, HostStoreDelegate>
implements HostStore {
private final Logger log = getLogger(getClass());
// Host inventory
private final Map<HostId, StoredHost> hosts = new ConcurrentHashMap<>(2000000, 0.75f, 16);
// Hosts tracked by their location
private final Multimap<ConnectPoint, Host> locations = HashMultimap.create();
private final Map<ConnectPoint, PortAddresses> portAddresses =
new ConcurrentHashMap<>();
@Activate
public void activate() {
log.info("Started");
}
@Deactivate
public void deactivate() {
log.info("Stopped");
}
@Override
public HostEvent createOrUpdateHost(ProviderId providerId, HostId hostId,
HostDescription hostDescription) {
StoredHost host = hosts.get(hostId);
if (host == null) {
return createHost(providerId, hostId, hostDescription);
}
return updateHost(providerId, host, hostDescription);
}
// creates a new host and sends HOST_ADDED
private HostEvent createHost(ProviderId providerId, HostId hostId,
HostDescription descr) {
StoredHost newhost = new StoredHost(providerId, hostId,
descr.hwAddress(),
descr.vlan(),
descr.location(),
ImmutableSet.of(descr.ipAddress()));
synchronized (this) {
hosts.put(hostId, newhost);
locations.put(descr.location(), newhost);
}
return new HostEvent(HOST_ADDED, newhost);
}
// checks for type of update to host, sends appropriate event
private HostEvent updateHost(ProviderId providerId, StoredHost host,
HostDescription descr) {
HostEvent event;
if (!host.location().equals(descr.location())) {
host.setLocation(descr.location());
return new HostEvent(HOST_MOVED, host);
}
if (host.ipAddresses().contains(descr.ipAddress())) {
return null;
}
Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses());
addresses.add(descr.ipAddress());
StoredHost updated = new StoredHost(providerId, host.id(),
host.mac(), host.vlan(),
descr.location(), addresses);
event = new HostEvent(HOST_UPDATED, updated);
synchronized (this) {
hosts.put(host.id(), updated);
locations.remove(host.location(), host);
locations.put(updated.location(), updated);
}
return event;
}
@Override
public HostEvent removeHost(HostId hostId) {
synchronized (this) {
Host host = hosts.remove(hostId);
if (host != null) {
locations.remove((host.location()), host);
return new HostEvent(HOST_REMOVED, host);
}
return null;
}
}
@Override
public int getHostCount() {
return hosts.size();
}
@Override
public Iterable<Host> getHosts() {
return ImmutableSet.<Host>copyOf(hosts.values());
}
@Override
public Host getHost(HostId hostId) {
return hosts.get(hostId);
}
@Override
public Set<Host> getHosts(VlanId vlanId) {
Set<Host> vlanset = new HashSet<>();
for (Host h : hosts.values()) {
if (h.vlan().equals(vlanId)) {
vlanset.add(h);
}
}
return vlanset;
}
@Override
public Set<Host> getHosts(MacAddress mac) {
Set<Host> macset = new HashSet<>();
for (Host h : hosts.values()) {
if (h.mac().equals(mac)) {
macset.add(h);
}
}
return macset;
}
@Override
public Set<Host> getHosts(IpPrefix ip) {
Set<Host> ipset = new HashSet<>();
for (Host h : hosts.values()) {
if (h.ipAddresses().contains(ip)) {
ipset.add(h);
}
}
return ipset;
}
@Override
public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
return ImmutableSet.copyOf(locations.get(connectPoint));
}
@Override
public Set<Host> getConnectedHosts(DeviceId deviceId) {
Set<Host> hostset = new HashSet<>();
for (ConnectPoint p : locations.keySet()) {
if (p.deviceId().equals(deviceId)) {
hostset.addAll(locations.get(p));
}
}
return hostset;
}
@Override
public void updateAddressBindings(PortAddresses addresses) {
synchronized (portAddresses) {
PortAddresses existing = portAddresses.get(addresses.connectPoint());
if (existing == null) {
portAddresses.put(addresses.connectPoint(), addresses);
} else {
Set<IpPrefix> union = Sets.union(existing.ips(), addresses.ips())
.immutableCopy();
MacAddress newMac = (addresses.mac() == null) ? existing.mac()
: addresses.mac();
PortAddresses newAddresses =
new PortAddresses(addresses.connectPoint(), union, newMac);
portAddresses.put(newAddresses.connectPoint(), newAddresses);
}
}
}
@Override
public void removeAddressBindings(PortAddresses addresses) {
synchronized (portAddresses) {
PortAddresses existing = portAddresses.get(addresses.connectPoint());
if (existing != null) {
Set<IpPrefix> difference =
Sets.difference(existing.ips(), addresses.ips()).immutableCopy();
// If they removed the existing mac, set the new mac to null.
// Otherwise, keep the existing mac.
MacAddress newMac = existing.mac();
if (addresses.mac() != null && addresses.mac().equals(existing.mac())) {
newMac = null;
}
PortAddresses newAddresses =
new PortAddresses(addresses.connectPoint(), difference, newMac);
portAddresses.put(newAddresses.connectPoint(), newAddresses);
}
}
}
@Override
public void clearAddressBindings(ConnectPoint connectPoint) {
synchronized (portAddresses) {
portAddresses.remove(connectPoint);
}
}
@Override
public Set<PortAddresses> getAddressBindings() {
synchronized (portAddresses) {
return new HashSet<>(portAddresses.values());
}
}
@Override
public PortAddresses getAddressBindingsForPort(ConnectPoint connectPoint) {
PortAddresses addresses;
synchronized (portAddresses) {
addresses = portAddresses.get(connectPoint);
}
if (addresses == null) {
addresses = new PortAddresses(connectPoint, null, null);
}
return addresses;
}
// Auxiliary extension to allow location to mutate.
private class StoredHost extends DefaultHost {
private HostLocation location;
/**
* Creates an end-station host using the supplied information.
*
* @param providerId provider identity
* @param id host identifier
* @param mac host MAC address
* @param vlan host VLAN identifier
* @param location host location
* @param ips host IP addresses
* @param annotations optional key/value annotations
*/
public StoredHost(ProviderId providerId, HostId id,
MacAddress mac, VlanId vlan, HostLocation location,
Set<IpPrefix> ips, Annotations... annotations) {
super(providerId, id, mac, vlan, location, ips, annotations);
this.location = location;
}
void setLocation(HostLocation location) {
this.location = location;
}
@Override
public HostLocation location() {
return location;
}
}
}
package org.onlab.onos.store.link.impl;
import static com.google.common.cache.CacheBuilder.newBuilder;
import static org.onlab.onos.net.Link.Type.DIRECT;
import static org.onlab.onos.net.Link.Type.INDIRECT;
import static org.onlab.onos.net.LinkKey.linkKey;
import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
import static org.onlab.onos.net.link.LinkEvent.Type.LINK_UPDATED;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.HashSet;
import java.util.Set;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultLink;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.LinkKey;
import org.onlab.onos.net.link.LinkDescription;
import org.onlab.onos.net.link.LinkEvent;
import org.onlab.onos.net.link.LinkStore;
import org.onlab.onos.net.link.LinkStoreDelegate;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.AbsentInvalidatingLoadingCache;
import org.onlab.onos.store.common.AbstractHazelcastStore;
import org.onlab.onos.store.common.OptionalCacheLoader;
import org.slf4j.Logger;
import com.google.common.base.Optional;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.ImmutableSet.Builder;
import com.hazelcast.core.IMap;
//TODO: Add support for multiple provider and annotations
/**
* Manages inventory of infrastructure links using Hazelcast-backed map.
*/
@Component(immediate = true)
@Service
public class DistributedLinkStore
extends AbstractHazelcastStore<LinkEvent, LinkStoreDelegate>
implements LinkStore {
private final Logger log = getLogger(getClass());
// Link inventory
private IMap<byte[], byte[]> rawLinks;
private LoadingCache<LinkKey, Optional<DefaultLink>> links;
// TODO synchronize?
// Egress and ingress link sets
private final Multimap<DeviceId, Link> srcLinks = HashMultimap.create();
private final Multimap<DeviceId, Link> dstLinks = HashMultimap.create();
private String linksListener;
@Override
@Activate
public void activate() {
super.activate();
boolean includeValue = true;
// TODO decide on Map name scheme to avoid collision
rawLinks = theInstance.getMap("links");
final OptionalCacheLoader<LinkKey, DefaultLink> linkLoader
= new OptionalCacheLoader<>(serializer, rawLinks);
links = new AbsentInvalidatingLoadingCache<>(newBuilder().build(linkLoader));
// refresh/populate cache based on notification from other instance
linksListener = rawLinks.addEntryListener(new RemoteLinkEventHandler(links), includeValue);
loadLinkCache();
log.info("Started");
}
@Deactivate
public void deactivate() {
rawLinks.removeEntryListener(linksListener);
log.info("Stopped");
}
private void loadLinkCache() {
for (byte[] keyBytes : rawLinks.keySet()) {
final LinkKey id = deserialize(keyBytes);
links.refresh(id);
}
}
@Override
public int getLinkCount() {
return links.asMap().size();
}
@Override
public Iterable<Link> getLinks() {
Builder<Link> builder = ImmutableSet.builder();
for (Optional<DefaultLink> e : links.asMap().values()) {
if (e.isPresent()) {
builder.add(e.get());
}
}
return builder.build();
}
@Override
public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
return ImmutableSet.copyOf(srcLinks.get(deviceId));
}
@Override
public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
return ImmutableSet.copyOf(dstLinks.get(deviceId));
}
@Override
public Link getLink(ConnectPoint src, ConnectPoint dst) {
return links.getUnchecked(linkKey(src, dst)).orNull();
}
@Override
public Set<Link> getEgressLinks(ConnectPoint src) {
Set<Link> egress = new HashSet<>();
for (Link link : srcLinks.get(src.deviceId())) {
if (link.src().equals(src)) {
egress.add(link);
}
}
return egress;
}
@Override
public Set<Link> getIngressLinks(ConnectPoint dst) {
Set<Link> ingress = new HashSet<>();
for (Link link : dstLinks.get(dst.deviceId())) {
if (link.dst().equals(dst)) {
ingress.add(link);
}
}
return ingress;
}
@Override
public LinkEvent createOrUpdateLink(ProviderId providerId,
LinkDescription linkDescription) {
LinkKey key = linkKey(linkDescription.src(), linkDescription.dst());
Optional<DefaultLink> link = links.getUnchecked(key);
if (!link.isPresent()) {
return createLink(providerId, key, linkDescription);
}
return updateLink(providerId, link.get(), key, linkDescription);
}
// Creates and stores the link and returns the appropriate event.
private LinkEvent createLink(ProviderId providerId, LinkKey key,
LinkDescription linkDescription) {
DefaultLink link = new DefaultLink(providerId, key.src(), key.dst(),
linkDescription.type());
synchronized (this) {
final byte[] keyBytes = serialize(key);
rawLinks.put(keyBytes, serialize(link));
links.asMap().putIfAbsent(key, Optional.of(link));
addNewLink(link);
}
return new LinkEvent(LINK_ADDED, link);
}
// update Egress and ingress link sets
private void addNewLink(DefaultLink link) {
synchronized (this) {
srcLinks.put(link.src().deviceId(), link);
dstLinks.put(link.dst().deviceId(), link);
}
}
// Updates, if necessary the specified link and returns the appropriate event.
private LinkEvent updateLink(ProviderId providerId, DefaultLink link,
LinkKey key, LinkDescription linkDescription) {
// FIXME confirm Link update condition is OK
if (link.type() == INDIRECT && linkDescription.type() == DIRECT) {
synchronized (this) {
DefaultLink updated =
new DefaultLink(providerId, link.src(), link.dst(),
linkDescription.type());
final byte[] keyBytes = serialize(key);
rawLinks.put(keyBytes, serialize(updated));
links.asMap().replace(key, Optional.of(link), Optional.of(updated));
replaceLink(link, updated);
return new LinkEvent(LINK_UPDATED, updated);
}
}
return null;
}
// update Egress and ingress link sets
private void replaceLink(DefaultLink link, DefaultLink updated) {
synchronized (this) {
srcLinks.remove(link.src().deviceId(), link);
dstLinks.remove(link.dst().deviceId(), link);
srcLinks.put(link.src().deviceId(), updated);
dstLinks.put(link.dst().deviceId(), updated);
}
}
@Override
public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) {
synchronized (this) {
LinkKey key = linkKey(src, dst);
byte[] keyBytes = serialize(key);
Link link = deserialize(rawLinks.remove(keyBytes));
links.invalidate(key);
if (link != null) {
removeLink(link);
return new LinkEvent(LINK_REMOVED, link);
}
return null;
}
}
// update Egress and ingress link sets
private void removeLink(Link link) {
synchronized (this) {
srcLinks.remove(link.src().deviceId(), link);
dstLinks.remove(link.dst().deviceId(), link);
}
}
private class RemoteLinkEventHandler extends RemoteCacheEventHandler<LinkKey, DefaultLink> {
public RemoteLinkEventHandler(LoadingCache<LinkKey, Optional<DefaultLink>> cache) {
super(cache);
}
@Override
protected void onAdd(LinkKey key, DefaultLink newVal) {
addNewLink(newVal);
notifyDelegate(new LinkEvent(LINK_ADDED, newVal));
}
@Override
protected void onUpdate(LinkKey key, DefaultLink oldVal, DefaultLink newVal) {
replaceLink(oldVal, newVal);
notifyDelegate(new LinkEvent(LINK_UPDATED, newVal));
}
@Override
protected void onRemove(LinkKey key, DefaultLink val) {
removeLink(val);
notifyDelegate(new LinkEvent(LINK_REMOVED, val));
}
}
}
package org.onlab.onos.store.topology.impl;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import org.onlab.graph.DijkstraGraphSearch;
import org.onlab.graph.GraphPathSearch;
import org.onlab.graph.TarjanGraphSearch;
import org.onlab.onos.net.AbstractModel;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultPath;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.Path;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.net.topology.ClusterId;
import org.onlab.onos.net.topology.DefaultTopologyCluster;
import org.onlab.onos.net.topology.DefaultTopologyVertex;
import org.onlab.onos.net.topology.GraphDescription;
import org.onlab.onos.net.topology.LinkWeight;
import org.onlab.onos.net.topology.Topology;
import org.onlab.onos.net.topology.TopologyCluster;
import org.onlab.onos.net.topology.TopologyEdge;
import org.onlab.onos.net.topology.TopologyGraph;
import org.onlab.onos.net.topology.TopologyVertex;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.collect.ImmutableSetMultimap.Builder;
import static org.onlab.graph.GraphPathSearch.Result;
import static org.onlab.graph.TarjanGraphSearch.SCCResult;
import static org.onlab.onos.net.Link.Type.INDIRECT;
/**
* Default implementation of the topology descriptor. This carries the
* backing topology data.
*/
public class DefaultTopology extends AbstractModel implements Topology {
private static final DijkstraGraphSearch<TopologyVertex, TopologyEdge> DIJKSTRA =
new DijkstraGraphSearch<>();
private static final TarjanGraphSearch<TopologyVertex, TopologyEdge> TARJAN =
new TarjanGraphSearch<>();
private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.net");
private final long time;
private final TopologyGraph graph;
private final SCCResult<TopologyVertex, TopologyEdge> clusterResults;
private final ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>> results;
private final ImmutableSetMultimap<PathKey, Path> paths;
private final ImmutableMap<ClusterId, TopologyCluster> clusters;
private final ImmutableSet<ConnectPoint> infrastructurePoints;
private final ImmutableSetMultimap<ClusterId, ConnectPoint> broadcastSets;
private ImmutableMap<DeviceId, TopologyCluster> clustersByDevice;
private ImmutableSetMultimap<TopologyCluster, DeviceId> devicesByCluster;
private ImmutableSetMultimap<TopologyCluster, Link> linksByCluster;
/**
* Creates a topology descriptor attributed to the specified provider.
*
* @param providerId identity of the provider
* @param description data describing the new topology
*/
DefaultTopology(ProviderId providerId, GraphDescription description) {
super(providerId);
this.time = description.timestamp();
// Build the graph
this.graph = new DefaultTopologyGraph(description.vertexes(),
description.edges());
this.results = searchForShortestPaths();
this.paths = buildPaths();
this.clusterResults = searchForClusters();
this.clusters = buildTopologyClusters();
buildIndexes();
this.broadcastSets = buildBroadcastSets();
this.infrastructurePoints = findInfrastructurePoints();
}
@Override
public long time() {
return time;
}
@Override
public int clusterCount() {
return clusters.size();
}
@Override
public int deviceCount() {
return graph.getVertexes().size();
}
@Override
public int linkCount() {
return graph.getEdges().size();
}
@Override
public int pathCount() {
return paths.size();
}
/**
* Returns the backing topology graph.
*
* @return topology graph
*/
TopologyGraph getGraph() {
return graph;
}
/**
* Returns the set of topology clusters.
*
* @return set of clusters
*/
Set<TopologyCluster> getClusters() {
return ImmutableSet.copyOf(clusters.values());
}
/**
* Returns the specified topology cluster.
*
* @param clusterId cluster identifier
* @return topology cluster
*/
TopologyCluster getCluster(ClusterId clusterId) {
return clusters.get(clusterId);
}
/**
* Returns the topology cluster that contains the given device.
*
* @param deviceId device identifier
* @return topology cluster
*/
TopologyCluster getCluster(DeviceId deviceId) {
return clustersByDevice.get(deviceId);
}
/**
* Returns the set of cluster devices.
*
* @param cluster topology cluster
* @return cluster devices
*/
Set<DeviceId> getClusterDevices(TopologyCluster cluster) {
return devicesByCluster.get(cluster);
}
/**
* Returns the set of cluster links.
*
* @param cluster topology cluster
* @return cluster links
*/
Set<Link> getClusterLinks(TopologyCluster cluster) {
return linksByCluster.get(cluster);
}
/**
* Indicates whether the given point is an infrastructure link end-point.
*
* @param connectPoint connection point
* @return true if infrastructure
*/
boolean isInfrastructure(ConnectPoint connectPoint) {
return infrastructurePoints.contains(connectPoint);
}
/**
* Indicates whether the given point is part of a broadcast set.
*
* @param connectPoint connection point
* @return true if in broadcast set
*/
boolean isBroadcastPoint(ConnectPoint connectPoint) {
// Any non-infrastructure, i.e. edge points are assumed to be OK.
if (!isInfrastructure(connectPoint)) {
return true;
}
// Find the cluster to which the device belongs.
TopologyCluster cluster = clustersByDevice.get(connectPoint.deviceId());
if (cluster == null) {
throw new IllegalArgumentException("No cluster found for device " + connectPoint.deviceId());
}
// If the broadcast set is null or empty, or if the point explicitly
// belongs to it, return true;
Set<ConnectPoint> points = broadcastSets.get(cluster.id());
return points == null || points.isEmpty() || points.contains(connectPoint);
}
/**
* Returns the size of the cluster broadcast set.
*
* @param clusterId cluster identifier
* @return size of the cluster broadcast set
*/
int broadcastSetSize(ClusterId clusterId) {
return broadcastSets.get(clusterId).size();
}
/**
* Returns the set of pre-computed shortest paths between source and
* destination devices.
*
* @param src source device
* @param dst destination device
* @return set of shortest paths
*/
Set<Path> getPaths(DeviceId src, DeviceId dst) {
return paths.get(new PathKey(src, dst));
}
/**
* Computes on-demand the set of shortest paths between source and
* destination devices.
*
* @param src source device
* @param dst destination device
* @return set of shortest paths
*/
Set<Path> getPaths(DeviceId src, DeviceId dst, LinkWeight weight) {
GraphPathSearch.Result<TopologyVertex, TopologyEdge> result =
DIJKSTRA.search(graph, new DefaultTopologyVertex(src),
new DefaultTopologyVertex(dst), weight);
ImmutableSet.Builder<Path> builder = ImmutableSet.builder();
for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
builder.add(networkPath(path));
}
return builder.build();
}
// Searches the graph for all shortest paths and returns the search results.
private ImmutableMap<DeviceId, Result<TopologyVertex, TopologyEdge>> searchForShortestPaths() {
ImmutableMap.Builder<DeviceId, Result<TopologyVertex, TopologyEdge>> builder = ImmutableMap.builder();
// Search graph paths for each source to all destinations.
LinkWeight weight = new HopCountLinkWeight(graph.getVertexes().size());
for (TopologyVertex src : graph.getVertexes()) {
builder.put(src.deviceId(), DIJKSTRA.search(graph, src, null, weight));
}
return builder.build();
}
// Builds network paths from the graph path search results
private ImmutableSetMultimap<PathKey, Path> buildPaths() {
Builder<PathKey, Path> builder = ImmutableSetMultimap.builder();
for (DeviceId deviceId : results.keySet()) {
Result<TopologyVertex, TopologyEdge> result = results.get(deviceId);
for (org.onlab.graph.Path<TopologyVertex, TopologyEdge> path : result.paths()) {
builder.put(new PathKey(path.src().deviceId(), path.dst().deviceId()),
networkPath(path));
}
}
return builder.build();
}
// Converts graph path to a network path with the same cost.
private Path networkPath(org.onlab.graph.Path<TopologyVertex, TopologyEdge> path) {
List<Link> links = new ArrayList<>();
for (TopologyEdge edge : path.edges()) {
links.add(edge.link());
}
return new DefaultPath(PID, links, path.cost());
}
// Searches for SCC clusters in the network topology graph using Tarjan
// algorithm.
private SCCResult<TopologyVertex, TopologyEdge> searchForClusters() {
return TARJAN.search(graph, new NoIndirectLinksWeight());
}
// Builds the topology clusters and returns the id-cluster bindings.
private ImmutableMap<ClusterId, TopologyCluster> buildTopologyClusters() {
ImmutableMap.Builder<ClusterId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
SCCResult<TopologyVertex, TopologyEdge> result =
TARJAN.search(graph, new NoIndirectLinksWeight());
// Extract both vertexes and edges from the results; the lists form
// pairs along the same index.
List<Set<TopologyVertex>> clusterVertexes = result.clusterVertexes();
List<Set<TopologyEdge>> clusterEdges = result.clusterEdges();
// Scan over the lists and create a cluster from the results.
for (int i = 0, n = result.clusterCount(); i < n; i++) {
Set<TopologyVertex> vertexSet = clusterVertexes.get(i);
Set<TopologyEdge> edgeSet = clusterEdges.get(i);
ClusterId cid = ClusterId.clusterId(i);
DefaultTopologyCluster cluster =
new DefaultTopologyCluster(cid, vertexSet.size(), edgeSet.size(),
findRoot(vertexSet).deviceId());
clusterBuilder.put(cid, cluster);
}
return clusterBuilder.build();
}
// Finds the vertex whose device id is the lexicographical minimum in the
// specified set.
private TopologyVertex findRoot(Set<TopologyVertex> vertexSet) {
TopologyVertex minVertex = null;
for (TopologyVertex vertex : vertexSet) {
if (minVertex == null ||
minVertex.deviceId().toString()
.compareTo(minVertex.deviceId().toString()) < 0) {
minVertex = vertex;
}
}
return minVertex;
}
// Processes a map of broadcast sets for each cluster.
private ImmutableSetMultimap<ClusterId, ConnectPoint> buildBroadcastSets() {
Builder<ClusterId, ConnectPoint> builder = ImmutableSetMultimap.builder();
for (TopologyCluster cluster : clusters.values()) {
addClusterBroadcastSet(cluster, builder);
}
return builder.build();
}
// Finds all broadcast points for the cluster. These are those connection
// points which lie along the shortest paths between the cluster root and
// all other devices within the cluster.
private void addClusterBroadcastSet(TopologyCluster cluster,
Builder<ClusterId, ConnectPoint> builder) {
// Use the graph root search results to build the broadcast set.
Result<TopologyVertex, TopologyEdge> result = results.get(cluster.root());
for (Map.Entry<TopologyVertex, Set<TopologyEdge>> entry : result.parents().entrySet()) {
TopologyVertex vertex = entry.getKey();
// Ignore any parents that lead outside the cluster.
if (clustersByDevice.get(vertex.deviceId()) != cluster) {
continue;
}
// Ignore any back-link sets that are empty.
Set<TopologyEdge> parents = entry.getValue();
if (parents.isEmpty()) {
continue;
}
// Use the first back-link source and destinations to add to the
// broadcast set.
Link link = parents.iterator().next().link();
builder.put(cluster.id(), link.src());
builder.put(cluster.id(), link.dst());
}
}
// Collects and returns an set of all infrastructure link end-points.
private ImmutableSet<ConnectPoint> findInfrastructurePoints() {
ImmutableSet.Builder<ConnectPoint> builder = ImmutableSet.builder();
for (TopologyEdge edge : graph.getEdges()) {
builder.add(edge.link().src());
builder.add(edge.link().dst());
}
return builder.build();
}
// Builds cluster-devices, cluster-links and device-cluster indexes.
private void buildIndexes() {
// Prepare the index builders
ImmutableMap.Builder<DeviceId, TopologyCluster> clusterBuilder = ImmutableMap.builder();
ImmutableSetMultimap.Builder<TopologyCluster, DeviceId> devicesBuilder = ImmutableSetMultimap.builder();
ImmutableSetMultimap.Builder<TopologyCluster, Link> linksBuilder = ImmutableSetMultimap.builder();
// Now scan through all the clusters
for (TopologyCluster cluster : clusters.values()) {
int i = cluster.id().index();
// Scan through all the cluster vertexes.
for (TopologyVertex vertex : clusterResults.clusterVertexes().get(i)) {
devicesBuilder.put(cluster, vertex.deviceId());
clusterBuilder.put(vertex.deviceId(), cluster);
}
// Scan through all the cluster edges.
for (TopologyEdge edge : clusterResults.clusterEdges().get(i)) {
linksBuilder.put(cluster, edge.link());
}
}
// Finalize all indexes.
clustersByDevice = clusterBuilder.build();
devicesByCluster = devicesBuilder.build();
linksByCluster = linksBuilder.build();
}
// Link weight for measuring link cost as hop count with indirect links
// being as expensive as traversing the entire graph to assume the worst.
private static class HopCountLinkWeight implements LinkWeight {
private final int indirectLinkCost;
HopCountLinkWeight(int indirectLinkCost) {
this.indirectLinkCost = indirectLinkCost;
}
@Override
public double weight(TopologyEdge edge) {
// To force preference to use direct paths first, make indirect
// links as expensive as the linear vertex traversal.
return edge.link().type() == INDIRECT ? indirectLinkCost : 1;
}
}
// Link weight for preventing traversal over indirect links.
private static class NoIndirectLinksWeight implements LinkWeight {
@Override
public double weight(TopologyEdge edge) {
return edge.link().type() == INDIRECT ? -1 : 1;
}
}
@Override
public String toString() {
return toStringHelper(this)
.add("time", time)
.add("clusters", clusterCount())
.add("devices", deviceCount())
.add("links", linkCount())
.add("pathCount", pathCount())
.toString();
}
}
package org.onlab.onos.store.topology.impl;
import org.onlab.graph.AdjacencyListsGraph;
import org.onlab.onos.net.topology.TopologyEdge;
import org.onlab.onos.net.topology.TopologyGraph;
import org.onlab.onos.net.topology.TopologyVertex;
import java.util.Set;
/**
* Default implementation of an immutable topology graph based on a generic
* implementation of adjacency lists graph.
*/
public class DefaultTopologyGraph
extends AdjacencyListsGraph<TopologyVertex, TopologyEdge>
implements TopologyGraph {
/**
* Creates a topology graph comprising of the specified vertexes and edges.
*
* @param vertexes set of graph vertexes
* @param edges set of graph edges
*/
public DefaultTopologyGraph(Set<TopologyVertex> vertexes, Set<TopologyEdge> edges) {
super(vertexes, edges);
}
}
package org.onlab.onos.store.topology.impl;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.List;
import java.util.Set;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Service;
import org.onlab.onos.event.Event;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.Path;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.net.topology.ClusterId;
import org.onlab.onos.net.topology.GraphDescription;
import org.onlab.onos.net.topology.LinkWeight;
import org.onlab.onos.net.topology.Topology;
import org.onlab.onos.net.topology.TopologyCluster;
import org.onlab.onos.net.topology.TopologyEvent;
import org.onlab.onos.net.topology.TopologyGraph;
import org.onlab.onos.net.topology.TopologyStore;
import org.onlab.onos.net.topology.TopologyStoreDelegate;
import org.onlab.onos.store.AbstractStore;
import org.slf4j.Logger;
/**
* TEMPORARY: Manages inventory of topology snapshots using distributed
* structures implementation.
*/
//FIXME: I LIE I AM NOT DISTRIBUTED
@Component(immediate = true)
@Service
public class DistributedTopologyStore
extends AbstractStore<TopologyEvent, TopologyStoreDelegate>
implements TopologyStore {
private final Logger log = getLogger(getClass());
private volatile DefaultTopology current;
@Activate
public void activate() {
log.info("Started");
}
@Deactivate
public void deactivate() {
log.info("Stopped");
}
@Override
public Topology currentTopology() {
return current;
}
@Override
public boolean isLatest(Topology topology) {
// Topology is current only if it is the same as our current topology
return topology == current;
}
@Override
public TopologyGraph getGraph(Topology topology) {
return defaultTopology(topology).getGraph();
}
@Override
public Set<TopologyCluster> getClusters(Topology topology) {
return defaultTopology(topology).getClusters();
}
@Override
public TopologyCluster getCluster(Topology topology, ClusterId clusterId) {
return defaultTopology(topology).getCluster(clusterId);
}
@Override
public Set<DeviceId> getClusterDevices(Topology topology, TopologyCluster cluster) {
return defaultTopology(topology).getClusterDevices(cluster);
}
@Override
public Set<Link> getClusterLinks(Topology topology, TopologyCluster cluster) {
return defaultTopology(topology).getClusterLinks(cluster);
}
@Override
public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst) {
return defaultTopology(topology).getPaths(src, dst);
}
@Override
public Set<Path> getPaths(Topology topology, DeviceId src, DeviceId dst,
LinkWeight weight) {
return defaultTopology(topology).getPaths(src, dst, weight);
}
@Override
public boolean isInfrastructure(Topology topology, ConnectPoint connectPoint) {
return defaultTopology(topology).isInfrastructure(connectPoint);
}
@Override
public boolean isBroadcastPoint(Topology topology, ConnectPoint connectPoint) {
return defaultTopology(topology).isBroadcastPoint(connectPoint);
}
@Override
public TopologyEvent updateTopology(ProviderId providerId,
GraphDescription graphDescription,
List<Event> reasons) {
// First off, make sure that what we're given is indeed newer than
// what we already have.
if (current != null && graphDescription.timestamp() < current.time()) {
return null;
}
// Have the default topology construct self from the description data.
DefaultTopology newTopology =
new DefaultTopology(providerId, graphDescription);
// Promote the new topology to current and return a ready-to-send event.
synchronized (this) {
current = newTopology;
return new TopologyEvent(TopologyEvent.Type.TOPOLOGY_CHANGED,
current, reasons);
}
}
// Validates the specified topology and returns it as a default
private DefaultTopology defaultTopology(Topology topology) {
if (topology instanceof DefaultTopology) {
return (DefaultTopology) topology;
}
throw new IllegalArgumentException("Topology class " + topology.getClass() +
" not supported");
}
}
package org.onlab.onos.store.topology.impl;
import org.onlab.onos.net.DeviceId;
import java.util.Objects;
/**
* Key for filing pre-computed paths between source and destination devices.
*/
class PathKey {
private final DeviceId src;
private final DeviceId dst;
/**
* Creates a path key from the given source/dest pair.
* @param src source device
* @param dst destination device
*/
PathKey(DeviceId src, DeviceId dst) {
this.src = src;
this.dst = dst;
}
@Override
public int hashCode() {
return Objects.hash(src, dst);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof PathKey) {
final PathKey other = (PathKey) obj;
return Objects.equals(this.src, other.src) && Objects.equals(this.dst, other.dst);
}
return false;
}
}
/**
*
*/
package org.onlab.onos.store.device.impl;
import static org.junit.Assert.*;
import static org.onlab.onos.net.Device.Type.SWITCH;
import static org.onlab.onos.net.DeviceId.deviceId;
import static org.onlab.onos.net.device.DeviceEvent.Type.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.device.DefaultDeviceDescription;
import org.onlab.onos.net.device.DefaultPortDescription;
import org.onlab.onos.net.device.DeviceDescription;
import org.onlab.onos.net.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceStoreDelegate;
import org.onlab.onos.net.device.PortDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.StoreManager;
import org.onlab.onos.store.common.StoreService;
import org.onlab.onos.store.common.TestStoreManager;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
/**
* Test of the Hazelcast based distributed DeviceStore implementation.
*/
public class DistributedDeviceStoreTest {
private static final ProviderId PID = new ProviderId("of", "foo");
private static final DeviceId DID1 = deviceId("of:foo");
private static final DeviceId DID2 = deviceId("of:bar");
private static final String MFR = "whitebox";
private static final String HW = "1.1.x";
private static final String SW1 = "3.8.1";
private static final String SW2 = "3.9.5";
private static final String SN = "43311-12345";
private static final PortNumber P1 = PortNumber.portNumber(1);
private static final PortNumber P2 = PortNumber.portNumber(2);
private static final PortNumber P3 = PortNumber.portNumber(3);
private DistributedDeviceStore deviceStore;
private StoreManager storeManager;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
}
@Before
public void setUp() throws Exception {
// TODO should find a way to clean Hazelcast instance without shutdown.
Config config = TestStoreManager.getTestConfig();
storeManager = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
storeManager.activate();
deviceStore = new TestDistributedDeviceStore(storeManager);
deviceStore.activate();
}
@After
public void tearDown() throws Exception {
deviceStore.deactivate();
storeManager.deactivate();
}
private void putDevice(DeviceId deviceId, String swVersion) {
DeviceDescription description =
new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
HW, swVersion, SN);
deviceStore.createOrUpdateDevice(PID, deviceId, description);
}
private static void assertDevice(DeviceId id, String swVersion, Device device) {
assertNotNull(device);
assertEquals(id, device.id());
assertEquals(MFR, device.manufacturer());
assertEquals(HW, device.hwVersion());
assertEquals(swVersion, device.swVersion());
assertEquals(SN, device.serialNumber());
}
@Test
public final void testGetDeviceCount() {
assertEquals("initialy empty", 0, deviceStore.getDeviceCount());
putDevice(DID1, SW1);
putDevice(DID2, SW2);
putDevice(DID1, SW1);
assertEquals("expect 2 uniq devices", 2, deviceStore.getDeviceCount());
}
@Test
public final void testGetDevices() {
assertEquals("initialy empty", 0, Iterables.size(deviceStore.getDevices()));
putDevice(DID1, SW1);
putDevice(DID2, SW2);
putDevice(DID1, SW1);
assertEquals("expect 2 uniq devices",
2, Iterables.size(deviceStore.getDevices()));
Map<DeviceId, Device> devices = new HashMap<>();
for (Device device : deviceStore.getDevices()) {
devices.put(device.id(), device);
}
assertDevice(DID1, SW1, devices.get(DID1));
assertDevice(DID2, SW2, devices.get(DID2));
// add case for new node?
}
@Test
public final void testGetDevice() {
putDevice(DID1, SW1);
assertDevice(DID1, SW1, deviceStore.getDevice(DID1));
assertNull("DID2 shouldn't be there", deviceStore.getDevice(DID2));
}
@Test
public final void testCreateOrUpdateDevice() {
DeviceDescription description =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW1, SN);
DeviceEvent event = deviceStore.createOrUpdateDevice(PID, DID1, description);
assertEquals(DEVICE_ADDED, event.type());
assertDevice(DID1, SW1, event.subject());
DeviceDescription description2 =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW2, SN);
DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
assertEquals(DEVICE_UPDATED, event2.type());
assertDevice(DID1, SW2, event2.subject());
assertNull("No change expected", deviceStore.createOrUpdateDevice(PID, DID1, description2));
}
@Test
public final void testMarkOffline() {
putDevice(DID1, SW1);
assertTrue(deviceStore.isAvailable(DID1));
DeviceEvent event = deviceStore.markOffline(DID1);
assertEquals(DEVICE_AVAILABILITY_CHANGED, event.type());
assertDevice(DID1, SW1, event.subject());
assertFalse(deviceStore.isAvailable(DID1));
DeviceEvent event2 = deviceStore.markOffline(DID1);
assertNull("No change, no event", event2);
}
@Test
public final void testUpdatePorts() {
putDevice(DID1, SW1);
List<PortDescription> pds = Arrays.<PortDescription>asList(
new DefaultPortDescription(P1, true),
new DefaultPortDescription(P2, true)
);
List<DeviceEvent> events = deviceStore.updatePorts(PID, DID1, pds);
Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
for (DeviceEvent event : events) {
assertEquals(PORT_ADDED, event.type());
assertDevice(DID1, SW1, event.subject());
assertTrue("PortNumber is one of expected",
expectedPorts.remove(event.port().number()));
assertTrue("Port is enabled", event.port().isEnabled());
}
assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
List<PortDescription> pds2 = Arrays.<PortDescription>asList(
new DefaultPortDescription(P1, false),
new DefaultPortDescription(P2, true),
new DefaultPortDescription(P3, true)
);
events = deviceStore.updatePorts(PID, DID1, pds2);
assertFalse("event should be triggered", events.isEmpty());
for (DeviceEvent event : events) {
PortNumber num = event.port().number();
if (P1.equals(num)) {
assertEquals(PORT_UPDATED, event.type());
assertDevice(DID1, SW1, event.subject());
assertFalse("Port is disabled", event.port().isEnabled());
} else if (P2.equals(num)) {
fail("P2 event not expected.");
} else if (P3.equals(num)) {
assertEquals(PORT_ADDED, event.type());
assertDevice(DID1, SW1, event.subject());
assertTrue("Port is enabled", event.port().isEnabled());
} else {
fail("Unknown port number encountered: " + num);
}
}
List<PortDescription> pds3 = Arrays.<PortDescription>asList(
new DefaultPortDescription(P1, false),
new DefaultPortDescription(P2, true)
);
events = deviceStore.updatePorts(PID, DID1, pds3);
assertFalse("event should be triggered", events.isEmpty());
for (DeviceEvent event : events) {
PortNumber num = event.port().number();
if (P1.equals(num)) {
fail("P1 event not expected.");
} else if (P2.equals(num)) {
fail("P2 event not expected.");
} else if (P3.equals(num)) {
assertEquals(PORT_REMOVED, event.type());
assertDevice(DID1, SW1, event.subject());
assertTrue("Port was enabled", event.port().isEnabled());
} else {
fail("Unknown port number encountered: " + num);
}
}
}
@Test
public final void testUpdatePortStatus() {
putDevice(DID1, SW1);
List<PortDescription> pds = Arrays.<PortDescription>asList(
new DefaultPortDescription(P1, true)
);
deviceStore.updatePorts(PID, DID1, pds);
DeviceEvent event = deviceStore.updatePortStatus(PID, DID1,
new DefaultPortDescription(P1, false));
assertEquals(PORT_UPDATED, event.type());
assertDevice(DID1, SW1, event.subject());
assertEquals(P1, event.port().number());
assertFalse("Port is disabled", event.port().isEnabled());
}
@Test
public final void testGetPorts() {
putDevice(DID1, SW1);
putDevice(DID2, SW1);
List<PortDescription> pds = Arrays.<PortDescription>asList(
new DefaultPortDescription(P1, true),
new DefaultPortDescription(P2, true)
);
deviceStore.updatePorts(PID, DID1, pds);
Set<PortNumber> expectedPorts = Sets.newHashSet(P1, P2);
List<Port> ports = deviceStore.getPorts(DID1);
for (Port port : ports) {
assertTrue("Port is enabled", port.isEnabled());
assertTrue("PortNumber is one of expected",
expectedPorts.remove(port.number()));
}
assertTrue("Event for all expectedport appeared", expectedPorts.isEmpty());
assertTrue("DID2 has no ports", deviceStore.getPorts(DID2).isEmpty());
}
@Test
public final void testGetPort() {
putDevice(DID1, SW1);
putDevice(DID2, SW1);
List<PortDescription> pds = Arrays.<PortDescription>asList(
new DefaultPortDescription(P1, true),
new DefaultPortDescription(P2, false)
);
deviceStore.updatePorts(PID, DID1, pds);
Port port1 = deviceStore.getPort(DID1, P1);
assertEquals(P1, port1.number());
assertTrue("Port is enabled", port1.isEnabled());
Port port2 = deviceStore.getPort(DID1, P2);
assertEquals(P2, port2.number());
assertFalse("Port is disabled", port2.isEnabled());
Port port3 = deviceStore.getPort(DID1, P3);
assertNull("P3 not expected", port3);
}
@Test
public final void testRemoveDevice() {
putDevice(DID1, SW1);
putDevice(DID2, SW1);
assertEquals(2, deviceStore.getDeviceCount());
DeviceEvent event = deviceStore.removeDevice(DID1);
assertEquals(DEVICE_REMOVED, event.type());
assertDevice(DID1, SW1, event.subject());
assertEquals(1, deviceStore.getDeviceCount());
}
// TODO add test for Port events when we have them
@Ignore("Ignore until Delegate spec. is clear.")
@Test
public final void testEvents() throws InterruptedException {
final CountDownLatch addLatch = new CountDownLatch(1);
DeviceStoreDelegate checkAdd = new DeviceStoreDelegate() {
@Override
public void notify(DeviceEvent event) {
assertEquals(DEVICE_ADDED, event.type());
assertDevice(DID1, SW1, event.subject());
addLatch.countDown();
}
};
final CountDownLatch updateLatch = new CountDownLatch(1);
DeviceStoreDelegate checkUpdate = new DeviceStoreDelegate() {
@Override
public void notify(DeviceEvent event) {
assertEquals(DEVICE_UPDATED, event.type());
assertDevice(DID1, SW2, event.subject());
updateLatch.countDown();
}
};
final CountDownLatch removeLatch = new CountDownLatch(1);
DeviceStoreDelegate checkRemove = new DeviceStoreDelegate() {
@Override
public void notify(DeviceEvent event) {
assertEquals(DEVICE_REMOVED, event.type());
assertDevice(DID1, SW2, event.subject());
removeLatch.countDown();
}
};
DeviceDescription description =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW1, SN);
deviceStore.setDelegate(checkAdd);
deviceStore.createOrUpdateDevice(PID, DID1, description);
assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
DeviceDescription description2 =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW2, SN);
deviceStore.unsetDelegate(checkAdd);
deviceStore.setDelegate(checkUpdate);
deviceStore.createOrUpdateDevice(PID, DID1, description2);
assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
deviceStore.unsetDelegate(checkUpdate);
deviceStore.setDelegate(checkRemove);
deviceStore.removeDevice(DID1);
assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
}
private class TestDistributedDeviceStore extends DistributedDeviceStore {
public TestDistributedDeviceStore(StoreService storeService) {
this.storeService = storeService;
}
}
}
package org.onlab.onos.store.link.impl;
import static org.junit.Assert.*;
import static org.onlab.onos.net.DeviceId.deviceId;
import static org.onlab.onos.net.Link.Type.*;
import static org.onlab.onos.net.LinkKey.linkKey;
import static org.onlab.onos.net.link.LinkEvent.Type.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.LinkKey;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.Link.Type;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.link.LinkEvent;
import org.onlab.onos.net.link.LinkStoreDelegate;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.common.StoreManager;
import org.onlab.onos.store.common.StoreService;
import org.onlab.onos.store.common.TestStoreManager;
import com.google.common.collect.Iterables;
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
/**
* Test of the Hazelcast based distributed LinkStore implementation.
*/
public class DistributedLinkStoreTest {
private static final ProviderId PID = new ProviderId("of", "foo");
private static final DeviceId DID1 = deviceId("of:foo");
private static final DeviceId DID2 = deviceId("of:bar");
private static final PortNumber P1 = PortNumber.portNumber(1);
private static final PortNumber P2 = PortNumber.portNumber(2);
private static final PortNumber P3 = PortNumber.portNumber(3);
private StoreManager storeManager;
private DistributedLinkStore linkStore;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
}
@Before
public void setUp() throws Exception {
// TODO should find a way to clean Hazelcast instance without shutdown.
Config config = TestStoreManager.getTestConfig();
storeManager = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
storeManager.activate();
linkStore = new TestDistributedLinkStore(storeManager);
linkStore.activate();
}
@After
public void tearDown() throws Exception {
linkStore.deactivate();
storeManager.deactivate();
}
private void putLink(DeviceId srcId, PortNumber srcNum,
DeviceId dstId, PortNumber dstNum, Type type) {
ConnectPoint src = new ConnectPoint(srcId, srcNum);
ConnectPoint dst = new ConnectPoint(dstId, dstNum);
linkStore.createOrUpdateLink(PID, new DefaultLinkDescription(src, dst, type));
}
private void putLink(LinkKey key, Type type) {
putLink(key.src().deviceId(), key.src().port(),
key.dst().deviceId(), key.dst().port(),
type);
}
private static void assertLink(DeviceId srcId, PortNumber srcNum,
DeviceId dstId, PortNumber dstNum, Type type,
Link link) {
assertEquals(srcId, link.src().deviceId());
assertEquals(srcNum, link.src().port());
assertEquals(dstId, link.dst().deviceId());
assertEquals(dstNum, link.dst().port());
assertEquals(type, link.type());
}
private static void assertLink(LinkKey key, Type type, Link link) {
assertLink(key.src().deviceId(), key.src().port(),
key.dst().deviceId(), key.dst().port(),
type, link);
}
@Test
public final void testGetLinkCount() {
assertEquals("initialy empty", 0, linkStore.getLinkCount());
putLink(DID1, P1, DID2, P2, DIRECT);
putLink(DID2, P2, DID1, P1, DIRECT);
putLink(DID1, P1, DID2, P2, DIRECT);
assertEquals("expecting 2 unique link", 2, linkStore.getLinkCount());
}
@Test
public final void testGetLinks() {
assertEquals("initialy empty", 0,
Iterables.size(linkStore.getLinks()));
LinkKey linkId1 = linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
LinkKey linkId2 = linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
putLink(linkId1, DIRECT);
putLink(linkId2, DIRECT);
putLink(linkId1, DIRECT);
assertEquals("expecting 2 unique link", 2,
Iterables.size(linkStore.getLinks()));
Map<LinkKey, Link> links = new HashMap<>();
for (Link link : linkStore.getLinks()) {
links.put(linkKey(link), link);
}
assertLink(linkId1, DIRECT, links.get(linkId1));
assertLink(linkId2, DIRECT, links.get(linkId2));
}
@Test
public final void testGetDeviceEgressLinks() {
LinkKey linkId1 = linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
LinkKey linkId2 = linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
putLink(linkId1, DIRECT);
putLink(linkId2, DIRECT);
putLink(linkId3, DIRECT);
// DID1,P1 => DID2,P2
// DID2,P2 => DID1,P1
// DID1,P2 => DID2,P3
Set<Link> links1 = linkStore.getDeviceEgressLinks(DID1);
assertEquals(2, links1.size());
// check
Set<Link> links2 = linkStore.getDeviceEgressLinks(DID2);
assertEquals(1, links2.size());
assertLink(linkId2, DIRECT, links2.iterator().next());
}
@Test
public final void testGetDeviceIngressLinks() {
LinkKey linkId1 = linkKey(new ConnectPoint(DID1, P1), new ConnectPoint(DID2, P2));
LinkKey linkId2 = linkKey(new ConnectPoint(DID2, P2), new ConnectPoint(DID1, P1));
LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
putLink(linkId1, DIRECT);
putLink(linkId2, DIRECT);
putLink(linkId3, DIRECT);
// DID1,P1 => DID2,P2
// DID2,P2 => DID1,P1
// DID1,P2 => DID2,P3
Set<Link> links1 = linkStore.getDeviceIngressLinks(DID2);
assertEquals(2, links1.size());
// check
Set<Link> links2 = linkStore.getDeviceIngressLinks(DID1);
assertEquals(1, links2.size());
assertLink(linkId2, DIRECT, links2.iterator().next());
}
@Test
public final void testGetLink() {
ConnectPoint src = new ConnectPoint(DID1, P1);
ConnectPoint dst = new ConnectPoint(DID2, P2);
LinkKey linkId1 = linkKey(src, dst);
putLink(linkId1, DIRECT);
Link link = linkStore.getLink(src, dst);
assertLink(linkId1, DIRECT, link);
assertNull("There shouldn't be reverese link",
linkStore.getLink(dst, src));
}
@Test
public final void testGetEgressLinks() {
final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
LinkKey linkId1 = linkKey(d1P1, d2P2);
LinkKey linkId2 = linkKey(d2P2, d1P1);
LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
putLink(linkId1, DIRECT);
putLink(linkId2, DIRECT);
putLink(linkId3, DIRECT);
// DID1,P1 => DID2,P2
// DID2,P2 => DID1,P1
// DID1,P2 => DID2,P3
Set<Link> links1 = linkStore.getEgressLinks(d1P1);
assertEquals(1, links1.size());
assertLink(linkId1, DIRECT, links1.iterator().next());
Set<Link> links2 = linkStore.getEgressLinks(d2P2);
assertEquals(1, links2.size());
assertLink(linkId2, DIRECT, links2.iterator().next());
}
@Test
public final void testGetIngressLinks() {
final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
LinkKey linkId1 = linkKey(d1P1, d2P2);
LinkKey linkId2 = linkKey(d2P2, d1P1);
LinkKey linkId3 = linkKey(new ConnectPoint(DID1, P2), new ConnectPoint(DID2, P3));
putLink(linkId1, DIRECT);
putLink(linkId2, DIRECT);
putLink(linkId3, DIRECT);
// DID1,P1 => DID2,P2
// DID2,P2 => DID1,P1
// DID1,P2 => DID2,P3
Set<Link> links1 = linkStore.getIngressLinks(d2P2);
assertEquals(1, links1.size());
assertLink(linkId1, DIRECT, links1.iterator().next());
Set<Link> links2 = linkStore.getIngressLinks(d1P1);
assertEquals(1, links2.size());
assertLink(linkId2, DIRECT, links2.iterator().next());
}
@Test
public final void testCreateOrUpdateLink() {
ConnectPoint src = new ConnectPoint(DID1, P1);
ConnectPoint dst = new ConnectPoint(DID2, P2);
// add link
LinkEvent event = linkStore.createOrUpdateLink(PID,
new DefaultLinkDescription(src, dst, INDIRECT));
assertLink(DID1, P1, DID2, P2, INDIRECT, event.subject());
assertEquals(LINK_ADDED, event.type());
// update link type
LinkEvent event2 = linkStore.createOrUpdateLink(PID,
new DefaultLinkDescription(src, dst, DIRECT));
assertLink(DID1, P1, DID2, P2, DIRECT, event2.subject());
assertEquals(LINK_UPDATED, event2.type());
// no change
LinkEvent event3 = linkStore.createOrUpdateLink(PID,
new DefaultLinkDescription(src, dst, DIRECT));
assertNull("No change event expected", event3);
}
@Test
public final void testRemoveLink() {
final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
LinkKey linkId1 = linkKey(d1P1, d2P2);
LinkKey linkId2 = linkKey(d2P2, d1P1);
putLink(linkId1, DIRECT);
putLink(linkId2, DIRECT);
// DID1,P1 => DID2,P2
// DID2,P2 => DID1,P1
// DID1,P2 => DID2,P3
LinkEvent event = linkStore.removeLink(d1P1, d2P2);
assertEquals(LINK_REMOVED, event.type());
LinkEvent event2 = linkStore.removeLink(d1P1, d2P2);
assertNull(event2);
assertLink(linkId2, DIRECT, linkStore.getLink(d2P2, d1P1));
}
@Ignore("Ignore until Delegate spec. is clear.")
@Test
public final void testEvents() throws InterruptedException {
final ConnectPoint d1P1 = new ConnectPoint(DID1, P1);
final ConnectPoint d2P2 = new ConnectPoint(DID2, P2);
final LinkKey linkId1 = linkKey(d1P1, d2P2);
final CountDownLatch addLatch = new CountDownLatch(1);
LinkStoreDelegate checkAdd = new LinkStoreDelegate() {
@Override
public void notify(LinkEvent event) {
assertEquals(LINK_ADDED, event.type());
assertLink(linkId1, INDIRECT, event.subject());
addLatch.countDown();
}
};
final CountDownLatch updateLatch = new CountDownLatch(1);
LinkStoreDelegate checkUpdate = new LinkStoreDelegate() {
@Override
public void notify(LinkEvent event) {
assertEquals(LINK_UPDATED, event.type());
assertLink(linkId1, DIRECT, event.subject());
updateLatch.countDown();
}
};
final CountDownLatch removeLatch = new CountDownLatch(1);
LinkStoreDelegate checkRemove = new LinkStoreDelegate() {
@Override
public void notify(LinkEvent event) {
assertEquals(LINK_REMOVED, event.type());
assertLink(linkId1, DIRECT, event.subject());
removeLatch.countDown();
}
};
linkStore.setDelegate(checkAdd);
putLink(linkId1, INDIRECT);
assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
linkStore.unsetDelegate(checkAdd);
linkStore.setDelegate(checkUpdate);
putLink(linkId1, DIRECT);
assertTrue("Update event fired", updateLatch.await(1, TimeUnit.SECONDS));
linkStore.unsetDelegate(checkUpdate);
linkStore.setDelegate(checkRemove);
linkStore.removeLink(d1P1, d2P2);
assertTrue("Remove event fired", removeLatch.await(1, TimeUnit.SECONDS));
}
class TestDistributedLinkStore extends DistributedLinkStore {
TestDistributedLinkStore(StoreService storeService) {
this.storeService = storeService;
}
}
}
......@@ -17,35 +17,13 @@
<modules>
<module>common</module>
<module>cluster</module>
<module>net</module>
</modules>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-misc</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-junit</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
......@@ -23,20 +25,13 @@
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-misc</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-junit</artifactId>
<artifactId>onos-api</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
</dependencies>
......@@ -44,7 +39,7 @@
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<artifactId>maven-scr-plugin</artifactId>
</plugin>
</plugins>
</build>
......
......@@ -18,14 +18,6 @@
<dependencies>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
</dependency>
......@@ -36,13 +28,4 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
......
package org.onlab.onos.store.serializers;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.HostLocation;
import org.onlab.onos.net.PortNumber;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
/**
* Kryo Serializer for {@link HostLocation}.
*/
public class HostLocationSerializer extends Serializer<HostLocation> {
/**
* Creates {@link HostLocation} serializer instance.
*/
public HostLocationSerializer() {
// non-null, immutable
super(false, true);
}
@Override
public void write(Kryo kryo, Output output, HostLocation object) {
kryo.writeClassAndObject(output, object.deviceId());
kryo.writeClassAndObject(output, object.port());
output.writeLong(object.time());
}
@Override
public HostLocation read(Kryo kryo, Input input, Class<HostLocation> type) {
DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
PortNumber portNumber = (PortNumber) kryo.readClassAndObject(input);
long time = input.readLong();
return new HostLocation(deviceId, portNumber, time);
}
}
package org.onlab.onos.store.serializers;
import org.onlab.util.KryoPool.FamilySerializer;
import org.onlab.util.KryoNamespace.FamilySerializer;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
......
......@@ -4,7 +4,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.onlab.util.KryoPool.FamilySerializer;
import org.onlab.util.KryoNamespace.FamilySerializer;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
......
......@@ -3,7 +3,7 @@ package org.onlab.onos.store.serializers;
import java.util.ArrayList;
import java.util.List;
import org.onlab.util.KryoPool.FamilySerializer;
import org.onlab.util.KryoNamespace.FamilySerializer;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
......
......@@ -20,8 +20,7 @@ public class IpAddressSerializer extends Serializer<IpAddress> {
}
@Override
public void write(Kryo kryo, Output output,
IpAddress object) {
public void write(Kryo kryo, Output output, IpAddress object) {
byte[] octs = object.toOctets();
output.writeInt(octs.length);
output.writeBytes(octs);
......@@ -29,11 +28,10 @@ public class IpAddressSerializer extends Serializer<IpAddress> {
}
@Override
public IpAddress read(Kryo kryo, Input input,
Class<IpAddress> type) {
int octLen = input.readInt();
public IpAddress read(Kryo kryo, Input input, Class<IpAddress> type) {
final int octLen = input.readInt();
byte[] octs = new byte[octLen];
input.read(octs);
input.readBytes(octs);
int prefLen = input.readInt();
return IpAddress.valueOf(octs, prefLen);
}
......
......@@ -34,7 +34,7 @@ public final class IpPrefixSerializer extends Serializer<IpPrefix> {
Class<IpPrefix> type) {
int octLen = input.readInt();
byte[] octs = new byte[octLen];
input.read(octs);
input.readBytes(octs);
int prefLen = input.readInt();
return IpPrefix.valueOf(octs, prefLen);
}
......
......@@ -17,41 +17,52 @@ import org.onlab.onos.net.DefaultPort;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Element;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.HostLocation;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.LinkKey;
import org.onlab.onos.net.MastershipRole;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.device.DefaultDeviceDescription;
import org.onlab.onos.net.device.DefaultPortDescription;
import org.onlab.onos.net.flow.DefaultFlowRule;
import org.onlab.onos.net.host.DefaultHostDescription;
import org.onlab.onos.net.host.HostDescription;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.Timestamp;
import org.onlab.packet.ChassisId;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.util.KryoPool;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.KryoNamespace;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
public final class KryoPoolUtil {
public final class KryoNamespaces {
/**
* KryoPool which can serialize ON.lab misc classes.
* KryoNamespace which can serialize ON.lab misc classes.
*/
public static final KryoPool MISC = KryoPool.newBuilder()
public static final KryoNamespace MISC = KryoNamespace.newBuilder()
.register(IpPrefix.class, new IpPrefixSerializer())
.register(IpAddress.class, new IpAddressSerializer())
.register(MacAddress.class, new MacAddressSerializer())
.register(VlanId.class)
.build();
// TODO: Populate other classes
/**
* KryoPool which can serialize API bundle classes.
* KryoNamespace which can serialize API bundle classes.
*/
public static final KryoPool API = KryoPool.newBuilder()
public static final KryoNamespace API = KryoNamespace.newBuilder()
.register(MISC)
.register(ImmutableMap.class, new ImmutableMapSerializer())
.register(ImmutableList.class, new ImmutableListSerializer())
.register(ImmutableSet.class, new ImmutableSetSerializer())
.register(
//
ArrayList.class,
......@@ -61,18 +72,21 @@ public final class KryoPoolUtil {
//
ControllerNode.State.class,
Device.Type.class,
ChassisId.class,
DefaultAnnotations.class,
DefaultControllerNode.class,
DefaultDevice.class,
DefaultDeviceDescription.class,
DefaultLinkDescription.class,
MastershipRole.class,
Port.class,
DefaultPortDescription.class,
Element.class,
Link.Type.class,
Timestamp.class
Timestamp.class,
HostId.class,
HostDescription.class,
DefaultHostDescription.class,
DefaultFlowRule.class
)
.register(URI.class, new URISerializer())
.register(NodeId.class, new NodeIdSerializer())
......@@ -84,11 +98,11 @@ public final class KryoPoolUtil {
.register(ConnectPoint.class, new ConnectPointSerializer())
.register(DefaultLink.class, new DefaultLinkSerializer())
.register(MastershipTerm.class, new MastershipTermSerializer())
.register(MastershipRole.class, new MastershipRoleSerializer())
.register(HostLocation.class, new HostLocationSerializer())
.build();
// not to be instantiated
private KryoPoolUtil() {}
private KryoNamespaces() {}
}
......
package org.onlab.onos.store.serializers;
import org.onlab.util.KryoPool;
import org.onlab.util.KryoNamespace;
import java.nio.ByteBuffer;
/**
......@@ -8,7 +8,7 @@ import java.nio.ByteBuffer;
*/
public class KryoSerializer implements StoreSerializer {
protected KryoPool serializerPool;
protected KryoNamespace serializerPool;
public KryoSerializer() {
setupKryoPool();
......@@ -18,8 +18,8 @@ public class KryoSerializer implements StoreSerializer {
* Sets up the common serialzers pool.
*/
protected void setupKryoPool() {
serializerPool = KryoPool.newBuilder()
.register(KryoPoolUtil.API)
serializerPool = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
.build()
.populate(1);
}
......
package org.onlab.onos.store.serializers;
import org.onlab.onos.net.MastershipRole;
import org.onlab.packet.MacAddress;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
......@@ -8,27 +8,25 @@ import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
/**
* Kryo Serializer for {@link org.onlab.onos.net.MastershipRole}.
* Kryo Serializer for {@link MacAddress}.
*/
public class MastershipRoleSerializer extends Serializer<MastershipRole> {
public class MacAddressSerializer extends Serializer<MacAddress> {
/**
* Creates {@link MastershipRole} serializer instance.
* Creates {@link MacAddress} serializer instance.
*/
public MastershipRoleSerializer() {
// non-null, immutable
public MacAddressSerializer() {
super(false, true);
}
@Override
public MastershipRole read(Kryo kryo, Input input, Class<MastershipRole> type) {
final String role = kryo.readObject(input, String.class);
return MastershipRole.valueOf(role);
public void write(Kryo kryo, Output output, MacAddress object) {
output.writeBytes(object.getAddress());
}
@Override
public void write(Kryo kryo, Output output, MastershipRole object) {
kryo.writeObject(output, object.toString());
public MacAddress read(Kryo kryo, Input input, Class<MacAddress> type) {
return MacAddress.valueOf(input.readBytes(MacAddress.MAC_ADDRESS_LENGTH));
}
}
......
......@@ -20,16 +20,19 @@ import org.onlab.onos.net.DefaultLink;
import org.onlab.onos.net.DefaultPort;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.HostLocation;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.LinkKey;
import org.onlab.onos.net.MastershipRole;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.SparseAnnotations;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.packet.ChassisId;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.util.KryoPool;
import org.onlab.packet.MacAddress;
import org.onlab.util.KryoNamespace;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.testing.EqualsTester;
......@@ -49,7 +52,9 @@ public class KryoSerializerTest {
private static final String SW1 = "3.8.1";
private static final String SW2 = "3.9.5";
private static final String SN = "43311-12345";
private static final Device DEV1 = new DefaultDevice(PID, DID1, Device.Type.SWITCH, MFR, HW, SW1, SN);
private static final ChassisId CID = new ChassisId();
private static final Device DEV1 = new DefaultDevice(PID, DID1, Device.Type.SWITCH, MFR, HW,
SW1, SN, CID);
private static final SparseAnnotations A1 = DefaultAnnotations.builder()
.set("A1", "a1")
.set("B1", "b1")
......@@ -59,81 +64,147 @@ public class KryoSerializerTest {
.set("B3", "b3")
.build();
private static KryoPool kryos;
private KryoSerializer serializer;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
kryos = KryoPool.newBuilder()
.register(KryoPoolUtil.API)
.register(ImmutableMap.class, new ImmutableMapSerializer())
.register(ImmutableSet.class, new ImmutableSetSerializer())
.build();
}
@Before
public void setUp() throws Exception {
serializer = new KryoSerializer() {
@Override
protected void setupKryoPool() {
serializerPool = KryoNamespace.newBuilder()
.register(KryoNamespaces.API)
.build()
.populate(1);
}
};
}
@After
public void tearDown() throws Exception {
// removing Kryo instance to use fresh Kryo on each tests
kryos.getKryo();
}
private static <T> void testSerialized(T original) {
private <T> void testSerialized(T original) {
ByteBuffer buffer = ByteBuffer.allocate(1 * 1024 * 1024);
kryos.serialize(original, buffer);
serializer.encode(original, buffer);
buffer.flip();
T copy = kryos.deserialize(buffer);
T copy = serializer.decode(buffer);
T copy2 = serializer.decode(serializer.encode(original));
new EqualsTester()
.addEqualityGroup(original, copy)
.addEqualityGroup(original, copy, copy2)
.testEquals();
}
@Test
public final void testSerialization() {
public void testConnectPoint() {
testSerialized(new ConnectPoint(DID1, P1));
}
@Test
public void testDefaultLink() {
testSerialized(new DefaultLink(PID, CP1, CP2, Link.Type.DIRECT));
testSerialized(new DefaultPort(DEV1, P1, true));
testSerialized(new DefaultLink(PID, CP1, CP2, Link.Type.DIRECT, A1));
}
@Test
public void testDefaultPort() {
testSerialized(new DefaultPort(DEV1, P1, true));
testSerialized(new DefaultPort(DEV1, P1, true, A1_2));
}
@Test
public void testDeviceId() {
testSerialized(DID1);
}
@Test
public void testImmutableMap() {
testSerialized(ImmutableMap.of(DID1, DEV1, DID2, DEV1));
testSerialized(ImmutableMap.of(DID1, DEV1));
testSerialized(ImmutableMap.of());
}
@Test
public void testImmutableSet() {
testSerialized(ImmutableSet.of(DID1, DID2));
testSerialized(ImmutableSet.of(DID1));
testSerialized(ImmutableSet.of());
}
@Test
public void testImmutableList() {
testSerialized(ImmutableList.of(DID1, DID2));
testSerialized(ImmutableList.of(DID1));
testSerialized(ImmutableList.of());
}
@Test
public void testIpPrefix() {
testSerialized(IpPrefix.valueOf("192.168.0.1/24"));
}
@Test
public void testIpAddress() {
testSerialized(IpAddress.valueOf("192.168.0.1"));
}
@Test
public void testMacAddress() {
testSerialized(MacAddress.valueOf("12:34:56:78:90:ab"));
}
@Test
public void testLinkKey() {
testSerialized(LinkKey.linkKey(CP1, CP2));
}
@Test
public void testNodeId() {
testSerialized(new NodeId("SomeNodeIdentifier"));
}
@Test
public void testPortNumber() {
testSerialized(P1);
}
@Test
public void testProviderId() {
testSerialized(PID);
testSerialized(PIDA);
testSerialized(new NodeId("bar"));
}
@Test
public void testMastershipTerm() {
testSerialized(MastershipTerm.of(new NodeId("foo"), 2));
for (MastershipRole role : MastershipRole.values()) {
testSerialized(role);
}
@Test
public void testHostLocation() {
testSerialized(new HostLocation(CP1, 1234L));
}
@Test
public final void testAnnotations() {
public void testAnnotations() {
// Annotations does not have equals defined, manually test equality
final byte[] a1Bytes = kryos.serialize(A1);
SparseAnnotations copiedA1 = kryos.deserialize(a1Bytes);
final byte[] a1Bytes = serializer.encode(A1);
SparseAnnotations copiedA1 = serializer.decode(a1Bytes);
assertAnnotationsEquals(copiedA1, A1);
final byte[] a12Bytes = kryos.serialize(A1_2);
SparseAnnotations copiedA12 = kryos.deserialize(a12Bytes);
final byte[] a12Bytes = serializer.encode(A1_2);
SparseAnnotations copiedA12 = serializer.decode(a12Bytes);
assertAnnotationsEquals(copiedA12, A1_2);
}
// code clone
public static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
protected static void assertAnnotationsEquals(Annotations actual, SparseAnnotations... annotations) {
SparseAnnotations expected = DefaultAnnotations.builder().build();
for (SparseAnnotations a : annotations) {
expected = DefaultAnnotations.union(expected, a);
......
......@@ -18,26 +18,9 @@
<dependencies>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-scr-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
......
......@@ -5,8 +5,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.concurrent.ConcurrentException;
import org.apache.commons.lang3.concurrent.ConcurrentInitializer;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
......@@ -30,11 +28,13 @@ import org.onlab.onos.net.device.DeviceStoreDelegate;
import org.onlab.onos.net.device.PortDescription;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.AbstractStore;
import org.onlab.packet.ChassisId;
import org.onlab.util.NewConcurrentHashMap;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
......@@ -71,8 +71,7 @@ public class SimpleDeviceStore
public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
// collection of Description given from various providers
private final ConcurrentMap<DeviceId,
ConcurrentMap<ProviderId, DeviceDescriptions>>
private final ConcurrentMap<DeviceId, Map<ProviderId, DeviceDescriptions>>
deviceDescs = Maps.newConcurrentMap();
// cache of Device and Ports generated by compositing descriptions from providers
......@@ -117,15 +116,16 @@ public class SimpleDeviceStore
DeviceId deviceId,
DeviceDescription deviceDescription) {
ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs
= getDeviceDescriptions(deviceId);
Map<ProviderId, DeviceDescriptions> providerDescs
= getOrCreateDeviceDescriptions(deviceId);
synchronized (providerDescs) {
// locking per device
DeviceDescriptions descs
= createIfAbsentUnchecked(providerDescs, providerId,
new InitDeviceDescs(deviceDescription));
= getOrCreateProviderDeviceDescriptions(providerDescs,
providerId,
deviceDescription);
Device oldDevice = devices.get(deviceId);
// update description
......@@ -192,8 +192,8 @@ public class SimpleDeviceStore
@Override
public DeviceEvent markOffline(DeviceId deviceId) {
ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs
= getDeviceDescriptions(deviceId);
Map<ProviderId, DeviceDescriptions> providerDescs
= getOrCreateDeviceDescriptions(deviceId);
// locking device
synchronized (providerDescs) {
......@@ -218,7 +218,7 @@ public class SimpleDeviceStore
Device device = devices.get(deviceId);
checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
Map<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
List<DeviceEvent> events = new ArrayList<>();
......@@ -287,12 +287,12 @@ public class SimpleDeviceStore
Map<PortNumber, Port> ports,
Set<PortNumber> processed) {
List<DeviceEvent> events = new ArrayList<>();
Iterator<PortNumber> iterator = ports.keySet().iterator();
Iterator<Entry<PortNumber, Port>> iterator = ports.entrySet().iterator();
while (iterator.hasNext()) {
PortNumber portNumber = iterator.next();
Entry<PortNumber, Port> e = iterator.next();
PortNumber portNumber = e.getKey();
if (!processed.contains(portNumber)) {
events.add(new DeviceEvent(PORT_REMOVED, device,
ports.get(portNumber)));
events.add(new DeviceEvent(PORT_REMOVED, device, e.getValue()));
iterator.remove();
}
}
......@@ -306,10 +306,36 @@ public class SimpleDeviceStore
NewConcurrentHashMap.<PortNumber, Port>ifNeeded());
}
private ConcurrentMap<ProviderId, DeviceDescriptions> getDeviceDescriptions(
private Map<ProviderId, DeviceDescriptions> getOrCreateDeviceDescriptions(
DeviceId deviceId) {
return createIfAbsentUnchecked(deviceDescs, deviceId,
NewConcurrentHashMap.<ProviderId, DeviceDescriptions>ifNeeded());
Map<ProviderId, DeviceDescriptions> r;
r = deviceDescs.get(deviceId);
if (r != null) {
return r;
}
r = new HashMap<>();
final Map<ProviderId, DeviceDescriptions> concurrentlyAdded;
concurrentlyAdded = deviceDescs.putIfAbsent(deviceId, r);
if (concurrentlyAdded != null) {
return concurrentlyAdded;
} else {
return r;
}
}
// Guarded by deviceDescs value (=Device lock)
private DeviceDescriptions getOrCreateProviderDeviceDescriptions(
Map<ProviderId, DeviceDescriptions> device,
ProviderId providerId, DeviceDescription deltaDesc) {
synchronized (device) {
DeviceDescriptions r = device.get(providerId);
if (r == null) {
r = new DeviceDescriptions(deltaDesc);
device.put(providerId, r);
}
return r;
}
}
@Override
......@@ -318,12 +344,12 @@ public class SimpleDeviceStore
Device device = devices.get(deviceId);
checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
ConcurrentMap<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
Map<ProviderId, DeviceDescriptions> descsMap = deviceDescs.get(deviceId);
checkArgument(descsMap != null, DEVICE_NOT_FOUND, deviceId);
synchronized (descsMap) {
DeviceDescriptions descs = descsMap.get(providerId);
// assuming all providers must to give DeviceDescription
// assuming all providers must give DeviceDescription first
checkArgument(descs != null,
"Device description for Device ID %s from Provider %s was not found",
deviceId, providerId);
......@@ -367,7 +393,7 @@ public class SimpleDeviceStore
@Override
public DeviceEvent removeDevice(DeviceId deviceId) {
ConcurrentMap<ProviderId, DeviceDescriptions> descs = getDeviceDescriptions(deviceId);
Map<ProviderId, DeviceDescriptions> descs = getOrCreateDeviceDescriptions(deviceId);
synchronized (descs) {
Device device = devices.remove(deviceId);
// should DEVICE_REMOVED carry removed ports?
......@@ -390,7 +416,7 @@ public class SimpleDeviceStore
* @return Device instance
*/
private Device composeDevice(DeviceId deviceId,
ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
Map<ProviderId, DeviceDescriptions> providerDescs) {
checkArgument(!providerDescs.isEmpty(), "No Device descriptions supplied");
......@@ -404,6 +430,7 @@ public class SimpleDeviceStore
String hwVersion = base.hwVersion();
String swVersion = base.swVersion();
String serialNumber = base.serialNumber();
ChassisId chassisId = base.chassisId();
DefaultAnnotations annotations = DefaultAnnotations.builder().build();
annotations = merge(annotations, base.annotations());
......@@ -421,7 +448,8 @@ public class SimpleDeviceStore
}
return new DefaultDevice(primary, deviceId , type, manufacturer,
hwVersion, swVersion, serialNumber, annotations);
hwVersion, swVersion, serialNumber,
chassisId, annotations);
}
/**
......@@ -429,14 +457,14 @@ public class SimpleDeviceStore
*
* @param device device the port is on
* @param number port number
* @param providerDescs Collection of Descriptions from multiple providers
* @param descsMap Collection of Descriptions from multiple providers
* @return Port instance
*/
private Port composePort(Device device, PortNumber number,
ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
Map<ProviderId, DeviceDescriptions> descsMap) {
ProviderId primary = pickPrimaryPID(providerDescs);
DeviceDescriptions primDescs = providerDescs.get(primary);
ProviderId primary = pickPrimaryPID(descsMap);
DeviceDescriptions primDescs = descsMap.get(primary);
// if no primary, assume not enabled
// TODO: revisit this default port enabled/disabled behavior
boolean isEnabled = false;
......@@ -448,7 +476,7 @@ public class SimpleDeviceStore
annotations = merge(annotations, portDesc.annotations());
}
for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
for (Entry<ProviderId, DeviceDescriptions> e : descsMap.entrySet()) {
if (e.getKey().equals(primary)) {
continue;
}
......@@ -470,10 +498,9 @@ public class SimpleDeviceStore
/**
* @return primary ProviderID, or randomly chosen one if none exists
*/
private ProviderId pickPrimaryPID(
ConcurrentMap<ProviderId, DeviceDescriptions> providerDescs) {
private ProviderId pickPrimaryPID(Map<ProviderId, DeviceDescriptions> descsMap) {
ProviderId fallBackPrimary = null;
for (Entry<ProviderId, DeviceDescriptions> e : providerDescs.entrySet()) {
for (Entry<ProviderId, DeviceDescriptions> e : descsMap.entrySet()) {
if (!e.getKey().isAncillary()) {
return e.getKey();
} else if (fallBackPrimary == null) {
......@@ -484,21 +511,6 @@ public class SimpleDeviceStore
return fallBackPrimary;
}
public static final class InitDeviceDescs
implements ConcurrentInitializer<DeviceDescriptions> {
private final DeviceDescription deviceDesc;
public InitDeviceDescs(DeviceDescription deviceDesc) {
this.deviceDesc = checkNotNull(deviceDesc);
}
@Override
public DeviceDescriptions get() throws ConcurrentException {
return new DeviceDescriptions(deviceDesc);
}
}
/**
* Collection of Description of a Device and it's Ports given from a Provider.
*/
......
......@@ -2,9 +2,14 @@ package org.onlab.onos.store.trivial.impl;
import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
import static org.slf4j.LoggerFactory.getLogger;
import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
import static java.util.Collections.unmodifiableCollection;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
......@@ -15,18 +20,17 @@ import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.flow.DefaultFlowEntry;
import org.onlab.onos.net.flow.FlowEntry;
import org.onlab.onos.net.flow.FlowEntry.FlowEntryState;
import org.onlab.onos.net.flow.FlowId;
import org.onlab.onos.net.flow.FlowRule;
import org.onlab.onos.net.flow.FlowRuleEvent;
import org.onlab.onos.net.flow.FlowRuleEvent.Type;
import org.onlab.onos.net.flow.FlowRuleStore;
import org.onlab.onos.net.flow.FlowRuleStoreDelegate;
import org.onlab.onos.net.flow.StoredFlowEntry;
import org.onlab.onos.store.AbstractStore;
import org.onlab.util.NewConcurrentHashMap;
import org.slf4j.Logger;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
/**
* Manages inventory of flow rules using trivial in-memory implementation.
*/
......@@ -38,12 +42,11 @@ public class SimpleFlowRuleStore
private final Logger log = getLogger(getClass());
// store entries as a pile of rules, no info about device tables
private final Multimap<DeviceId, FlowEntry> flowEntries =
ArrayListMultimap.<DeviceId, FlowEntry>create();
private final Multimap<Short, FlowRule> flowEntriesById =
ArrayListMultimap.<Short, FlowRule>create();
// inner Map is Device flow table
// Assumption: FlowId cannot have synonyms
private final ConcurrentMap<DeviceId, ConcurrentMap<FlowId, StoredFlowEntry>>
flowEntries = new ConcurrentHashMap<>();
@Activate
public void activate() {
......@@ -52,88 +55,130 @@ public class SimpleFlowRuleStore
@Deactivate
public void deactivate() {
flowEntries.clear();
log.info("Stopped");
}
@Override
public int getFlowRuleCount() {
return flowEntries.size();
int sum = 0;
for (ConcurrentMap<FlowId, StoredFlowEntry> ft : flowEntries.values()) {
sum += ft.size();
}
return sum;
}
@Override
public synchronized FlowEntry getFlowEntry(FlowRule rule) {
for (FlowEntry f : flowEntries.get(rule.deviceId())) {
if (f.equals(rule)) {
return f;
private static NewConcurrentHashMap<FlowId, StoredFlowEntry> lazyEmptyFlowTable() {
return NewConcurrentHashMap.<FlowId, StoredFlowEntry>ifNeeded();
}
/**
* Returns the flow table for specified device.
*
* @param deviceId identifier of the device
* @return Map representing Flow Table of given device.
*/
private ConcurrentMap<FlowId, StoredFlowEntry> getFlowTable(DeviceId deviceId) {
return createIfAbsentUnchecked(flowEntries,
deviceId, lazyEmptyFlowTable());
}
return null;
private StoredFlowEntry getFlowEntry(DeviceId deviceId, FlowId flowId) {
return getFlowTable(deviceId).get(flowId);
}
@Override
public synchronized Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
Collection<FlowEntry> rules = flowEntries.get(deviceId);
if (rules == null) {
return Collections.emptyList();
public FlowEntry getFlowEntry(FlowRule rule) {
return getFlowEntry(rule.deviceId(), rule.id());
}
return ImmutableSet.copyOf(rules);
@Override
public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
return unmodifiableCollection((Collection<? extends FlowEntry>)
getFlowTable(deviceId).values());
}
@Override
public synchronized Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId) {
Collection<FlowRule> rules = flowEntriesById.get(appId.id());
if (rules == null) {
return Collections.emptyList();
public Iterable<FlowRule> getFlowRulesByAppId(ApplicationId appId) {
Set<FlowRule> rules = new HashSet<>();
for (DeviceId did : flowEntries.keySet()) {
ConcurrentMap<FlowId, StoredFlowEntry> ft = getFlowTable(did);
for (FlowEntry fe : ft.values()) {
if (fe.appId() == appId.id()) {
rules.add(fe);
}
return ImmutableSet.copyOf(rules);
}
}
return rules;
}
@Override
public synchronized void storeFlowRule(FlowRule rule) {
FlowEntry f = new DefaultFlowEntry(rule);
DeviceId did = f.deviceId();
if (!flowEntries.containsEntry(did, f)) {
flowEntries.put(did, f);
flowEntriesById.put(rule.appId(), f);
public void storeFlowRule(FlowRule rule) {
final boolean added = storeFlowRuleInternal(rule);
}
private boolean storeFlowRuleInternal(FlowRule rule) {
StoredFlowEntry f = new DefaultFlowEntry(rule);
final DeviceId did = f.deviceId();
final FlowId fid = f.id();
FlowEntry existing = getFlowTable(did).putIfAbsent(fid, f);
if (existing != null) {
// was already there? ignore
return false;
}
// new flow rule added
// TODO: notify through delegate about remote event?
return true;
}
@Override
public synchronized void deleteFlowRule(FlowRule rule) {
FlowEntry entry = getFlowEntry(rule);
public void deleteFlowRule(FlowRule rule) {
StoredFlowEntry entry = getFlowEntry(rule.deviceId(), rule.id());
if (entry == null) {
//log.warn("Cannot find rule {}", rule);
return;
}
synchronized (entry) {
entry.setState(FlowEntryState.PENDING_REMOVE);
}
}
@Override
public synchronized FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
DeviceId did = rule.deviceId();
public FlowRuleEvent addOrUpdateFlowRule(FlowEntry rule) {
// check if this new rule is an update to an existing entry
FlowEntry stored = getFlowEntry(rule);
StoredFlowEntry stored = getFlowEntry(rule.deviceId(), rule.id());
if (stored != null) {
synchronized (stored) {
stored.setBytes(rule.bytes());
stored.setLife(rule.life());
stored.setPackets(rule.packets());
if (stored.state() == FlowEntryState.PENDING_ADD) {
stored.setState(FlowEntryState.ADDED);
// TODO: Do we need to change `rule` state?
return new FlowRuleEvent(Type.RULE_ADDED, rule);
}
return new FlowRuleEvent(Type.RULE_UPDATED, rule);
}
}
// should not reach here
// storeFlowRule was expected to be called
log.error("FlowRule was not found in store {} to update", rule);
//flowEntries.put(did, rule);
return null;
}
@Override
public synchronized FlowRuleEvent removeFlowRule(FlowEntry rule) {
public FlowRuleEvent removeFlowRule(FlowEntry rule) {
// This is where one could mark a rule as removed and still keep it in the store.
if (flowEntries.remove(rule.deviceId(), rule)) {
final DeviceId did = rule.deviceId();
ConcurrentMap<FlowId, StoredFlowEntry> ft = getFlowTable(did);
if (ft.remove(rule.id(), rule)) {
return new FlowRuleEvent(RULE_REMOVED, rule);
} else {
return null;
......
......@@ -84,7 +84,7 @@ public class SimpleHostStore
descr.hwAddress(),
descr.vlan(),
descr.location(),
ImmutableSet.of(descr.ipAddress()));
ImmutableSet.copyOf(descr.ipAddress()));
synchronized (this) {
hosts.put(hostId, newhost);
locations.put(descr.location(), newhost);
......@@ -101,12 +101,12 @@ public class SimpleHostStore
return new HostEvent(HOST_MOVED, host);
}
if (host.ipAddresses().contains(descr.ipAddress())) {
if (host.ipAddresses().containsAll(descr.ipAddress())) {
return null;
}
Set<IpPrefix> addresses = new HashSet<>(host.ipAddresses());
addresses.add(descr.ipAddress());
addresses.addAll(descr.ipAddress());
StoredHost updated = new StoredHost(providerId, host.id(),
host.mac(), host.vlan(),
descr.location(), addresses);
......
package org.onlab.onos.store.trivial.impl;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import org.apache.commons.lang3.concurrent.ConcurrentUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
......@@ -20,7 +18,6 @@ import org.onlab.onos.net.Link;
import org.onlab.onos.net.SparseAnnotations;
import org.onlab.onos.net.Link.Type;
import org.onlab.onos.net.LinkKey;
import org.onlab.onos.net.Provided;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.link.LinkDescription;
import org.onlab.onos.net.link.LinkEvent;
......@@ -28,11 +25,12 @@ import org.onlab.onos.net.link.LinkStore;
import org.onlab.onos.net.link.LinkStoreDelegate;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.AbstractStore;
import org.onlab.util.NewConcurrentHashMap;
import org.slf4j.Logger;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
......@@ -47,6 +45,7 @@ import static org.onlab.onos.net.link.LinkEvent.Type.*;
import static org.slf4j.LoggerFactory.getLogger;
import static com.google.common.collect.Multimaps.synchronizedSetMultimap;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.base.Verify.verifyNotNull;
/**
* Manages inventory of infrastructure links using trivial in-memory structures
......@@ -61,8 +60,7 @@ public class SimpleLinkStore
private final Logger log = getLogger(getClass());
// Link inventory
private final ConcurrentMap<LinkKey,
ConcurrentMap<ProviderId, LinkDescription>>
private final ConcurrentMap<LinkKey, Map<ProviderId, LinkDescription>>
linkDescs = new ConcurrentHashMap<>();
// Link instance cache
......@@ -151,7 +149,7 @@ public class SimpleLinkStore
LinkDescription linkDescription) {
LinkKey key = linkKey(linkDescription.src(), linkDescription.dst());
ConcurrentMap<ProviderId, LinkDescription> descs = getLinkDescriptions(key);
Map<ProviderId, LinkDescription> descs = getOrCreateLinkDescriptions(key);
synchronized (descs) {
final Link oldLink = links.get(key);
// update description
......@@ -166,7 +164,7 @@ public class SimpleLinkStore
// Guarded by linkDescs value (=locking each Link)
private LinkDescription createOrUpdateLinkDescription(
ConcurrentMap<ProviderId, LinkDescription> descs,
Map<ProviderId, LinkDescription> descs,
ProviderId providerId,
LinkDescription linkDescription) {
......@@ -227,7 +225,7 @@ public class SimpleLinkStore
@Override
public LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) {
final LinkKey key = linkKey(src, dst);
ConcurrentMap<ProviderId, LinkDescription> descs = getLinkDescriptions(key);
Map<ProviderId, LinkDescription> descs = getOrCreateLinkDescriptions(key);
synchronized (descs) {
Link link = links.remove(key);
descs.clear();
......@@ -247,8 +245,8 @@ public class SimpleLinkStore
/**
* @return primary ProviderID, or randomly chosen one if none exists
*/
private ProviderId pickPrimaryPID(
ConcurrentMap<ProviderId, LinkDescription> providerDescs) {
// Guarded by linkDescs value (=locking each Link)
private ProviderId getBaseProviderId(Map<ProviderId, LinkDescription> providerDescs) {
ProviderId fallBackPrimary = null;
for (Entry<ProviderId, LinkDescription> e : providerDescs.entrySet()) {
......@@ -262,9 +260,10 @@ public class SimpleLinkStore
return fallBackPrimary;
}
private Link composeLink(ConcurrentMap<ProviderId, LinkDescription> descs) {
ProviderId primary = pickPrimaryPID(descs);
LinkDescription base = descs.get(primary);
// Guarded by linkDescs value (=locking each Link)
private Link composeLink(Map<ProviderId, LinkDescription> descs) {
ProviderId primary = getBaseProviderId(descs);
LinkDescription base = descs.get(verifyNotNull(primary));
ConnectPoint src = base.src();
ConnectPoint dst = base.dst();
......@@ -289,9 +288,20 @@ public class SimpleLinkStore
return new DefaultLink(primary , src, dst, type, annotations);
}
private ConcurrentMap<ProviderId, LinkDescription> getLinkDescriptions(LinkKey key) {
return ConcurrentUtils.createIfAbsentUnchecked(linkDescs, key,
NewConcurrentHashMap.<ProviderId, LinkDescription>ifNeeded());
private Map<ProviderId, LinkDescription> getOrCreateLinkDescriptions(LinkKey key) {
Map<ProviderId, LinkDescription> r;
r = linkDescs.get(key);
if (r != null) {
return r;
}
r = new HashMap<>();
final Map<ProviderId, LinkDescription> concurrentlyAdded;
concurrentlyAdded = linkDescs.putIfAbsent(key, r);
if (concurrentlyAdded == null) {
return r;
} else {
return concurrentlyAdded;
}
}
private final Function<LinkKey, Link> lookupLink = new LookupLink();
......@@ -302,20 +312,11 @@ public class SimpleLinkStore
private final class LookupLink implements Function<LinkKey, Link> {
@Override
public Link apply(LinkKey input) {
if (input == null) {
return null;
} else {
return links.get(input);
}
}
private static final Predicate<Provided> IS_PRIMARY = new IsPrimary();
private static final Predicate<Provided> isPrimary() {
return IS_PRIMARY;
}
private static final class IsPrimary implements Predicate<Provided> {
@Override
public boolean apply(Provided input) {
return !input.providerId().isAncillary();
}
}
}
......
......@@ -2,9 +2,11 @@ package org.onlab.onos.store.trivial.impl;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
......@@ -95,6 +97,18 @@ public class SimpleMastershipStore
}
@Override
public List<NodeId> getNodes(DeviceId deviceId) {
List<NodeId> nodes = new ArrayList<>();
nodes.addAll(backups);
if (!nodes.contains(masterMap.get(deviceId))) {
nodes.add(masterMap.get(deviceId));
}
return Collections.unmodifiableList(nodes);
}
@Override
public Set<DeviceId> getDevices(NodeId nodeId) {
Set<DeviceId> ids = new HashSet<>();
for (Map.Entry<DeviceId, NodeId> d : masterMap.entrySet()) {
......
......@@ -17,6 +17,7 @@ import org.onlab.onos.net.topology.GraphDescription;
import org.onlab.onos.net.topology.LinkWeight;
import org.onlab.onos.net.topology.TopologyCluster;
import org.onlab.onos.net.topology.TopologyEdge;
import org.onlab.packet.ChassisId;
import java.util.Set;
......@@ -119,7 +120,7 @@ public class DefaultTopologyTest {
// Crates a new device with the specified id
public static Device device(String id) {
return new DefaultDevice(PID, did(id), Device.Type.SWITCH,
"mfg", "1.0", "1.1", "1234");
"mfg", "1.0", "1.1", "1234", new ChassisId());
}
// Short-hand for producing a device id from a string
......
......@@ -40,6 +40,7 @@ import org.onlab.onos.net.provider.ProviderId;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.onlab.packet.ChassisId;
/**
* Test of the simple DeviceStore implementation.
......@@ -55,6 +56,7 @@ public class SimpleDeviceStoreTest {
private static final String SW1 = "3.8.1";
private static final String SW2 = "3.9.5";
private static final String SN = "43311-12345";
private static final ChassisId CID = new ChassisId();
private static final PortNumber P1 = PortNumber.portNumber(1);
private static final PortNumber P2 = PortNumber.portNumber(2);
......@@ -107,7 +109,7 @@ public class SimpleDeviceStoreTest {
SparseAnnotations... annotations) {
DeviceDescription description =
new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
HW, swVersion, SN, annotations);
HW, swVersion, SN, CID, annotations);
deviceStore.createOrUpdateDevice(PID, deviceId, description);
}
......@@ -115,7 +117,7 @@ public class SimpleDeviceStoreTest {
SparseAnnotations... annotations) {
DeviceDescription description =
new DefaultDeviceDescription(deviceId.uri(), SWITCH, MFR,
HW, swVersion, SN, annotations);
HW, swVersion, SN, CID, annotations);
deviceStore.createOrUpdateDevice(PIDA, deviceId, description);
}
......@@ -193,14 +195,14 @@ public class SimpleDeviceStoreTest {
public final void testCreateOrUpdateDevice() {
DeviceDescription description =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW1, SN);
HW, SW1, SN, CID);
DeviceEvent event = deviceStore.createOrUpdateDevice(PID, DID1, description);
assertEquals(DEVICE_ADDED, event.type());
assertDevice(DID1, SW1, event.subject());
DeviceDescription description2 =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW2, SN);
HW, SW2, SN, CID);
DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
assertEquals(DEVICE_UPDATED, event2.type());
assertDevice(DID1, SW2, event2.subject());
......@@ -212,7 +214,7 @@ public class SimpleDeviceStoreTest {
public final void testCreateOrUpdateDeviceAncillary() {
DeviceDescription description =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW1, SN, A2);
HW, SW1, SN, CID, A2);
DeviceEvent event = deviceStore.createOrUpdateDevice(PIDA, DID1, description);
assertEquals(DEVICE_ADDED, event.type());
assertDevice(DID1, SW1, event.subject());
......@@ -222,7 +224,7 @@ public class SimpleDeviceStoreTest {
DeviceDescription description2 =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW2, SN, A1);
HW, SW2, SN, CID, A1);
DeviceEvent event2 = deviceStore.createOrUpdateDevice(PID, DID1, description2);
assertEquals(DEVICE_UPDATED, event2.type());
assertDevice(DID1, SW2, event2.subject());
......@@ -238,7 +240,7 @@ public class SimpleDeviceStoreTest {
// But, Ancillary annotations will be in effect
DeviceDescription description3 =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW1, SN, A2_2);
HW, SW1, SN, CID, A2_2);
DeviceEvent event3 = deviceStore.createOrUpdateDevice(PIDA, DID1, description3);
assertEquals(DEVICE_UPDATED, event3.type());
// basic information will be the one from Primary
......@@ -508,7 +510,7 @@ public class SimpleDeviceStoreTest {
DeviceDescription description =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW1, SN);
HW, SW1, SN, CID);
deviceStore.setDelegate(checkAdd);
deviceStore.createOrUpdateDevice(PID, DID1, description);
assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
......@@ -516,7 +518,7 @@ public class SimpleDeviceStoreTest {
DeviceDescription description2 =
new DefaultDeviceDescription(DID1.uri(), SWITCH, MFR,
HW, SW2, SN);
HW, SW2, SN, CID);
deviceStore.unsetDelegate(checkAdd);
deviceStore.setDelegate(checkUpdate);
deviceStore.createOrUpdateDevice(PID, DID1, description2);
......
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
name="onos-1.0.0">
<repository>mvn:org.onlab.onos/onos-features/1.0.0-SNAPSHOT/xml/features</repository>
<repository>mvn:org.onlab.onos/onos-features/1.0.0-SNAPSHOT/xml/features
</repository>
<feature name="onos-thirdparty-base" version="1.0.0"
description="ONOS 3rd party dependencies">
......@@ -30,13 +31,15 @@
<bundle>mvn:org.codehaus.jackson/jackson-core-asl/1.9.13</bundle>
<bundle>mvn:org.codehaus.jackson/jackson-mapper-asl/1.9.13</bundle>
<bundle>mvn:org.onlab.onos/onlab-thirdparty/1.0.0-SNAPSHOT</bundle>
</feature>
<feature name="onos-thirdparty-web" version="1.0.0"
description="ONOS 3rd party dependencies">
<feature>war</feature>
<bundle>mvn:com.fasterxml.jackson.core/jackson-core/2.4.2</bundle>
<bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/2.4.2</bundle>
<bundle>mvn:com.fasterxml.jackson.core/jackson-annotations/2.4.2
</bundle>
<bundle>mvn:com.fasterxml.jackson.core/jackson-databind/2.4.2</bundle>
<bundle>mvn:com.sun.jersey/jersey-core/1.18.1</bundle>
<bundle>mvn:com.sun.jersey/jersey-server/1.18.1</bundle>
......@@ -101,9 +104,9 @@
<bundle>mvn:org.onlab.onos/onos-of-api/1.0.0-SNAPSHOT</bundle>
<bundle>mvn:org.onlab.onos/onos-of-ctl/1.0.0-SNAPSHOT</bundle>
<bundle>mvn:org.onlab.onos/onos-lldp-provider/1.0.0-SNAPSHOT</bundle>
<bundle>mvn:org.onlab.onos/onos-host-provider/1.0.0-SNAPSHOT</bundle>
<bundle>mvn:org.onlab.onos/onos-of-provider-device/1.0.0-SNAPSHOT</bundle>
<bundle>mvn:org.onlab.onos/onos-of-provider-link/1.0.0-SNAPSHOT</bundle>
<bundle>mvn:org.onlab.onos/onos-of-provider-host/1.0.0-SNAPSHOT</bundle>
<bundle>mvn:org.onlab.onos/onos-of-provider-packet/1.0.0-SNAPSHOT</bundle>
<bundle>mvn:org.onlab.onos/onos-of-provider-flow/1.0.0-SNAPSHOT</bundle>
......@@ -153,10 +156,24 @@
<bundle>mvn:org.onlab.onos/onos-app-config/1.0.0-SNAPSHOT</bundle>
</feature>
<feature name="onos-app-optical" version="1.0.0"
description="ONOS optical network config">
<feature>onos-api</feature>
<bundle>mvn:org.onlab.onos/onos-app-optical/1.0.0-SNAPSHOT</bundle>
</feature>
<feature name="onos-app-sdnip" version="1.0.0"
description="SDN-IP peering application">
<feature>onos-api</feature>
<bundle>mvn:org.onlab.onos/onos-app-sdnip/1.0.0-SNAPSHOT</bundle>
</feature>
<feature name="onos-app-calendar" version="1.0.0"
description="REST interface for scheduling intents from an external calendar">
<feature>onos-api</feature>
<feature>onos-thirdparty-web</feature>
<bundle>mvn:org.onlab.onos/onos-app-calendar/1.0.0-SNAPSHOT</bundle>
</feature>
</features>
......
......@@ -107,6 +107,12 @@
</dependency>
<dependency>
<groupId>com.googlecode.concurrent-trees</groupId>
<artifactId>concurrent-trees</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
......@@ -164,6 +170,12 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
<version>4.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
<version>1.9.8</version>
......@@ -260,6 +272,13 @@
<artifactId>onos-of-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-thirdparty</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-of-api</artifactId>
......@@ -412,7 +431,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.12.1</version>
<version>2.13</version>
<dependencies>
<dependency>
<groupId>org.onlab.tools</groupId>
......@@ -502,7 +521,7 @@
<group>
<title>Core Subsystems</title>
<packages>
org.onlab.onos.impl:org.onlab.onos.cluster.impl:org.onlab.onos.net.device.impl:org.onlab.onos.net.link.impl:org.onlab.onos.net.host.impl:org.onlab.onos.net.topology.impl:org.onlab.onos.net.packet.impl:org.onlab.onos.net.flow.impl:org.onlab.onos.store.trivial.*:org.onlab.onos.net.*.impl:org.onlab.onos.event.impl:org.onlab.onos.store.*:org.onlab.onos.net.intent.impl:org.onlab.onos.net.proxyarp.impl:org.onlab.onos.mastership.impl
org.onlab.onos.impl:org.onlab.onos.cluster.impl:org.onlab.onos.net.device.impl:org.onlab.onos.net.link.impl:org.onlab.onos.net.host.impl:org.onlab.onos.net.topology.impl:org.onlab.onos.net.packet.impl:org.onlab.onos.net.flow.impl:org.onlab.onos.store.trivial.*:org.onlab.onos.net.*.impl:org.onlab.onos.event.impl:org.onlab.onos.store.*:org.onlab.onos.net.intent.impl:org.onlab.onos.net.proxyarp.impl:org.onlab.onos.mastership.impl:org.onlab.onos.json:org.onlab.onos.json.*
</packages>
</group>
<group>
......@@ -527,10 +546,11 @@
<group>
<title>Sample Applications</title>
<packages>
org.onlab.onos.tvue:org.onlab.onos.fwd:org.onlab.onos.ifwd:org.onlab.onos.mobility:org.onlab.onos.proxyarp:org.onlab.onos.foo
org.onlab.onos.tvue:org.onlab.onos.fwd:org.onlab.onos.ifwd:org.onlab.onos.mobility:org.onlab.onos.proxyarp:org.onlab.onos.foo:org.onlab.onos.calendar
</packages>
</group>
</groups>
<excludePackageNames>org.onlab.thirdparty</excludePackageNames>
</configuration>
</plugin>
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-of-providers</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-of-provider-host</artifactId>
<packaging>bundle</packaging>
<description>ONOS OpenFlow protocol host provider</description>
</project>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-providers</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-host-provider</artifactId>
<packaging>bundle</packaging>
<description>ONOS host tracking provider</description>
<dependencies>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-api</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package org.onlab.onos.provider.host.impl;
import static org.slf4j.LoggerFactory.getLogger;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Host;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.HostLocation;
import org.onlab.onos.net.host.DefaultHostDescription;
import org.onlab.onos.net.host.HostDescription;
import org.onlab.onos.net.host.HostProvider;
import org.onlab.onos.net.host.HostProviderRegistry;
import org.onlab.onos.net.host.HostProviderService;
import org.onlab.onos.net.packet.PacketContext;
import org.onlab.onos.net.packet.PacketProcessor;
import org.onlab.onos.net.packet.PacketService;
import org.onlab.onos.net.provider.AbstractProvider;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.net.topology.Topology;
import org.onlab.onos.net.topology.TopologyService;
import org.onlab.packet.ARP;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.VlanId;
import org.slf4j.Logger;
/**
* Provider which uses an OpenFlow controller to detect network
* end-station hosts.
*/
@Component(immediate = true)
public class HostLocationProvider extends AbstractProvider implements HostProvider {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostProviderRegistry providerRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService pktService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected TopologyService topologyService;
private HostProviderService providerService;
private final InternalHostProvider processor = new InternalHostProvider();
/**
* Creates an OpenFlow host provider.
*/
public HostLocationProvider() {
super(new ProviderId("of", "org.onlab.onos.provider.host"));
}
@Activate
public void activate() {
providerService = providerRegistry.register(this);
pktService.addProcessor(processor, 1);
log.info("Started");
}
@Deactivate
public void deactivate() {
providerRegistry.unregister(this);
pktService.removeProcessor(processor);
providerService = null;
log.info("Stopped");
}
@Override
public void triggerProbe(Host host) {
log.info("Triggering probe on device {}", host);
}
private class InternalHostProvider implements PacketProcessor {
@Override
public void process(PacketContext context) {
if (context == null) {
return;
}
Ethernet eth = context.inPacket().parsed();
VlanId vlan = VlanId.vlanId(eth.getVlanID());
ConnectPoint heardOn = context.inPacket().receivedFrom();
// If this is not an edge port, bail out.
Topology topology = topologyService.currentTopology();
if (topologyService.isInfrastructure(topology, heardOn)) {
return;
}
HostLocation hloc = new HostLocation(heardOn, System.currentTimeMillis());
HostId hid = HostId.hostId(eth.getSourceMAC(), vlan);
// Potentially a new or moved host
if (eth.getEtherType() == Ethernet.TYPE_ARP) {
ARP arp = (ARP) eth.getPayload();
IpPrefix ip = IpPrefix.valueOf(arp.getSenderProtocolAddress(),
IpPrefix.MAX_INET_MASK);
HostDescription hdescr =
new DefaultHostDescription(eth.getSourceMAC(), vlan, hloc, ip);
providerService.hostDetected(hid, hdescr);
} else if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
//Do not learn new ip from ip packet.
HostDescription hdescr =
new DefaultHostDescription(eth.getSourceMAC(), vlan, hloc);
providerService.hostDetected(hid, hdescr);
}
}
}
}
/**
* Provider that uses packet service as a means of host discovery and tracking.
*/
package org.onlab.onos.provider.host.impl;
package org.onlab.onos.provider.host.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.nio.ByteBuffer;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.host.HostDescription;
import org.onlab.onos.net.host.HostProvider;
import org.onlab.onos.net.host.HostProviderRegistry;
import org.onlab.onos.net.host.HostProviderService;
import org.onlab.onos.net.packet.DefaultInboundPacket;
import org.onlab.onos.net.packet.InboundPacket;
import org.onlab.onos.net.packet.OutboundPacket;
import org.onlab.onos.net.packet.PacketContext;
import org.onlab.onos.net.packet.PacketProcessor;
import org.onlab.onos.net.packet.PacketService;
import org.onlab.onos.net.provider.AbstractProviderService;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.net.topology.Topology;
import org.onlab.onos.net.topology.TopologyServiceAdapter;
import org.onlab.packet.ARP;
import org.onlab.packet.Ethernet;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
public class HostLocationProviderTest {
private static final Integer INPORT = 10;
private static final String DEV1 = "of:1";
private static final String DEV2 = "of:2";
private static final String DEV3 = "of:3";
private static final VlanId VLAN = VlanId.vlanId();
private static final MacAddress MAC = MacAddress.valueOf("00:00:11:00:00:01");
private static final MacAddress BCMAC = MacAddress.valueOf("ff:ff:ff:ff:ff:ff");
private static final byte[] IP = new byte[]{10, 0, 0, 1};
private final HostLocationProvider provider = new HostLocationProvider();
private final TestHostRegistry hostService = new TestHostRegistry();
private final TestTopologyService topoService = new TestTopologyService();
private final TestPacketService packetService = new TestPacketService();
private PacketProcessor testProcessor;
private TestHostProviderService providerService;
@Before
public void setUp() {
provider.providerRegistry = hostService;
provider.topologyService = topoService;
provider.pktService = packetService;
provider.activate();
}
@Test
public void basics() {
assertNotNull("registration expected", providerService);
assertEquals("incorrect provider", provider, providerService.provider());
}
@Test
public void events() {
// new host
testProcessor.process(new TestPacketContext(DEV1));
assertNotNull("new host expected", providerService.added);
assertNull("host motion unexpected", providerService.moved);
// the host moved to new switch
testProcessor.process(new TestPacketContext(DEV2));
assertNotNull("host motion expected", providerService.moved);
// the host was misheard on a spine
testProcessor.process(new TestPacketContext(DEV3));
assertNull("host misheard on spine switch", providerService.spine);
}
@After
public void tearDown() {
provider.deactivate();
provider.providerRegistry = null;
}
private class TestHostRegistry implements HostProviderRegistry {
@Override
public HostProviderService register(HostProvider provider) {
providerService = new TestHostProviderService(provider);
return providerService;
}
@Override
public void unregister(HostProvider provider) {
}
@Override
public Set<ProviderId> getProviders() {
return null;
}
}
private class TestHostProviderService
extends AbstractProviderService<HostProvider>
implements HostProviderService {
DeviceId added = null;
DeviceId moved = null;
DeviceId spine = null;
protected TestHostProviderService(HostProvider provider) {
super(provider);
}
@Override
public void hostDetected(HostId hostId, HostDescription hostDescription) {
DeviceId descr = hostDescription.location().deviceId();
if (added == null) {
added = descr;
} else if ((moved == null) && !descr.equals(added)) {
moved = descr;
} else {
spine = descr;
}
}
@Override
public void hostVanished(HostId hostId) {
}
}
private class TestPacketService implements PacketService {
@Override
public void addProcessor(PacketProcessor processor, int priority) {
testProcessor = processor;
}
@Override
public void removeProcessor(PacketProcessor processor) {
}
@Override
public void emit(OutboundPacket packet) {
}
}
private class TestTopologyService extends TopologyServiceAdapter {
@Override
public boolean isInfrastructure(Topology topology,
ConnectPoint connectPoint) {
//simulate DPID3 as an infrastructure switch
if ((connectPoint.deviceId()).equals(DeviceId.deviceId(DEV3))) {
return true;
}
return false;
}
}
private class TestPacketContext implements PacketContext {
private final String deviceId;
public TestPacketContext(String deviceId) {
this.deviceId = deviceId;
}
@Override
public long time() {
return 0;
}
@Override
public InboundPacket inPacket() {
ARP arp = new ARP();
arp.setSenderProtocolAddress(IP)
.setSenderHardwareAddress(MAC.toBytes())
.setTargetHardwareAddress(BCMAC.toBytes())
.setTargetProtocolAddress(IP);
Ethernet eth = new Ethernet();
eth.setEtherType(Ethernet.TYPE_ARP)
.setVlanID(VLAN.toShort())
.setSourceMACAddress(MAC.toBytes())
.setDestinationMACAddress(BCMAC.getAddress())
.setPayload(arp);
ConnectPoint receivedFrom = new ConnectPoint(DeviceId.deviceId(deviceId),
PortNumber.portNumber(INPORT));
return new DefaultInboundPacket(receivedFrom, eth,
ByteBuffer.wrap(eth.serialize()));
}
@Override
public OutboundPacket outPacket() {
return null;
}
@Override
public TrafficTreatment.Builder treatmentBuilder() {
return null;
}
@Override
public void send() {
}
@Override
public boolean block() {
return false;
}
@Override
public boolean isHandled() {
return false;
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-providers</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-lldp-provider</artifactId>
<packaging>bundle</packaging>
<description>ONOS LLDP Link Discovery</description>
</project>
package org.onlab.onos.provider.lldp.impl;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.onos.mastership.MastershipService;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceListener;
import org.onlab.onos.net.device.DeviceService;
import org.onlab.onos.net.link.LinkProvider;
import org.onlab.onos.net.link.LinkProviderRegistry;
import org.onlab.onos.net.link.LinkProviderService;
import org.onlab.onos.net.packet.PacketContext;
import org.onlab.onos.net.packet.PacketProcessor;
import org.onlab.onos.net.packet.PacketService;
import org.onlab.onos.net.provider.AbstractProvider;
import org.onlab.onos.net.provider.ProviderId;
import org.slf4j.Logger;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provider which uses an OpenFlow controller to detect network
* infrastructure links.
*/
@Component(immediate = true)
public class LLDPLinkProvider extends AbstractProvider implements LinkProvider {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LinkProviderRegistry providerRegistry;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetSevice;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected MastershipService masterService;
private LinkProviderService providerService;
private final boolean useBDDP = true;
private final InternalLinkProvider listener = new InternalLinkProvider();
protected final Map<DeviceId, LinkDiscovery> discoverers = new ConcurrentHashMap<>();
/**
* Creates an OpenFlow link provider.
*/
public LLDPLinkProvider() {
super(new ProviderId("lldp", "org.onlab.onos.provider.lldp"));
}
@Activate
public void activate() {
providerService = providerRegistry.register(this);
deviceService.addListener(listener);
packetSevice.addProcessor(listener, 0);
log.info("Started");
}
@Deactivate
public void deactivate() {
for (LinkDiscovery ld : discoverers.values()) {
ld.stop();
}
providerRegistry.unregister(this);
deviceService.removeListener(listener);
packetSevice.removeProcessor(listener);
providerService = null;
log.info("Stopped");
}
private class InternalLinkProvider implements PacketProcessor, DeviceListener {
@Override
public void event(DeviceEvent event) {
LinkDiscovery ld = null;
Device device = event.subject();
Port port = event.port();
switch (event.type()) {
case DEVICE_ADDED:
discoverers.put(device.id(),
new LinkDiscovery(device, packetSevice, masterService,
providerService, useBDDP));
break;
case PORT_ADDED:
case PORT_UPDATED:
if (event.port().isEnabled()) {
ld = discoverers.get(device.id());
if (ld == null) {
return;
}
ld.addPort(port);
} else {
ConnectPoint point = new ConnectPoint(device.id(),
port.number());
providerService.linksVanished(point);
}
break;
case PORT_REMOVED:
ConnectPoint point = new ConnectPoint(device.id(),
port.number());
providerService.linksVanished(point);
break;
case DEVICE_REMOVED:
case DEVICE_SUSPENDED:
ld = discoverers.get(device.id());
if (ld == null) {
return;
}
ld.stop();
providerService.linksVanished(device.id());
break;
case DEVICE_AVAILABILITY_CHANGED:
ld = discoverers.get(device.id());
if (ld == null) {
return;
}
if (deviceService.isAvailable(device.id())) {
ld.start();
} else {
providerService.linksVanished(device.id());
ld.stop();
}
break;
case DEVICE_UPDATED:
case DEVICE_MASTERSHIP_CHANGED:
break;
default:
log.debug("Unknown event {}", event);
}
}
@Override
public void process(PacketContext context) {
if (context == null) {
return;
}
LinkDiscovery ld = discoverers.get(
context.inPacket().receivedFrom().deviceId());
if (ld == null) {
return;
}
if (ld.handleLLDP(context)) {
context.block();
}
}
}
}
/*******************************************************************************
* Copyright 2014 Open Networking 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
*
* 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 org.onlab.onos.provider.lldp.impl;
import static org.slf4j.LoggerFactory.getLogger;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.jboss.netty.util.Timeout;
import org.jboss.netty.util.TimerTask;
import org.onlab.onos.mastership.MastershipService;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link.Type;
import org.onlab.onos.net.MastershipRole;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.flow.DefaultTrafficTreatment;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.link.LinkDescription;
import org.onlab.onos.net.link.LinkProviderService;
import org.onlab.onos.net.packet.DefaultOutboundPacket;
import org.onlab.onos.net.packet.OutboundPacket;
import org.onlab.onos.net.packet.PacketContext;
import org.onlab.onos.net.packet.PacketService;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ONOSLLDP;
import org.onlab.util.Timer;
import org.slf4j.Logger;
/**
* Run discovery process from a physical switch. Ports are initially labeled as
* slow ports. When an LLDP is successfully received, label the remote port as
* fast. Every probeRate milliseconds, loop over all fast ports and send an
* LLDP, send an LLDP for a single slow port. Based on FlowVisor topology
* discovery implementation.
*
* TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen
* for flow_removed messages
*/
public class LinkDiscovery implements TimerTask {
private final Device device;
// send 1 probe every probeRate milliseconds
private final long probeRate;
private final Set<Long> slowPorts;
private final Set<Long> fastPorts;
// number of unacknowledged probes per port
private final Map<Long, AtomicInteger> portProbeCount;
// number of probes to send before link is removed
private static final short MAX_PROBE_COUNT = 3;
private final Logger log = getLogger(getClass());
private final ONOSLLDP lldpPacket;
private final Ethernet ethPacket;
private Ethernet bddpEth;
private final boolean useBDDP;
private final LinkProviderService linkProvider;
private final PacketService pktService;
private final MastershipService mastershipService;
private Timeout timeout;
private boolean isStopped;
/**
* Instantiates discovery manager for the given physical switch. Creates a
* generic LLDP packet that will be customized for the port it is sent out on.
* Starts the the timer for the discovery process.
* @param device the physical switch
* @param masterService
* @param useBDDP flag to also use BDDP for discovery
*/
public LinkDiscovery(Device device, PacketService pktService,
MastershipService masterService, LinkProviderService providerService, Boolean... useBDDP) {
this.device = device;
this.probeRate = 3000;
this.linkProvider = providerService;
this.pktService = pktService;
this.mastershipService = masterService;
this.slowPorts = Collections.synchronizedSet(new HashSet<Long>());
this.fastPorts = Collections.synchronizedSet(new HashSet<Long>());
this.portProbeCount = new HashMap<>();
this.lldpPacket = new ONOSLLDP();
this.lldpPacket.setChassisId(device.chassisId());
this.lldpPacket.setDevice(device.id().toString());
this.ethPacket = new Ethernet();
this.ethPacket.setEtherType(Ethernet.TYPE_LLDP);
this.ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA);
this.ethPacket.setPayload(this.lldpPacket);
this.ethPacket.setPad(true);
this.useBDDP = useBDDP.length > 0 ? useBDDP[0] : false;
if (this.useBDDP) {
this.bddpEth = new Ethernet();
this.bddpEth.setPayload(this.lldpPacket);
this.bddpEth.setEtherType(Ethernet.TYPE_BSN);
this.bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST);
this.bddpEth.setPad(true);
log.info("Using BDDP to discover network");
}
start();
this.log.debug("Started discovery manager for switch {}",
device.id());
}
/**
* Add physical port port to discovery process.
* Send out initial LLDP and label it as slow port.
*
* @param port the port
*/
public void addPort(final Port port) {
this.log.debug("sending init probe to port {}",
port.number().toLong());
sendProbes(port.number().toLong());
synchronized (this) {
this.slowPorts.add(port.number().toLong());
}
}
/**
* Removes physical port from discovery process.
*
* @param port the port
*/
public void removePort(final Port port) {
// Ignore ports that are not on this switch
long portnum = port.number().toLong();
synchronized (this) {
if (this.slowPorts.contains(portnum)) {
this.slowPorts.remove(portnum);
} else if (this.fastPorts.contains(portnum)) {
this.fastPorts.remove(portnum);
this.portProbeCount.remove(portnum);
// no iterator to update
} else {
this.log.warn(
"tried to dynamically remove non-existing port {}",
portnum);
}
}
}
/**
* Method called by remote port to acknowledge receipt of LLDP sent by
* this port. If slow port, updates label to fast. If fast port, decrements
* number of unacknowledged probes.
*
* @param portNumber the port
*/
public void ackProbe(final Long portNumber) {
synchronized (this) {
if (this.slowPorts.contains(portNumber)) {
this.log.debug("Setting slow port to fast: {}:{}",
this.device.id(), portNumber);
this.slowPorts.remove(portNumber);
this.fastPorts.add(portNumber);
this.portProbeCount.put(portNumber, new AtomicInteger(0));
} else if (this.fastPorts.contains(portNumber)) {
this.portProbeCount.get(portNumber).set(0);
} else {
this.log.debug(
"Got ackProbe for non-existing port: {}",
portNumber);
}
}
}
/**
* Handles an incoming LLDP packet. Creates link in topology and sends ACK
* to port where LLDP originated.
*/
public boolean handleLLDP(PacketContext context) {
Ethernet eth = context.inPacket().parsed();
ONOSLLDP onoslldp = ONOSLLDP.parseONOSLLDP(eth);
if (onoslldp != null) {
final PortNumber dstPort =
context.inPacket().receivedFrom().port();
final PortNumber srcPort = PortNumber.portNumber(onoslldp.getPort());
final DeviceId srcDeviceId = DeviceId.deviceId(onoslldp.getDeviceString());
final DeviceId dstDeviceId = context.inPacket().receivedFrom().deviceId();
this.ackProbe(srcPort.toLong());
ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort);
ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort);
LinkDescription ld;
if (eth.getEtherType() == Ethernet.TYPE_BSN) {
ld = new DefaultLinkDescription(src, dst, Type.INDIRECT);
} else {
ld = new DefaultLinkDescription(src, dst, Type.DIRECT);
}
linkProvider.linkDetected(ld);
return true;
}
return false;
}
/**
* Execute this method every t milliseconds. Loops over all ports
* labeled as fast and sends out an LLDP. Send out an LLDP on a single slow
* port.
*
* @param t timeout
* @throws Exception
*/
@Override
public void run(final Timeout t) {
this.log.debug("sending probes");
synchronized (this) {
final Iterator<Long> fastIterator = this.fastPorts.iterator();
Long portNumber;
Integer probeCount;
while (fastIterator.hasNext()) {
portNumber = fastIterator.next();
probeCount = this.portProbeCount.get(portNumber)
.getAndIncrement();
if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
this.log.debug("sending fast probe to port");
sendProbes(portNumber);
} else {
// Update fast and slow ports
//fastIterator.remove();
//this.slowPorts.add(portNumber);
//this.portProbeCount.remove(portNumber);
this.portProbeCount.get(portNumber).set(0);
ConnectPoint cp = new ConnectPoint(
device.id(),
PortNumber.portNumber(portNumber));
log.debug("Link down -> {}", cp);
linkProvider.linksVanished(cp);
}
}
// send a probe for the next slow port
if (!this.slowPorts.isEmpty()) {
Iterator<Long> slowIterator = this.slowPorts.iterator();
while (slowIterator.hasNext()) {
portNumber = slowIterator.next();
this.log.debug("sending slow probe to port {}", portNumber);
sendProbes(portNumber);
}
}
}
// reschedule timer
timeout = Timer.getTimer().newTimeout(this, this.probeRate,
TimeUnit.MILLISECONDS);
}
public void stop() {
timeout.cancel();
isStopped = true;
}
public void start() {
timeout = Timer.getTimer().newTimeout(this, 0,
TimeUnit.MILLISECONDS);
isStopped = false;
}
/**
* Creates packet_out LLDP for specified output port.
*
* @param port the port
* @return Packet_out message with LLDP data
*/
private OutboundPacket createOutBoundLLDP(final Long port) {
if (port == null) {
return null;
}
this.lldpPacket.setPortId(port.intValue());
this.ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11");
final byte[] lldp = this.ethPacket.serialize();
OutboundPacket outboundPacket = new DefaultOutboundPacket(
this.device.id(),
DefaultTrafficTreatment.builder().setOutput(
PortNumber.portNumber(port)).build(),
ByteBuffer.wrap(lldp));
return outboundPacket;
}
/**
* Creates packet_out BDDP for specified output port.
*
* @param port the port
* @return Packet_out message with LLDP data
*/
private OutboundPacket createOutBoundBDDP(final Long port) {
if (port == null) {
return null;
}
this.lldpPacket.setPortId(port.intValue());
this.bddpEth.setSourceMACAddress("DE:AD:BE:EF:BA:11");
final byte[] bddp = this.bddpEth.serialize();
OutboundPacket outboundPacket = new DefaultOutboundPacket(
this.device.id(),
DefaultTrafficTreatment.builder()
.setOutput(PortNumber.portNumber(port)).build(),
ByteBuffer.wrap(bddp));
return outboundPacket;
}
private void sendProbes(Long portNumber) {
if (mastershipService.getLocalRole(this.device.id()) ==
MastershipRole.MASTER) {
OutboundPacket pkt = this.createOutBoundLLDP(portNumber);
pktService.emit(pkt);
if (useBDDP) {
OutboundPacket bpkt = this.createOutBoundBDDP(portNumber);
pktService.emit(bpkt);
}
}
}
public boolean containsPort(Long portNumber) {
if (slowPorts.contains(portNumber) || fastPorts.contains(portNumber)) {
return true;
}
return false;
}
public boolean isStopped() {
return isStopped;
}
}
/**
* Provider that uses the core as a means of infrastructure link inference.
*/
package org.onlab.onos.provider.lldp.impl;
package org.onlab.onos.provider.lldp.impl;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.mastership.MastershipListener;
import org.onlab.onos.mastership.MastershipService;
import org.onlab.onos.mastership.MastershipTermService;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultDevice;
import org.onlab.onos.net.DefaultPort;
import org.onlab.onos.net.Device;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.MastershipRole;
import org.onlab.onos.net.Port;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.device.DeviceEvent;
import org.onlab.onos.net.device.DeviceListener;
import org.onlab.onos.net.device.DeviceService;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.link.LinkDescription;
import org.onlab.onos.net.link.LinkProvider;
import org.onlab.onos.net.link.LinkProviderRegistry;
import org.onlab.onos.net.link.LinkProviderService;
import org.onlab.onos.net.packet.DefaultInboundPacket;
import org.onlab.onos.net.packet.InboundPacket;
import org.onlab.onos.net.packet.OutboundPacket;
import org.onlab.onos.net.packet.PacketContext;
import org.onlab.onos.net.packet.PacketProcessor;
import org.onlab.onos.net.packet.PacketService;
import org.onlab.onos.net.provider.AbstractProviderService;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.packet.ChassisId;
import org.onlab.packet.Ethernet;
import org.onlab.packet.ONOSLLDP;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.*;
public class LLDPLinkProviderTest {
private static final DeviceId DID1 = DeviceId.deviceId("of:0000000000000001");
private static final DeviceId DID2 = DeviceId.deviceId("of:0000000000000002");
private static Port pd1;
private static Port pd2;
private static Port pd3;
private static Port pd4;
private final LLDPLinkProvider provider = new LLDPLinkProvider();
private final TestLinkRegistry linkService = new TestLinkRegistry();
private final TestPacketService packetService = new TestPacketService();
private final TestDeviceService deviceService = new TestDeviceService();
private final TestMasterShipService masterService = new TestMasterShipService();
private TestLinkProviderService providerService;
private PacketProcessor testProcessor;
private DeviceListener deviceListener;
@Before
public void setUp() {
provider.deviceService = deviceService;
provider.packetSevice = packetService;
provider.providerRegistry = linkService;
provider.masterService = masterService;
provider.activate();
}
@Test
public void basics() {
assertNotNull("registration expected", providerService);
assertEquals("incorrect provider", provider, providerService.provider());
}
@Test
public void switchAdd() {
DeviceEvent de = deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1);
deviceListener.event(de);
assertFalse("Device not added", provider.discoverers.isEmpty());
}
@Test
public void switchRemove() {
deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1));
deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_REMOVED, DID1));
assertTrue("Discoverer is not gone", provider.discoverers.get(DID1).isStopped());
assertTrue("Device is not gone.", vanishedDpid(DID1));
}
@Test
public void portUp() {
deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1));
deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID1, port(DID1, 3, true)));
assertTrue("Port not added to discoverer",
provider.discoverers.get(DID1).containsPort((long) 3));
}
@Test
public void portDown() {
deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1));
deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID1, port(DID1, 1, false)));
assertFalse("Port added to discoverer",
provider.discoverers.get(DID1).containsPort((long) 1));
assertTrue("Port is not gone.", vanishedPort((long) 1));
}
@Test
public void portUnknown() {
deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1));
deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID2, port(DID2, 1, false)));
assertNull("DPID exists",
provider.discoverers.get(DID2));
}
@Test
public void unknownPktCtx() {
PacketContext pktCtx = new TestPacketContext(deviceService.getDevice(DID2));
testProcessor.process(pktCtx);
assertFalse("Context should still be free", pktCtx.isHandled());
}
@Test
public void knownPktCtx() {
deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1));
deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID2));
PacketContext pktCtx = new TestPacketContext(deviceService.getDevice(DID2));
testProcessor.process(pktCtx);
assertTrue("Link not detected", detectedLink(DID1, DID2));
}
@After
public void tearDown() {
provider.deactivate();
provider.providerRegistry = null;
provider.deviceService = null;
provider.packetSevice = null;
}
private DeviceEvent deviceEvent(DeviceEvent.Type type, DeviceId did) {
return new DeviceEvent(type, deviceService.getDevice(did));
}
private DeviceEvent portEvent(DeviceEvent.Type type, DeviceId did, PortNumber port) {
return new DeviceEvent(type, deviceService.getDevice(did),
deviceService.getPort(did, port));
}
private DeviceEvent portEvent(DeviceEvent.Type type, DeviceId did, Port port) {
return new DeviceEvent(type, deviceService.getDevice(did), port);
}
private Port port(DeviceId did, long port, boolean enabled) {
return new DefaultPort(deviceService.getDevice(did),
PortNumber.portNumber(port), enabled);
}
private boolean vanishedDpid(DeviceId... dids) {
for (int i = 0; i < dids.length; i++) {
if (!providerService.vanishedDpid.contains(dids[i])) {
return false;
}
}
return true;
}
private boolean vanishedPort(Long... ports) {
for (int i = 0; i < ports.length; i++) {
if (!providerService.vanishedPort.contains(ports[i])) {
return false;
}
}
return true;
}
private boolean detectedLink(DeviceId src, DeviceId dst) {
for (DeviceId key : providerService.discoveredLinks.keySet()) {
if (key.equals(src)) {
return providerService.discoveredLinks.get(src).equals(dst);
}
}
return false;
}
private class TestLinkRegistry implements LinkProviderRegistry {
@Override
public LinkProviderService register(LinkProvider provider) {
providerService = new TestLinkProviderService(provider);
return providerService;
}
@Override
public void unregister(LinkProvider provider) {
}
@Override
public Set<ProviderId> getProviders() {
return null;
}
}
private class TestLinkProviderService
extends AbstractProviderService<LinkProvider>
implements LinkProviderService {
List<DeviceId> vanishedDpid = Lists.newLinkedList();
List<Long> vanishedPort = Lists.newLinkedList();
Map<DeviceId, DeviceId> discoveredLinks = Maps.newHashMap();
protected TestLinkProviderService(LinkProvider provider) {
super(provider);
}
@Override
public void linkDetected(LinkDescription linkDescription) {
DeviceId sDid = linkDescription.src().deviceId();
DeviceId dDid = linkDescription.dst().deviceId();
discoveredLinks.put(sDid, dDid);
}
@Override
public void linkVanished(LinkDescription linkDescription) {
// TODO Auto-generated method stub
}
@Override
public void linksVanished(ConnectPoint connectPoint) {
vanishedPort.add(connectPoint.port().toLong());
}
@Override
public void linksVanished(DeviceId deviceId) {
vanishedDpid.add(deviceId);
}
}
private class TestPacketContext implements PacketContext {
protected Device device;
protected boolean blocked = false;
public TestPacketContext(Device dev) {
device = dev;
}
@Override
public long time() {
return 0;
}
@Override
public InboundPacket inPacket() {
ONOSLLDP lldp = new ONOSLLDP();
lldp.setChassisId(device.chassisId());
lldp.setPortId((int) pd1.number().toLong());
lldp.setDevice(deviceService.getDevice(DID1).id().toString());
Ethernet ethPacket = new Ethernet();
ethPacket.setEtherType(Ethernet.TYPE_LLDP);
ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA);
ethPacket.setPayload(lldp);
ethPacket.setPad(true);
ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11");
ConnectPoint cp = new ConnectPoint(device.id(), pd3.number());
return new DefaultInboundPacket(cp, ethPacket,
ByteBuffer.wrap(ethPacket.serialize()));
}
@Override
public OutboundPacket outPacket() {
return null;
}
@Override
public TrafficTreatment.Builder treatmentBuilder() {
return null;
}
@Override
public void send() {
}
@Override
public boolean block() {
blocked = true;
return blocked;
}
@Override
public boolean isHandled() {
return blocked;
}
}
private class TestPacketService implements PacketService {
@Override
public void addProcessor(PacketProcessor processor, int priority) {
testProcessor = processor;
}
@Override
public void removeProcessor(PacketProcessor processor) {
}
@Override
public void emit(OutboundPacket packet) {
}
}
private class TestDeviceService implements DeviceService {
private Map<DeviceId, Device> devices = new HashMap<>();
private final ArrayListMultimap<DeviceId, Port> ports =
ArrayListMultimap.create();
public TestDeviceService() {
Device d1 = new DefaultDevice(ProviderId.NONE, DID1, Device.Type.SWITCH,
"TESTMF", "TESTHW", "TESTSW", "TESTSN", new ChassisId());
Device d2 = new DefaultDevice(ProviderId.NONE, DID2, Device.Type.SWITCH,
"TESTMF", "TESTHW", "TESTSW", "TESTSN", new ChassisId());
devices.put(DID1, d1);
devices.put(DID2, d2);
pd1 = new DefaultPort(d1, PortNumber.portNumber(1), true);
pd2 = new DefaultPort(d1, PortNumber.portNumber(2), true);
pd3 = new DefaultPort(d2, PortNumber.portNumber(1), true);
pd4 = new DefaultPort(d2, PortNumber.portNumber(2), true);
ports.putAll(DID1, Lists.newArrayList(pd1, pd2));
ports.putAll(DID2, Lists.newArrayList(pd3, pd4));
}
@Override
public int getDeviceCount() {
return devices.values().size();
}
@Override
public Iterable<Device> getDevices() {
return devices.values();
}
@Override
public Device getDevice(DeviceId deviceId) {
return devices.get(deviceId);
}
@Override
public MastershipRole getRole(DeviceId deviceId) {
return MastershipRole.MASTER;
}
@Override
public List<Port> getPorts(DeviceId deviceId) {
return ports.get(deviceId);
}
@Override
public Port getPort(DeviceId deviceId, PortNumber portNumber) {
for (Port p : ports.get(deviceId)) {
if (p.number().equals(portNumber)) {
return p;
}
}
return null;
}
@Override
public boolean isAvailable(DeviceId deviceId) {
return true;
}
@Override
public void addListener(DeviceListener listener) {
deviceListener = listener;
}
@Override
public void removeListener(DeviceListener listener) {
}
}
private final class TestMasterShipService implements MastershipService {
@Override
public MastershipRole getLocalRole(DeviceId deviceId) {
return MastershipRole.MASTER;
}
@Override
public MastershipRole requestRoleFor(DeviceId deviceId) {
return null;
}
@Override
public void relinquishMastership(DeviceId deviceId) {
}
@Override
public NodeId getMasterFor(DeviceId deviceId) {
return null;
}
@Override
public Set<DeviceId> getDevicesOf(NodeId nodeId) {
return null;
}
@Override
public MastershipTermService requestTermService() {
return null;
}
@Override
public void addListener(MastershipListener listener) {
}
@Override
public void removeListener(MastershipListener listener) {
}
@Override
public List<NodeId> getNodesFor(DeviceId deviceId) {
return Collections.emptyList();
}
}
}
......@@ -23,6 +23,7 @@ import org.onlab.onos.openflow.controller.OpenFlowController;
import org.onlab.onos.openflow.controller.OpenFlowSwitch;
import org.onlab.onos.openflow.controller.OpenFlowSwitchListener;
import org.onlab.onos.openflow.controller.RoleState;
import org.onlab.packet.ChassisId;
import org.projectfloodlight.openflow.protocol.OFPortConfig;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.OFPortState;
......@@ -120,12 +121,14 @@ public class OpenFlowDeviceProvider extends AbstractProvider implements DevicePr
Device.Type deviceType = sw.isOptical() ? Device.Type.ROADM :
Device.Type.SWITCH;
ChassisId cId = new ChassisId(dpid.value());
DeviceDescription description =
new DefaultDeviceDescription(did.uri(), deviceType,
sw.manfacturerDescription(),
sw.hardwareDescription(),
sw.softwareDescription(),
sw.serialNumber());
sw.serialNumber(),
cId);
providerService.deviceConnected(did, description);
providerService.updatePorts(did, buildPortDescriptions(sw.getPorts()));
}
......@@ -172,7 +175,7 @@ public class OpenFlowDeviceProvider extends AbstractProvider implements DevicePr
*/
private List<PortDescription> buildPortDescriptions(
List<OFPortDesc> ports) {
final List<PortDescription> portDescs = new ArrayList<>();
final List<PortDescription> portDescs = new ArrayList<>(ports.size());
for (OFPortDesc port : ports) {
portDescs.add(buildPortDescription(port));
}
......
......@@ -59,7 +59,7 @@ public class OpenFlowDeviceProviderTest {
private static final List<OFPortDesc> PLIST = Lists.newArrayList(PD1, PD2);
private static final Device DEV1 =
new DefaultDevice(PID, DID1, SWITCH, "", "", "", "");
new DefaultDevice(PID, DID1, SWITCH, "", "", "", "", null);
private static final TestOpenFlowSwitch SW1 = new TestOpenFlowSwitch();
......
......@@ -35,6 +35,7 @@ import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyAct
import org.projectfloodlight.openflow.protocol.match.Match;
import org.projectfloodlight.openflow.protocol.match.MatchField;
import org.projectfloodlight.openflow.types.IPv4Address;
import org.projectfloodlight.openflow.types.Masked;
import org.slf4j.Logger;
import com.google.common.collect.Lists;
......@@ -218,23 +219,35 @@ public class FlowEntryBuilder {
builder.matchEthType((short) ethType);
break;
case IPV4_DST:
IPv4Address di = match.get(MatchField.IPV4_DST);
IpPrefix dip;
if (di.isCidrMask()) {
dip = IpPrefix.valueOf(di.getInt(), di.asCidrMaskLength());
if (match.isPartiallyMasked(MatchField.IPV4_DST)) {
Masked<IPv4Address> maskedIp = match.getMasked(MatchField.IPV4_DST);
dip = IpPrefix.valueOf(
maskedIp.getValue().getInt(),
maskedIp.getMask().asCidrMaskLength());
} else {
dip = IpPrefix.valueOf(di.getInt());
dip = IpPrefix.valueOf(
match.get(MatchField.IPV4_DST).getInt(),
IpPrefix.MAX_INET_MASK);
}
builder.matchIPDst(dip);
break;
case IPV4_SRC:
IPv4Address si = match.get(MatchField.IPV4_SRC);
IpPrefix sip;
if (si.isCidrMask()) {
sip = IpPrefix.valueOf(si.getInt(), si.asCidrMaskLength());
if (match.isPartiallyMasked(MatchField.IPV4_SRC)) {
Masked<IPv4Address> maskedIp = match.getMasked(MatchField.IPV4_SRC);
sip = IpPrefix.valueOf(
maskedIp.getValue().getInt(),
maskedIp.getMask().asCidrMaskLength());
} else {
sip = IpPrefix.valueOf(si.getInt());
sip = IpPrefix.valueOf(
match.get(MatchField.IPV4_SRC).getInt(),
IpPrefix.MAX_INET_MASK);
}
builder.matchIPSrc(sip);
break;
case IP_PROTO:
......@@ -249,6 +262,12 @@ public class FlowEntryBuilder {
VlanId vlanId = VlanId.vlanId(match.get(MatchField.VLAN_VID).getVlan());
builder.matchVlanId(vlanId);
break;
case TCP_DST:
builder.matchTcpDst((short) match.get(MatchField.TCP_DST).getPort());
break;
case TCP_SRC:
builder.matchTcpSrc((short) match.get(MatchField.TCP_SRC).getPort());
break;
case ARP_OP:
case ARP_SHA:
case ARP_SPA:
......@@ -272,8 +291,6 @@ public class FlowEntryBuilder {
case MPLS_TC:
case SCTP_DST:
case SCTP_SRC:
case TCP_DST:
case TCP_SRC:
case TUNNEL_ID:
case UDP_DST:
case UDP_SRC:
......
......@@ -15,6 +15,7 @@ import org.onlab.onos.net.flow.criteria.Criteria.EthTypeCriterion;
import org.onlab.onos.net.flow.criteria.Criteria.IPCriterion;
import org.onlab.onos.net.flow.criteria.Criteria.IPProtocolCriterion;
import org.onlab.onos.net.flow.criteria.Criteria.PortCriterion;
import org.onlab.onos.net.flow.criteria.Criteria.TcpPortCriterion;
import org.onlab.onos.net.flow.criteria.Criteria.VlanIdCriterion;
import org.onlab.onos.net.flow.criteria.Criteria.VlanPcpCriterion;
import org.onlab.onos.net.flow.criteria.Criterion;
......@@ -42,6 +43,7 @@ import org.projectfloodlight.openflow.types.Masked;
import org.projectfloodlight.openflow.types.OFBufferId;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.OFVlanVidMatch;
import org.projectfloodlight.openflow.types.TransportPort;
import org.projectfloodlight.openflow.types.U64;
import org.projectfloodlight.openflow.types.VlanPcp;
import org.projectfloodlight.openflow.types.VlanVid;
......@@ -161,10 +163,10 @@ public class FlowModBuilder {
switch (l3m.subtype()) {
case IP_DST:
ip = (ModIPInstruction) i;
return factory.actions().setNwDst(IPv4Address.of(ip.ip().toRealInt()));
return factory.actions().setNwDst(IPv4Address.of(ip.ip().toInt()));
case IP_SRC:
ip = (ModIPInstruction) i;
return factory.actions().setNwSrc(IPv4Address.of(ip.ip().toRealInt()));
return factory.actions().setNwSrc(IPv4Address.of(ip.ip().toInt()));
default:
log.warn("Unimplemented action type {}.", l3m.subtype());
break;
......@@ -199,6 +201,7 @@ public class FlowModBuilder {
Match.Builder mBuilder = factory.buildMatch();
EthCriterion eth;
IPCriterion ip;
TcpPortCriterion tp;
for (Criterion c : selector.criteria()) {
switch (c.type()) {
case IN_PORT:
......@@ -220,21 +223,21 @@ public class FlowModBuilder {
case IPV4_DST:
ip = (IPCriterion) c;
if (ip.ip().isMasked()) {
Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toRealInt()),
IPv4Address.of(ip.ip().netmask().toRealInt()));
Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toInt()),
IPv4Address.of(ip.ip().netmask().toInt()));
mBuilder.setMasked(MatchField.IPV4_DST, maskedIp);
} else {
mBuilder.setExact(MatchField.IPV4_DST, IPv4Address.of(ip.ip().toRealInt()));
mBuilder.setExact(MatchField.IPV4_DST, IPv4Address.of(ip.ip().toInt()));
}
break;
case IPV4_SRC:
ip = (IPCriterion) c;
if (ip.ip().isMasked()) {
Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toRealInt()),
IPv4Address.of(ip.ip().netmask().toRealInt()));
Masked<IPv4Address> maskedIp = Masked.of(IPv4Address.of(ip.ip().toInt()),
IPv4Address.of(ip.ip().netmask().toInt()));
mBuilder.setMasked(MatchField.IPV4_SRC, maskedIp);
} else {
mBuilder.setExact(MatchField.IPV4_SRC, IPv4Address.of(ip.ip().toRealInt()));
mBuilder.setExact(MatchField.IPV4_SRC, IPv4Address.of(ip.ip().toInt()));
}
break;
case IP_PROTO:
......@@ -250,6 +253,14 @@ public class FlowModBuilder {
mBuilder.setExact(MatchField.VLAN_VID,
OFVlanVidMatch.ofVlanVid(VlanVid.ofVlan(vid.vlanId().toShort())));
break;
case TCP_DST:
tp = (TcpPortCriterion) c;
mBuilder.setExact(MatchField.TCP_DST, TransportPort.of(tp.tcpPort()));
break;
case TCP_SRC:
tp = (TcpPortCriterion) c;
mBuilder.setExact(MatchField.TCP_SRC, TransportPort.of(tp.tcpPort()));
break;
case ARP_OP:
case ARP_SHA:
case ARP_SPA:
......@@ -276,8 +287,6 @@ public class FlowModBuilder {
case PBB_ISID:
case SCTP_DST:
case SCTP_SRC:
case TCP_DST:
case TCP_SRC:
case TUNNEL_ID:
case UDP_DST:
case UDP_SRC:
......
package org.onlab.onos.provider.of.flow.impl;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
......@@ -68,10 +57,20 @@ import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.U32;
import org.slf4j.Logger;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provider which uses an OpenFlow controller to detect network
......@@ -166,6 +165,16 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
for (FlowRuleBatchEntry fbe : batch.getOperations()) {
FlowRule flowRule = fbe.getTarget();
OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId().uri()));
if (sw == null) {
/*
* if a switch we are supposed to install to is gone then
* cancel (ie. rollback) the work that has been done so far
* and return the associated future.
*/
InstallationFuture failed = new InstallationFuture(sws, fmXids);
failed.cancel(true);
return failed;
}
sws.add(new Dpid(sw.getId()));
FlowModBuilder builder = new FlowModBuilder(flowRule, sw.factory());
switch (fbe.getOperator()) {
......@@ -322,6 +331,7 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
public void fail(OFErrorMsg msg, Dpid dpid) {
ok.set(false);
removeRequirement(dpid);
FlowEntry fe = null;
FlowRuleBatchEntry fbe = fms.get(msg.getXid());
FlowRule offending = fbe.getTarget();
......@@ -374,11 +384,8 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
public void satisfyRequirement(Dpid dpid) {
log.warn("Satisfaction from switch {}", dpid);
sws.remove(dpid);
countDownLatch.countDown();
cleanUp();
log.debug("Satisfaction from switch {}", dpid);
removeRequirement(dpid);
}
......@@ -395,6 +402,7 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
ok.set(false);
this.state = BatchState.CANCELLED;
cleanUp();
for (FlowRuleBatchEntry fbe : fms.values()) {
......@@ -438,7 +446,7 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
}
private void cleanUp() {
if (sws.isEmpty()) {
if (isDone() || isCancelled()) {
pendingFutures.remove(pendingXid);
for (Long xid : fms.keySet()) {
pendingFMs.remove(xid);
......@@ -446,6 +454,12 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
}
}
private void removeRequirement(Dpid dpid) {
countDownLatch.countDown();
sws.remove(dpid);
cleanUp();
}
}
}
......
package org.onlab.onos.provider.of.host.impl;
import static org.onlab.onos.net.DeviceId.deviceId;
import static org.onlab.onos.net.PortNumber.portNumber;
import static org.slf4j.LoggerFactory.getLogger;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
......@@ -29,15 +33,12 @@ import org.onlab.packet.IpPrefix;
import org.onlab.packet.VlanId;
import org.slf4j.Logger;
import static org.onlab.onos.net.DeviceId.deviceId;
import static org.onlab.onos.net.PortNumber.portNumber;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provider which uses an OpenFlow controller to detect network
* end-station hosts.
*/
@Component(immediate = true)
@Deprecated
public class OpenFlowHostProvider extends AbstractProvider implements HostProvider {
private final Logger log = getLogger(getClass());
......@@ -109,14 +110,16 @@ public class OpenFlowHostProvider extends AbstractProvider implements HostProvid
// Potentially a new or moved host
if (eth.getEtherType() == Ethernet.TYPE_ARP) {
ARP arp = (ARP) eth.getPayload();
IpPrefix ip = IpPrefix.valueOf(arp.getSenderProtocolAddress());
IpPrefix ip = IpPrefix.valueOf(arp.getSenderProtocolAddress(),
IpPrefix.MAX_INET_MASK);
HostDescription hdescr =
new DefaultHostDescription(eth.getSourceMAC(), vlan, hloc, ip);
providerService.hostDetected(hid, hdescr);
} else if (ipLearn && eth.getEtherType() == Ethernet.TYPE_IPV4) {
IPv4 pip = (IPv4) eth.getPayload();
IpPrefix ip = IpPrefix.valueOf(pip.getSourceAddress());
IpPrefix ip = IpPrefix.valueOf(pip.getSourceAddress(),
IpPrefix.MAX_INET_MASK);
HostDescription hdescr =
new DefaultHostDescription(eth.getSourceMAC(), vlan, hloc, ip);
providerService.hostDetected(hid, hdescr);
......
......@@ -66,6 +66,7 @@ import org.slf4j.Logger;
* TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen
* for flow_removed messages
*/
@Deprecated
public class LinkDiscovery implements TimerTask {
private final OpenFlowSwitch sw;
......@@ -339,9 +340,14 @@ public class LinkDiscovery implements TimerTask {
final Iterator<Integer> fastIterator = this.fastPorts.iterator();
while (fastIterator.hasNext()) {
final Integer portNumber = fastIterator.next();
OFPortDesc port = findPort(portNumber);
if (port == null) {
// port can be null
// #removePort modifies `ports` outside synchronized block
continue;
}
final int probeCount = this.portProbeCount.get(portNumber)
.getAndIncrement();
OFPortDesc port = findPort(portNumber);
if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
this.log.debug("sending fast probe to port");
......
......@@ -35,6 +35,7 @@ import org.slf4j.Logger;
* infrastructure links.
*/
@Component(immediate = true)
@Deprecated
public class OpenFlowLinkProvider extends AbstractProvider implements LinkProvider {
private final Logger log = getLogger(getClass());
......
......@@ -28,7 +28,6 @@ import org.onlab.onos.openflow.controller.OpenFlowController;
import org.onlab.onos.openflow.controller.OpenFlowPacketContext;
import org.onlab.onos.openflow.controller.OpenFlowSwitch;
import org.onlab.onos.openflow.controller.PacketListener;
import org.onlab.packet.Ethernet;
import org.projectfloodlight.openflow.protocol.OFPacketOut;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.action.OFAction;
......@@ -96,13 +95,13 @@ public class OpenFlowPacketProvider extends AbstractProvider implements PacketPr
return;
}
Ethernet eth = new Ethernet();
eth.deserialize(packet.data().array(), 0, packet.data().array().length);
//Ethernet eth = new Ethernet();
//eth.deserialize(packet.data().array(), 0, packet.data().array().length);
OFPortDesc p = null;
for (Instruction inst : packet.treatment().instructions()) {
if (inst.type().equals(Instruction.Type.OUTPUT)) {
p = portDesc(((OutputInstruction) inst).port());
OFPacketOut po = packetOut(sw, eth, p.getPortNo());
OFPacketOut po = packetOut(sw, packet.data().array(), p.getPortNo());
sw.sendMsg(po);
}
}
......@@ -116,7 +115,7 @@ public class OpenFlowPacketProvider extends AbstractProvider implements PacketPr
return builder.build();
}
private OFPacketOut packetOut(OpenFlowSwitch sw, Ethernet eth, OFPort out) {
private OFPacketOut packetOut(OpenFlowSwitch sw, byte[] eth, OFPort out) {
OFPacketOut.Builder builder = sw.factory().buildPacketOut();
OFAction act = sw.factory().actions()
.buildOutput()
......@@ -126,7 +125,7 @@ public class OpenFlowPacketProvider extends AbstractProvider implements PacketPr
.setBufferId(OFBufferId.NO_BUFFER)
.setInPort(OFPort.NO_MASK)
.setActions(Collections.singletonList(act))
.setData(eth.serialize())
.setData(eth)
.build();
}
......
......@@ -18,6 +18,8 @@
<modules>
<module>openflow</module>
<module>lldp</module>
<module>host</module>
</modules>
<dependencies>
......
......@@ -5,8 +5,9 @@ export ONOS_ROOT=${ONOS_ROOT:-~/onos-next}
# M2 repository and Karaf gold bits
export M2_REPO=${M2_REPO:-~/.m2/repository}
export KARAF_ZIP=${KARAF_ZIP:-~/Downloads/apache-karaf-3.0.1.zip}
export KARAF_TAR=${KARAF_TAR:-~/Downloads/apache-karaf-3.0.1.tar.gz}
export KARAF_VERSION=${KARAF_VERSION:-3.0.1}
export KARAF_ZIP=${KARAF_ZIP:-~/Downloads/apache-karaf-$KARAF_VERSION.zip}
export KARAF_TAR=${KARAF_TAR:-~/Downloads/apache-karaf-$KARAF_VERSION.tar.gz}
export KARAF_DIST=$(basename $KARAF_ZIP .zip)
# Add ONOS-specific directories to the exectable PATH
......@@ -25,6 +26,9 @@ export ONOS_STAGE=$ONOS_STAGE_ROOT/$ONOS_BITS
export ONOS_TAR=$ONOS_STAGE.tar.gz
# Defaults for ONOS testing using remote machines.
if [ -n "${ONOS_CELL}" -a -f $ONOS_ROOT/tools/test/cells/${ONOS_CELL} ]; then
. $ONOS_ROOT/tools/test/cells/${ONOS_CELL}
fi
export ONOS_INSTALL_DIR="/opt/onos" # Installation directory on remote
export OCI="${OCI:-192.168.56.101}" # ONOS Controller Instance
export ONOS_USER="sdn" # ONOS user on remote system
......
......@@ -13,13 +13,23 @@ rm -fr $ONOS_STAGE # Remove this when package script is completed
# Make sure we have the original apache karaf bits first
[ ! -d $M2_REPO ] && echo "M2 repository $M2_REPO not found" && exit 1
[ ! -f $KARAF_ZIP -a ! -f $KARAF_TAR ] && echo "Apache Karaf bits $KARAF_ZIP or $KARAF_TAR not found" && exit 1
[ -d $ONOS_STAGE ] && echo "ONOS stage $ONOS_STAGE already exists" && exit 1
# Create the stage directory and warp into it
mkdir -p $ONOS_STAGE
cd $ONOS_STAGE
# Check if Apache Karaf bits are available and if not, fetch them.
if [ ! -f $KARAF_ZIP -a ! -f $KARAF_TAR ]; then
echo "Downloading $KARAF_TAR..."
karafURL=$(curl -s http://www.apache.org/dyn/closer.cgi/karaf/$KARAF_VERSION/apache-karaf-$KARAF_VERSION.tar.gz |
grep "<a href=\".*apache-karaf-$KARAF_VERSION.tar.gz\"" |
head -n 1 | sed 's/.*<a href="//g;s/".*//g')
curl -s $karafURL > $KARAF_TAR
fi
[ ! -f $KARAF_ZIP -a ! -f $KARAF_TAR ] && \
echo "Apache Karaf bits $KARAF_ZIP or $KARAF_TAR not found" && exit 1
# Unroll the Apache Karaf bits, prune them and make ONOS top-level directories.
[ -f $KARAF_ZIP ] && unzip -q $KARAF_ZIP && rm -rf $KARAF_DIST/demos
[ -f $KARAF_TAR ] && tar zxf $KARAF_TAR && rm -rf $KARAF_DIST/demos
......@@ -28,7 +38,13 @@ mkdir bin
# Stage the ONOS admin scripts and patch in Karaf service wrapper extras
cp -r $ONOS_ROOT/tools/package/bin .
cp -r $ONOS_ROOT/tools/package/debian $ONOS_STAGE/debian
cp -r $ONOS_ROOT/tools/package/etc/* $KARAF_DIST/etc
cp -r $ONOS_ROOT/tools/package/etc/* $ONOS_STAGE/$KARAF_DIST/etc
# Patch-in proper Karaf version into the startup script.
sed "s/\$KARAF_VERSION/$KARAF_VERSION/g" \
$ONOS_ROOT/tools/package/bin/onos-service > bin/onos-service
sed "s/\$KARAF_VERSION/$KARAF_VERSION/g" \
$ONOS_ROOT/tools/package/bin/onos > bin/onos
# Stage the ONOS bundles
mkdir -p $KARAF_DIST/system/org/onlab
......@@ -36,16 +52,6 @@ cp -r $M2_REPO/org/onlab $KARAF_DIST/system/org/
export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core,onos-cli,onos-rest,onos-gui,onos-openflow,onos-app-fwd,onos-app-foo}"
# Cellar Patching --------------------------------------------------------------
# Patch the Apache Karaf distribution file to add Cellar features repository
#perl -pi.old -e "s|^(featuresRepositories=.*)|\1,mvn:org.apache.karaf.cellar/apache-karaf-cellar/3.0.0/xml/features|" \
# $ONOS_STAGE/$KARAF_DIST/etc/org.apache.karaf.features.cfg
# Patch the Apache Karaf distribution file to load ONOS features
#perl -pi.old -e 's|^(featuresBoot=.*)|\1,cellar|' \
# $ONOS_STAGE/$KARAF_DIST/etc/org.apache.karaf.features.cfg
# ONOS Patching ----------------------------------------------------------------
# Patch the Apache Karaf distribution file to add ONOS features repository
......
......@@ -9,6 +9,8 @@
nodes=$(env | sort | egrep "OC[0-9]+" | cut -d= -f2)
onos-package
onos-verify-cell || exit 1
for node in $nodes; do onos-install -f $node 1>/dev/null & done
# Wait for shutdown before waiting for restart
......
......@@ -6,7 +6,13 @@
export ONOS_ROOT=${ONOS_ROOT:-~/onos-next}
# Setup some environmental context for developers
export JAVA_HOME=${JAVA_HOME:-$(/usr/libexec/java_home -v 1.7)}
if [ -z "${JAVA_HOME}" ]; then
if [ -x /usr/libexec/java_home ]; then
export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)
elif [ -d /usr/lib/jvm/java-7-openjdk-amd64 ]; then
export JAVA_HOME="/usr/lib/jvm/java-7-openjdk-amd64"
fi
fi
export MAVEN=${MAVEN:-~/Applications/apache-maven-3.2.2}
export KARAF=${KARAF:-~/Applications/apache-karaf-3.0.1}
export KARAF_LOG=$KARAF/data/log/karaf.log
......@@ -15,7 +21,6 @@ export KARAF_LOG=$KARAF/data/log/karaf.log
export PATH="$PATH:$ONOS_ROOT/tools/dev/bin:$ONOS_ROOT/tools/test/bin"
export PATH="$PATH:$ONOS_ROOT/tools/build"
export PATH="$PATH:$MAVEN/bin:$KARAF/bin"
export PATH="$PATH:."
# Convenience utility to warp to various ONOS source projects
# e.g. 'o api', 'o dev', 'o'
......@@ -40,6 +45,7 @@ alias pub='onos-push-update-bundle'
# Short-hand for tailing the ONOS (karaf) log
alias tl='$ONOS_ROOT/tools/dev/bin/onos-local-log'
alias tlo='tl | grep --colour=always org.onlab'
alias ll='less $KARAF_LOG'
# Pretty-print JSON output
alias pp='python -m json.tool'
......@@ -62,13 +68,14 @@ function cell {
[ ! -f $ONOS_ROOT/tools/test/cells/$1 ] && \
echo "No such cell: $1" >&2 && return 1
unset ONOS_CELL ONOS_NIC ONOS_FEATURES
unset OC1 OC2 OC3 OC4 OC5 OC6 OC7 OC8 OC9 OCN OCI
unset OC0 OC1 OC2 OC3 OC4 OC5 OC6 OC7 OC8 OC9 OCN OCI
export ONOS_CELL=$1
. $ONOS_ROOT/tools/test/cells/$1
cell
else
env | egrep "ONOS_CELL"
env | egrep "OCI"
env | egrep "OC[1-9]+" | sort
env | egrep "OC[0-9]+" | sort
env | egrep "OCN"
env | egrep "ONOS_" | egrep -v 'ONOS_ROOT|ONOS_CELL'
fi
......
#!/bin/tcsh
# ONOS developer csh/tcsh profile conveniences
# Simply include in your own $HOME/.cshrc file. E.g.:
#
# setenv ONOS_ROOT $HOME/onos
# if ( -f $ONOS_ROOT/tools/dev/onos.cshrc ) then
# source $ONOS_ROOT/tools/dev/onos.cshrc
# endif
#
# Root of the ONOS source tree
if ( ! $?ONOS_ROOT ) then
setenv ONOS_ROOT $HOME/onos
endif
# Setup some environmental context for developers
if ( ! $?JAVA_HOME ) then
if ( -x /usr/libexec/java_home ) then
setenv JAVA_HOME `/usr/libexec/java_home -v 1.7`
else if ( -d /usr/lib/jvm/java-7-openjdk-amd64 ) then
setenv JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64
endif
endif
if ( ! $?MAVEN ) then
setenv MAVEN $HOME/Applications/apache-maven-3.2.2
endif
if ( ! $?KARAF ) then
setenv KARAF $HOME/Applications/apache-karaf-3.0.1
endif
setenv KARAF_LOG $KARAF/data/log/karaf.log
alias onos-setup-cell ' ( $ONOS_ROOT/tools/test/bin/onos-show-cell \!^ ) && setenv ONOS_CELL \!^'
set path=( $path $ONOS_ROOT/tools/dev/bin $ONOS_ROOT/tools/test/bin )
set path=( $path $ONOS_ROOT/tools/build )
set path=( $path $KARAF/bin )
......@@ -5,5 +5,5 @@
export JAVA_HOME=${JAVA_HOME:-/usr/lib/jvm/java-7-openjdk-amd64/}
cd $(dirname $0)/../apache-karaf-*/bin
cd $(dirname $0)/../apache-karaf-$KARAF_VERSION/bin
./client -h localhost "$@"
......
......@@ -7,4 +7,4 @@ export JAVA_HOME=${JAVA_HOME:-/usr/lib/jvm/java-7-openjdk-amd64/}
export JAVA_OPTS="-Xms256M -Xmx2048M"
cd /opt/onos
/opt/onos/apache-karaf-3.0.1/bin/karaf "$@"
/opt/onos/apache-karaf-$KARAF_VERSION/bin/karaf "$@"
......
......@@ -6,4 +6,39 @@
[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
. $ONOS_ROOT/tools/build/envDefaults
ssh $ONOS_USER@${1:-$OCI} "sudo service onos ${2:-status}"
function print_usage {
command_name=`basename $0`
echo "Remotely administer the ONOS service on a single node or the current ONOS cell."
echo
echo "Usage: $command_name <TARGET> [COMMAND]"
echo " $command_name [-h | --help]"
echo "Options:"
echo " TARGET The target of the command"
echo " COMMAND The command to execute. Default value is 'status'"
echo " [-h | --help] Print this help"
echo ""
echo "TARGET: <hostname | --cell>"
echo " hostname Execute on the specified host name"
echo " --cell Execute on the current ONOS cell"
echo ""
echo "COMMAND: [start|stop|restart|status]"
echo ""
}
# Print usage
if [ "${1}" = "-h" -o "${1}" = "--help" ]; then
print_usage
exit 0
fi
# Select the target
if [ "${1}" = "--cell" ]; then
nodes=$(env | sort | egrep "OC[0-9]+" | cut -d= -f2)
else
nodes=${1:-$OCI}
fi
# Execute the remote commands
for node in $nodes; do
ssh $ONOS_USER@${node} "sudo service onos ${2:-status}"
done
......
......@@ -42,7 +42,7 @@ fi
echo "ONOS_CELL=${ONOS_CELL}"
echo "ONOS_NIC=${ONOS_NIC}"
for n in {1..9}; do
for n in {0..9}; do
ocn="OC${n}"
if [ -n "${!ocn}" ]; then
echo "$ocn=${!ocn}"
......
......@@ -8,4 +8,4 @@ export OC2="192.168.56.102"
export OCN="192.168.56.103"
export OCI="${OC1}"
export ONOS_FEATURES=""
export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core,onos-cli,onos-openflow,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}"
......
......@@ -2,8 +2,8 @@
export ONOS_CELL="office"
export ONOS_NIC="10.128.4.*"
export OC1="10.128.4.60"
export ONOS_NIC="10.1.10.*"
export OC1="10.1.10.223"
export OCI="${OC1}"
export ONOS_FEATURES="webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd,onos-app-mobility,onos-app-tvue,onos-app-proxyarp"
......
......@@ -7,4 +7,4 @@ export OC1="192.168.56.101"
export OCN="192.168.56.103"
export OCI="${OC1}"
export ONOS_FEATURES=""
export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}"
......
# Local VirtualBox-based single ONOS instance & ONOS mininet box
export ONOS_NIC=192.168.56.*
export OC1="192.168.56.101"
export OCN="192.168.56.103"
export ONOS_FEATURES=webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd,onos-app-mobility,onos-app-tvue,onos-app-optical
package org.onlab.packet;
/**
* The class representing a network device chassisId.
* This class is immutable.
*/
// TODO: Move this to a reasonable place.
public final class ChassisId {
private static final long UNKNOWN = 0;
private final long value;
/**
* Default constructor.
*/
public ChassisId() {
this.value = ChassisId.UNKNOWN;
}
/**
* Constructor from a long value.
*
* @param value the value to use.
*/
public ChassisId(long value) {
this.value = value;
}
/**
* Constructor from a string.
*
* @param value the value to use.
*/
public ChassisId(String value) {
this.value = Long.valueOf(value);
}
/**
* Get the value of the chassis id.
*
* @return the value of the chassis id.
*/
public long value() {
return value;
}
/**
* Convert the Chassis Id value to a ':' separated hexadecimal string.
*
* @return the Chassis Id value as a ':' separated hexadecimal string.
*/
@Override
public String toString() {
return Long.toHexString(this.value);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof ChassisId)) {
return false;
}
ChassisId otherChassisId = (ChassisId) other;
return value == otherChassisId.value;
}
@Override
public int hashCode() {
int hash = 17;
hash += 31 * hash + (int) (value ^ value >>> 32);
return hash;
}
}
......@@ -58,6 +58,7 @@ public class Ethernet extends BasePacket {
Ethernet.etherTypeClassMap.put(Ethernet.TYPE_RARP, ARP.class);
Ethernet.etherTypeClassMap.put(Ethernet.TYPE_IPV4, IPv4.class);
Ethernet.etherTypeClassMap.put(Ethernet.TYPE_LLDP, LLDP.class);
Ethernet.etherTypeClassMap.put(Ethernet.TYPE_BSN, LLDP.class);
}
protected MacAddress destinationMACAddress;
......
......@@ -2,13 +2,15 @@ package org.onlab.packet;
import java.util.Arrays;
/**
* A class representing an IPv4 address.
* <p/>
* TODO this class is a clone of IpPrefix and still needs to be modified to
* look more like an IpAddress.
*/
public final class IpAddress {
public final class IpAddress implements Comparable<IpAddress> {
// TODO a comparator for netmasks? E.g. for sorting by prefix match order.
......@@ -121,7 +123,7 @@ public final class IpAddress {
int mask = DEFAULT_MASK;
if (parts.length == 2) {
mask = Integer.valueOf(parts[1]);
mask = Integer.parseInt(parts[1]);
if (mask > MAX_INET_MASK) {
throw new IllegalArgumentException(
"Value of subnet mask cannot exceed "
......@@ -174,14 +176,6 @@ public final class IpAddress {
* @return the IP address's value as an integer
*/
public int toInt() {
int address = 0;
for (int i = 0; i < INET_LEN; i++) {
address |= octets[i] << ((INET_LEN - (i + 1)) * 8);
}
return address;
}
public int toRealInt() {
int val = 0;
for (int i = 0; i < octets.length; i++) {
val <<= 8;
......@@ -191,6 +185,15 @@ public final class IpAddress {
}
/**
* Converts the IP address to a /32 IP prefix.
*
* @return the new IP prefix
*/
public IpPrefix toPrefix() {
return IpPrefix.valueOf(octets, MAX_INET_MASK);
}
/**
* Helper for computing the mask value from CIDR.
*
* @return an integer bitmask
......@@ -280,6 +283,13 @@ public final class IpAddress {
}
@Override
public int compareTo(IpAddress o) {
Long lv = ((long) this.toInt()) & 0xffffffffL;
Long rv = ((long) o.toInt()) & 0xffffffffL;
return lv.compareTo(rv);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
......
......@@ -120,7 +120,7 @@ public final class IpPrefix {
int mask = DEFAULT_MASK;
if (parts.length == 2) {
mask = Integer.valueOf(parts[1]);
mask = Integer.parseInt(parts[1]);
if (mask > MAX_INET_MASK) {
throw new IllegalArgumentException(
"Value of subnet mask cannot exceed "
......@@ -173,14 +173,6 @@ public final class IpPrefix {
* @return the IP address's value as an integer
*/
public int toInt() {
int address = 0;
for (int i = 0; i < INET_LEN; i++) {
address |= octets[i] << ((INET_LEN - (i + 1)) * 8);
}
return address;
}
public int toRealInt() {
int val = 0;
for (int i = 0; i < octets.length; i++) {
val <<= 8;
......
......@@ -150,7 +150,7 @@ public class LLDP extends BasePacket {
final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
LLDPTLV tlv;
do {
tlv = new LLDPTLV().deserialize(bb);
tlv = new LLDPOrganizationalTLV().deserialize(bb);
// if there was a failure to deserialize stop processing TLVs
if (tlv == null) {
......@@ -169,6 +169,7 @@ public class LLDP extends BasePacket {
case 0x3:
this.ttl = tlv;
break;
default:
this.optionalTLVList.add(tlv);
break;
......
......@@ -140,6 +140,9 @@ public class LLDPOrganizationalTLV extends LLDPTLV {
@Override
public byte[] serialize() {
if (this.type != LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
return super.serialize();
}
final int valueLength = LLDPOrganizationalTLV.OUI_LENGTH
+ LLDPOrganizationalTLV.SUBTYPE_LENGTH + this.infoString.length;
this.value = new byte[valueLength];
......@@ -152,7 +155,11 @@ public class LLDPOrganizationalTLV extends LLDPTLV {
@Override
public LLDPTLV deserialize(final ByteBuffer bb) {
super.deserialize(bb);
LLDPTLV tlv = super.deserialize(bb);
if (tlv.getType() != LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
return tlv;
}
final ByteBuffer optionalField = ByteBuffer.wrap(this.value);
final byte[] oui = new byte[LLDPOrganizationalTLV.OUI_LENGTH];
......
......@@ -111,6 +111,7 @@ public class LLDPTLV {
sscratch = bb.getShort();
this.type = (byte) (sscratch >> 9 & 0x7f);
this.length = (short) (sscratch & 0x1ff);
if (this.length > 0) {
this.value = new byte[this.length];
......@@ -120,6 +121,7 @@ public class LLDPTLV {
}
bb.get(this.value);
}
return this;
}
......
......@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
* Refer to IEEE Std 802.1ABTM-2009 for more information.
*
*/
@Deprecated
public class ONLabLddp extends LLDP {
private static final Logger LOG = LoggerFactory.getLogger(ONLabLddp.class);
......
package org.onlab.packet;
import com.google.common.collect.Lists;
import org.apache.commons.lang.ArrayUtils;
import java.nio.ByteBuffer;
/**
* ONOS LLDP containing organizational TLV for ONOS device dicovery.
*/
public class ONOSLLDP extends LLDP {
public static final byte[] ONLAB_OUI = {(byte) 0xa4, 0x23, 0x05};
public static final String DEFAULT_DEVICE = "INVALID";
public static final String DEFAULT_NAME = "ONOS Discovery";
public static final byte[] LLDP_NICIRA = {0x01, 0x23, 0x20, 0x00, 0x00,
0x01};
public static final byte[] LLDP_MULTICAST = {0x01, (byte) 0x80,
(byte) 0xc2, 0x00, 0x00, 0x0e};
public static final byte[] BDDP_MULTICAST = {(byte) 0xff, (byte) 0xff,
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
private static final byte NAME_SUBTYPE = 1;
private static final byte DEVICE_SUBTYPE = 2;
private static final short NAME_LENGTH = 4; //1 for subtype + 3 for OUI
private static final short DEVICE_LENGTH = 4; //1 for subtype + 3 for OUI
private final LLDPOrganizationalTLV nameTLV = new LLDPOrganizationalTLV();
private final LLDPOrganizationalTLV deviceTLV = new LLDPOrganizationalTLV();
// TLV constants: type, size and subtype
// Organizationally specific TLV also have packet offset and contents of TLV
// header
private static final byte CHASSIS_TLV_TYPE = 1;
private static final byte CHASSIS_TLV_SIZE = 7;
private static final byte CHASSIS_TLV_SUBTYPE = 4;
private static final byte PORT_TLV_TYPE = 2;
private static final byte PORT_TLV_SIZE = 5;
private static final byte PORT_TLV_SUBTYPE = 2;
private static final byte TTL_TLV_TYPE = 3;
private final byte[] ttlValue = new byte[] {0, 0x78};
public ONOSLLDP() {
super();
setName(DEFAULT_NAME);
setDevice(DEFAULT_DEVICE);
setOptionalTLVList(Lists.<LLDPTLV>newArrayList(nameTLV, deviceTLV));
setTtl(new LLDPTLV().setType((byte) TTL_TLV_TYPE)
.setLength((short) ttlValue.length)
.setValue(ttlValue));
}
private ONOSLLDP(LLDP lldp) {
this.portId = lldp.getPortId();
this.chassisId = lldp.getChassisId();
this.ttl = lldp.getTtl();
this.optionalTLVList = lldp.getOptionalTLVList();
}
public void setName(String name) {
nameTLV.setLength((short) (name.length() + NAME_LENGTH));
nameTLV.setInfoString(name);
nameTLV.setSubType(NAME_SUBTYPE);
nameTLV.setOUI(ONLAB_OUI);
}
public void setDevice(String device) {
deviceTLV.setInfoString(device);
deviceTLV.setLength((short) (device.length() + DEVICE_LENGTH));
deviceTLV.setSubType(DEVICE_SUBTYPE);
deviceTLV.setOUI(ONLAB_OUI);
}
public void setChassisId(final ChassisId chassisId) {
MacAddress chassisMac = MacAddress.valueOf(chassisId.value());
byte[] chassis = ArrayUtils.addAll(new byte[] {CHASSIS_TLV_SUBTYPE},
chassisMac.getAddress());
LLDPTLV chassisTLV = new LLDPTLV();
chassisTLV.setLength(CHASSIS_TLV_SIZE);
chassisTLV.setType(CHASSIS_TLV_TYPE);
chassisTLV.setValue(chassis);
this.setChassisId(chassisTLV);
}
public void setPortId(final int portNumber) {
byte[] port = ArrayUtils.addAll(new byte[] {PORT_TLV_SUBTYPE},
ByteBuffer.allocate(4).putInt(portNumber).array());
LLDPTLV portTLV = new LLDPTLV();
portTLV.setLength(PORT_TLV_SIZE);
portTLV.setType(PORT_TLV_TYPE);
portTLV.setValue(port);
this.setPortId(portTLV);
}
public LLDPOrganizationalTLV getNameTLV() {
for (LLDPTLV tlv : this.getOptionalTLVList()) {
if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
LLDPOrganizationalTLV orgTLV = (LLDPOrganizationalTLV) tlv;
if (orgTLV.getSubType() == NAME_SUBTYPE) {
return orgTLV;
}
}
}
return null;
}
public LLDPOrganizationalTLV getDeviceTLV() {
for (LLDPTLV tlv : this.getOptionalTLVList()) {
if (tlv.getType() == LLDPOrganizationalTLV.ORGANIZATIONAL_TLV_TYPE) {
LLDPOrganizationalTLV orgTLV = (LLDPOrganizationalTLV) tlv;
if (orgTLV.getSubType() == DEVICE_SUBTYPE) {
return orgTLV;
}
}
}
return null;
}
public String getNameString() {
LLDPOrganizationalTLV tlv = getNameTLV();
if (tlv != null) {
return new String(tlv.getInfoString());
}
return null;
}
public String getDeviceString() {
LLDPOrganizationalTLV tlv = getDeviceTLV();
if (tlv != null) {
return new String(tlv.getInfoString());
}
return null;
}
public Integer getPort() {
ByteBuffer portBB = ByteBuffer.wrap(this.getPortId().getValue());
portBB.position(1);
return portBB.getInt();
}
/**
* Given an ethernet packet, determines if this is an LLDP from
* ONOS and returns the device the LLDP came from.
* @param eth an ethernet packet
* @return a the lldp packet or null
*/
public static ONOSLLDP parseONOSLLDP(Ethernet eth) {
if (eth.getEtherType() == Ethernet.TYPE_LLDP ||
eth.getEtherType() == Ethernet.TYPE_BSN) {
ONOSLLDP onosLldp = new ONOSLLDP((LLDP) eth.getPayload()); //(ONOSLLDP) eth.getPayload();
if (ONOSLLDP.DEFAULT_NAME.equals(onosLldp.getNameString())) {
return onosLldp;
}
}
return null;
}
}
package org.onlab.util;
public final class HexString {
private HexString() {
}
/**
* Convert a string of bytes to a ':' separated hex string.
*
* @param bytes
* @return "0f:ca:fe:de:ad:be:ef"
*/
public static String toHexString(final byte[] bytes) {
int i;
StringBuilder ret = new StringBuilder();
String tmp;
for (i = 0; i < bytes.length; i++) {
if (i > 0) {
ret.append(':');
}
tmp = Integer.toHexString((bytes[i] & 0xff));
if (tmp.length() == 1) {
ret.append('0');
}
ret.append(tmp);
}
return ret.toString();
}
public static String toHexString(final long val, final int padTo) {
char[] arr = Long.toHexString(val).toCharArray();
String ret = "";
// prepend the right number of leading zeros
int i = 0;
for (; i < (padTo * 2 - arr.length); i++) {
ret += "0";
if ((i % 2) != 0) {
ret += ":";
}
}
for (int j = 0; j < arr.length; j++) {
ret += arr[j];
if ((((i + j) % 2) != 0) && (j < (arr.length - 1))) {
ret += ":";
}
}
return ret;
}
public static String toHexString(final long val) {
return toHexString(val, 8);
}
/**
* Convert a string of hex values into a string of bytes.
*
* @param values
* "0f:ca:fe:de:ad:be:ef"
* @return [15, 5 ,2, 5, 17]
* @throws NumberFormatException
* If the string can not be parsed
*/
public static byte[] fromHexString(final String values) {
String[] octets = values.split(":");
byte[] ret = new byte[octets.length];
for (int i = 0; i < octets.length; i++) {
if (octets[i].length() > 2) {
throw new NumberFormatException("Invalid octet length");
}
ret[i] = Integer.valueOf(octets[i], 16).byteValue();
}
return ret;
}
public static long toLong(String value) {
String[] octets = value.split(":");
if (octets.length > 8) {
throw new NumberFormatException("Input string is too big to fit in long: " + value);
}
long l = 0;
for (String octet: octets) {
if (octet.length() > 2) {
throw new NumberFormatException(
"Each colon-separated byte component must consist of 1 or 2 hex digits: " + value);
}
short s = Short.parseShort(octet, 16);
l = (l << 8) + s;
}
return l;
}
}
......@@ -13,6 +13,7 @@ import com.esotericsoftware.kryo.io.ByteBufferInput;
import com.esotericsoftware.kryo.io.ByteBufferOutput;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.pool.KryoFactory;
import com.google.common.collect.ImmutableList;
// TODO Add tests for this class.
......@@ -20,7 +21,7 @@ import com.google.common.collect.ImmutableList;
* Pool of Kryo instances, with classes pre-registered.
*/
//@ThreadSafe
public final class KryoPool {
public final class KryoNamespace implements KryoFactory {
/**
* Default buffer size used for serialization.
......@@ -34,7 +35,7 @@ public final class KryoPool {
private final boolean registrationRequired;
/**
* KryoPool builder.
* KryoNamespace builder.
*/
//@NotThreadSafe
public static final class Builder {
......@@ -42,12 +43,12 @@ public final class KryoPool {
private final List<Pair<Class<?>, Serializer<?>>> types = new ArrayList<>();
/**
* Builds a {@link KryoPool} instance.
* Builds a {@link KryoNamespace} instance.
*
* @return KryoPool
* @return KryoNamespace
*/
public KryoPool build() {
return new KryoPool(types);
public KryoNamespace build() {
return new KryoNamespace(types);
}
/**
......@@ -76,19 +77,19 @@ public final class KryoPool {
}
/**
* Registers all the class registered to given KryoPool.
* Registers all the class registered to given KryoNamespace.
*
* @param pool KryoPool
* @param pool KryoNamespace
* @return this
*/
public Builder register(final KryoPool pool) {
public Builder register(final KryoNamespace pool) {
types.addAll(pool.registeredTypes);
return this;
}
}
/**
* Creates a new {@link KryoPool} builder.
* Creates a new {@link KryoNamespace} builder.
*
* @return builder
*/
......@@ -101,7 +102,7 @@ public final class KryoPool {
*
* @param registerdTypes types to register
*/
private KryoPool(final List<Pair<Class<?>, Serializer<?>>> registerdTypes) {
private KryoNamespace(final List<Pair<Class<?>, Serializer<?>>> registerdTypes) {
this.registeredTypes = ImmutableList.copyOf(registerdTypes);
// always true for now
this.registrationRequired = true;
......@@ -113,10 +114,10 @@ public final class KryoPool {
* @param instances to add to the pool
* @return this
*/
public KryoPool populate(int instances) {
public KryoNamespace populate(int instances) {
List<Kryo> kryos = new ArrayList<>(instances);
for (int i = 0; i < instances; ++i) {
kryos.add(newKryoInstance());
kryos.add(create());
}
pool.addAll(kryos);
return this;
......@@ -130,7 +131,7 @@ public final class KryoPool {
public Kryo getKryo() {
Kryo kryo = pool.poll();
if (kryo == null) {
return newKryoInstance();
return create();
}
return kryo;
}
......@@ -235,7 +236,8 @@ public final class KryoPool {
*
* @return Kryo instance
*/
private Kryo newKryoInstance() {
@Override
public Kryo create() {
Kryo kryo = new Kryo();
kryo.setRegistrationRequired(registrationRequired);
for (Pair<Class<?>, Serializer<?>> registry : registeredTypes) {
......
package org.onlab.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Utilities for testing.
*/
public final class TestUtils {
/**
* Sets the field, bypassing scope restriction.
*
* @param subject Object where the field belongs
* @param fieldName name of the field to set
* @param value value to set to the field.
* @param <T> subject type
* @param <U> value type
* @throws TestUtilsException if there are reflection errors while setting
* the field
*/
public static <T, U> void setField(T subject, String fieldName, U value)
throws TestUtilsException {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) subject.getClass();
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(subject, value);
} catch (NoSuchFieldException | SecurityException |
IllegalArgumentException | IllegalAccessException e) {
throw new TestUtilsException("setField failed", e);
}
}
/**
* Gets the field, bypassing scope restriction.
*
* @param subject Object where the field belongs
* @param fieldName name of the field to get
* @return value of the field.
* @param <T> subject type
* @param <U> field value type
* @throws TestUtilsException if there are reflection errors while getting
* the field
*/
public static <T, U> U getField(T subject, String fieldName)
throws TestUtilsException {
try {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) subject.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
@SuppressWarnings("unchecked")
U result = (U) field.get(subject);
return result;
} catch (NoSuchFieldException | SecurityException |
IllegalArgumentException | IllegalAccessException e) {
throw new TestUtilsException("getField failed", e);
}
}
/**
* Calls the method, bypassing scope restriction.
*
* @param subject Object where the method belongs
* @param methodName name of the method to call
* @param paramTypes formal parameter type array
* @param args arguments
* @return return value or null if void
* @param <T> subject type
* @param <U> return value type
* @throws TestUtilsException if there are reflection errors while calling
* the method
*/
public static <T, U> U callMethod(T subject, String methodName,
Class<?>[] paramTypes, Object...args) throws TestUtilsException {
try {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) subject.getClass();
final Method method;
if (paramTypes == null || paramTypes.length == 0) {
method = clazz.getDeclaredMethod(methodName);
} else {
method = clazz.getDeclaredMethod(methodName, paramTypes);
}
method.setAccessible(true);
@SuppressWarnings("unchecked")
U result = (U) method.invoke(subject, args);
return result;
} catch (NoSuchMethodException | SecurityException |
IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
throw new TestUtilsException("callMethod failed", e);
}
}
/**
* Calls the method, bypassing scope restriction.
*
* @param subject Object where the method belongs
* @param methodName name of the method to call
* @param paramType formal parameter type
* @param arg argument
* @return return value or null if void
* @param <T> subject type
* @param <U> return value type
* @throws TestUtilsException if there are reflection errors while calling
* the method
*/
public static <T, U> U callMethod(T subject, String methodName,
Class<?> paramType, Object arg) throws TestUtilsException {
return callMethod(subject, methodName, new Class<?>[]{paramType}, arg);
}
/**
* Triggers an allocation of an object of type <T> and forces a call to
* the private constructor.
*
* @param constructor Constructor to call
* @param <T> type of the object to create
* @return created object of type <T>
* @throws TestUtilsException if there are reflection errors while calling
* the constructor
*/
public static <T> T callConstructor(Constructor<T> constructor)
throws TestUtilsException {
try {
constructor.setAccessible(true);
return constructor.newInstance();
} catch (InstantiationException | IllegalAccessException |
InvocationTargetException error) {
throw new TestUtilsException("callConstructor failed", error);
}
}
/**
* Avoid instantiation.
*/
private TestUtils() {}
/**
* Exception that can be thrown if problems are encountered while executing
* the utility method. These are usually problems accessing fields/methods
* through reflection. The original exception can be found by examining the
* cause.
*/
public static class TestUtilsException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Constructs a new exception with the specified detail message and
* cause.
*
* @param message the detail message
* @param cause the original cause of this exception
*/
public TestUtilsException(String message, Throwable cause) {
super(message, cause);
}
}
}
package org.onlab.util;
import org.junit.Test;
import com.esotericsoftware.minlog.Log;
import junit.framework.TestCase;
/**
* Test of the Hexstring.
*
*/
public class HexStringTest extends TestCase {
@Test
public void testMarshalling() throws Exception {
String dpidStr = "00:00:00:23:20:2d:16:71";
long dpid = HexString.toLong(dpidStr);
String testStr = HexString.toHexString(dpid);
TestCase.assertEquals(dpidStr, testStr);
}
@Test
public void testToLong() {
String dpidStr = "3e:1f:01:fc:72:8c:63:31";
long valid = 0x3e1f01fc728c6331L;
long testLong = HexString.toLong(dpidStr);
TestCase.assertEquals(valid, testLong);
}
@Test
public void testToLongMSB() {
String dpidStr = "ca:7c:5e:d1:64:7a:95:9b";
long valid = -3856102927509056101L;
long testLong = HexString.toLong(dpidStr);
TestCase.assertEquals(valid, testLong);
}
@Test
public void testToLongError() {
String dpidStr = "09:08:07:06:05:04:03:02:01";
try {
HexString.toLong(dpidStr);
fail("HexString.toLong() should have thrown a NumberFormatException");
} catch (NumberFormatException expected) {
Log.info("HexString.toLong() have thrown a NumberFormatException");
}
}
@Test
public void testToStringBytes() {
byte[] dpid = {0, 0, 0, 0, 0, 0, 0, -1 };
String valid = "00:00:00:00:00:00:00:ff";
String testString = HexString.toHexString(dpid);
TestCase.assertEquals(valid, testString);
}
@Test
public void testFromHexStringError() {
String invalidStr = "00:00:00:00:00:00:ffff";
try {
HexString.fromHexString(invalidStr);
fail("HexString.fromHexString() should have thrown a NumberFormatException");
} catch (NumberFormatException expected) {
Log.info("HexString.toLong() have thrown a NumberFormatException");
}
}
}
package org.onlab.util;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Before;
import org.junit.Test;
import org.onlab.util.TestUtils.TestUtilsException;
/**
* Test and usage examples for TestUtils.
*/
public class TestUtilsTest {
/**
* Test data.
*/
private static final class TestClass {
@SuppressWarnings("unused")
private int privateField = 42;
@SuppressWarnings("unused")
protected int protectedField = 2501; // CHECKSTYLE IGNORE THIS LINE
/**
* Protected method with multiple argument.
*
* @param x simply returns
* @param y not used
* @return x
*/
@SuppressWarnings("unused")
private int privateMethod(Number x, Long y) {
return x.intValue();
}
/**
* Protected method with no argument.
*
* @return int
*/
@SuppressWarnings("unused")
protected int protectedMethod() {
return 42;
}
/**
* Method returning array.
*
* @param ary random array
* @return ary
*/
@SuppressWarnings("unused")
private int[] arrayReturnMethod(int[] ary) {
return ary;
}
/**
* Method without return value.
*
* @param s ignored
*/
@SuppressWarnings("unused")
private void voidMethod(String s) {
System.out.println(s);
}
}
private TestClass test;
/**
* Sets up the test fixture.
*/
@Before
public void setUp() {
test = new TestClass();
}
/**
* Example to access private field.
*
* @throws TestUtilsException TestUtils error
*/
@Test
public void testSetGetPrivateField() throws TestUtilsException {
assertEquals(42, TestUtils.getField(test, "privateField"));
TestUtils.setField(test, "privateField", 0xDEAD);
assertEquals(0xDEAD, TestUtils.getField(test, "privateField"));
}
/**
* Example to access protected field.
*
* @throws TestUtilsException TestUtils error
*/
@Test
public void testSetGetProtectedField() throws TestUtilsException {
assertEquals(2501, TestUtils.getField(test, "protectedField"));
TestUtils.setField(test, "protectedField", 0xBEEF);
assertEquals(0xBEEF, TestUtils.getField(test, "protectedField"));
}
/**
* Example to call private method and multiple parameters.
* <p/>
* It also illustrates that paramTypes must match declared type,
* not the runtime types of arguments.
*
* @throws TestUtilsException TestUtils error
*/
@Test
public void testCallPrivateMethod() throws TestUtilsException {
int result = TestUtils.callMethod(test, "privateMethod",
new Class<?>[] {Number.class, Long.class},
Long.valueOf(42), Long.valueOf(32));
assertEquals(42, result);
}
/**
* Example to call protected method and no parameters.
*
* @throws TestUtilsException TestUtils error
*/
@Test
public void testCallProtectedMethod() throws TestUtilsException {
int result = TestUtils.callMethod(test, "protectedMethod",
new Class<?>[] {});
assertEquals(42, result);
}
/**
* Example to call method returning array.
* <p/>
* Note: It is not required to receive as Object.
* Following is just verifying it is not Boxed arrays.
*
* @throws TestUtilsException TestUtils error
*/
@Test
public void testCallArrayReturnMethod() throws TestUtilsException {
int[] array = {1, 2, 3};
Object aryResult = TestUtils.callMethod(test, "arrayReturnMethod",
new Class<?>[] {int[].class}, array);
assertEquals(int[].class, aryResult.getClass());
assertArrayEquals(array, (int[]) aryResult);
}
/**
* Example to call void returning method.
* <p/>
* Note: Return value will be null for void methods.
*
* @throws TestUtilsException TestUtils error
*/
@Test
public void testCallVoidReturnMethod() throws TestUtilsException {
Object voidResult = TestUtils.callMethod(test, "voidMethod",
String.class, "foobar");
assertNull(voidResult);
}
}
package org.onlab.netty;
import org.onlab.util.KryoPool;
import org.onlab.util.KryoNamespace;
import java.nio.ByteBuffer;
import java.util.ArrayList;
......@@ -12,7 +12,7 @@ import java.util.HashMap;
*/
public class KryoSerializer {
private KryoPool serializerPool;
private KryoNamespace serializerPool;
public KryoSerializer() {
setupKryoPool();
......@@ -23,7 +23,7 @@ public class KryoSerializer {
*/
protected void setupKryoPool() {
// FIXME Slice out types used in common to separate pool/namespace.
serializerPool = KryoPool.newBuilder()
serializerPool = KryoNamespace.newBuilder()
.register(ArrayList.class,
HashMap.class,
ArrayList.class,
......
......@@ -23,6 +23,7 @@
<module>nio</module>
<module>osgi</module>
<module>rest</module>
<module>thirdparty</module>
</modules>
<dependencies>
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-utils</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onlab-thirdparty</artifactId>
<packaging>bundle</packaging>
<description>ONLab third-party dependencies</description>
<dependencies>
<dependency>
<groupId>com.googlecode.concurrent-trees</groupId>
<artifactId>concurrent-trees</artifactId>
<version>2.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<filters>
<filter>
<artifact>com.googlecode.concurrent-trees:concurrent-trees</artifact>
<includes>
<include>com/googlecode/**</include>
</includes>
</filter>
<filter>
<artifact>com.google.guava:guava</artifact>
<excludes>
<exclude>**</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Export-Package>
com.googlecode.concurrenttrees.*
</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
package org.onlab.thirdparty;
/**
* Empty class required to get the onlab-thirdparty module to build properly.
* <p/>
* TODO Figure out how to remove this.
*/
public class OnlabThirdparty {
}
......@@ -2,6 +2,9 @@
<html>
<head>
<title>ONOS GUI</title>
<script src="libs/d3.min.js"></script>
<script src="libs/jquery-2.1.1.min.js"></script>
</head>
<body>
<h1>ONOS GUI</h1>
......