Madan Jampani

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

Showing 117 changed files with 2225 additions and 574 deletions
......@@ -8,5 +8,6 @@
.checkstyle
target
*.iml
*.pyc
dependency-reduced-pom.xml
.idea
......
......@@ -16,14 +16,24 @@
package org.onlab.onos.calendar;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.intent.Intent;
import org.onlab.onos.net.intent.IntentEvent;
import org.onlab.onos.net.intent.IntentId;
import org.onlab.onos.net.intent.IntentListener;
import org.onlab.onos.net.intent.IntentService;
import org.onlab.onos.net.intent.IntentState;
import org.onlab.rest.BaseResource;
import javax.ws.rs.POST;
import javax.ws.rs.DELETE;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import org.onlab.onos.core.ApplicationId;
import org.onlab.onos.core.CoreService;
import org.onlab.onos.net.flow.DefaultTrafficSelector;
......@@ -31,10 +41,15 @@ import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.intent.PointToPointIntent;
import org.onlab.packet.Ethernet;
import static org.onlab.onos.net.PortNumber.portNumber;
import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
import static org.onlab.onos.net.intent.IntentState.FAILED;
import static org.onlab.onos.net.intent.IntentState.INSTALLED;
import static org.onlab.onos.net.intent.IntentState.WITHDRAWN;
import static org.slf4j.LoggerFactory.getLogger;
import org.slf4j.Logger;
/**
......@@ -44,6 +59,7 @@ import org.slf4j.Logger;
public class BandwidthCalendarResource extends BaseResource {
private static final Logger log = getLogger(BandwidthCalendarResource.class);
private static final long TIMEOUT = 5; // seconds
@javax.ws.rs.Path("/{src}/{dst}/{srcPort}/{dstPort}/{bandwidth}")
@POST
......@@ -55,7 +71,7 @@ public class BandwidthCalendarResource extends BaseResource {
log.info("Receiving Create Intent request...");
log.info("Path Constraints: Src = {} SrcPort = {} Dest = {} DestPort = {} BW = {}",
src, srcPort, dst, dstPort, bandwidth);
src, srcPort, dst, dstPort, bandwidth);
IntentService service = get(IntentService.class);
......@@ -66,36 +82,50 @@ public class BandwidthCalendarResource extends BaseResource {
TrafficTreatment treatment = builder().build();
PointToPointIntent intentP2P =
new PointToPointIntent(appId(), selector, treatment,
srcPoint, dstPoint);
service.submit(intentP2P);
log.info("Submitted Calendar App intent: src = " + src + "dest = " + dst
+ "srcPort = " + srcPort + "destPort" + dstPort + "intentID = " + intentP2P.id().toString());
String reply = intentP2P.id().toString() + "\n";
new PointToPointIntent(appId(), selector, treatment,
srcPoint, dstPoint);
return Response.ok(reply).build();
CountDownLatch latch = new CountDownLatch(1);
InternalIntentListener listener = new InternalIntentListener(intentP2P, service, latch);
service.addListener(listener);
service.submit(intentP2P);
try {
if (latch.await(TIMEOUT, TimeUnit.SECONDS)) {
log.info("Submitted Calendar App intent: src = {}; dst = {}; " +
"srcPort = {}; dstPort = {}; intentID = {}",
src, dst, srcPort, dstPort, intentP2P.id());
String reply = intentP2P.id() + " " + listener.getState() + "\n";
return Response.ok(reply).build();
}
} catch (InterruptedException e) {
log.warn("Interrupted while waiting for intent {} status", intentP2P.id());
}
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
@javax.ws.rs.Path("/cancellation/{intentId}")
@DELETE
public Response withdrawIntent(@PathParam("intentId") String intentId) {
log.info("Receiving Teardown request...");
log.info("Withdraw intentId = {} ", intentId);
String reply = "ok\n";
return Response.ok(reply).build();
log.info("Receiving Teardown request for {}", intentId);
IntentService service = get(IntentService.class);
Intent intent = service.getIntent(IntentId.valueOf(Long.parseLong(intentId)));
if (intent != null) {
service.withdraw(intent);
String reply = "ok\n";
return Response.ok(reply).build();
}
return Response.status(Response.Status.NOT_FOUND).build();
}
@javax.ws.rs.Path("/modification/{intentId}/{bandwidth}")
@POST
public Response modifyBandwidth(@PathParam("intentId") String intentId,
@PathParam("bandwidth") String bandwidth) {
@PathParam("bandwidth") String bandwidth) {
log.info("Receiving Modify request...");
log.info("Modify bw for intentId = {} with new bandwidth = {}", intentId, bandwidth);
String reply = "ok\n";
String reply = "ok\n";
return Response.ok(reply).build();
}
......@@ -115,4 +145,34 @@ public class BandwidthCalendarResource extends BaseResource {
protected ApplicationId appId() {
return get(CoreService.class).registerApplication("org.onlab.onos.calendar");
}
// Auxiliary listener to wait until the given intent reaches the installed or failed states.
private final class InternalIntentListener implements IntentListener {
private final Intent intent;
private final IntentService service;
private final CountDownLatch latch;
private IntentState state;
private InternalIntentListener(Intent intent, IntentService service,
CountDownLatch latch) {
this.intent = intent;
this.service = service;
this.latch = latch;
}
@Override
public void event(IntentEvent event) {
if (event.subject().equals(intent)) {
state = service.getIntentState(intent.id());
if (state == INSTALLED || state == FAILED || state == WITHDRAWN) {
latch.countDown();
}
service.removeListener(this);
}
}
public IntentState getState() {
return state;
}
}
}
......
......@@ -50,7 +50,10 @@ public class NetworkConfigReader {
private final Logger log = getLogger(getClass());
private static final String DEFAULT_CONFIG_FILE = "config/addresses.json";
// Current working dir seems to be /opt/onos/apache-karaf-3.0.2
// TODO: Set the path to /opt/onos/config
private static final String CONFIG_DIR = "../config";
private static final String DEFAULT_CONFIG_FILE = "addresses.json";
private String configFileName = DEFAULT_CONFIG_FILE;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
......@@ -60,52 +63,9 @@ public class NetworkConfigReader {
protected void activate() {
log.info("Started network config reader");
log.info("Config file set to {}", configFileName);
AddressConfiguration config = readNetworkConfig();
if (config != null) {
for (AddressEntry entry : config.getAddresses()) {
ConnectPoint cp = new ConnectPoint(
DeviceId.deviceId(dpidToUri(entry.getDpid())),
PortNumber.portNumber(entry.getPortNumber()));
Set<InterfaceIpAddress> interfaceIpAddresses = new HashSet<>();
for (String strIp : entry.getIpAddresses()) {
// Get the IP address and the subnet mask length
try {
String[] splits = strIp.split("/");
if (splits.length != 2) {
throw new IllegalArgumentException("Invalid IP address and prefix length format");
}
// NOTE: IpPrefix will mask-out the bits after the prefix length.
IpPrefix subnet = IpPrefix.valueOf(strIp);
IpAddress addr = IpAddress.valueOf(splits[0]);
InterfaceIpAddress ia =
new InterfaceIpAddress(addr, subnet);
interfaceIpAddresses.add(ia);
} catch (IllegalArgumentException e) {
log.warn("Bad format for IP address in config: {}", strIp);
}
}
MacAddress macAddress = null;
if (entry.getMacAddress() != null) {
try {
macAddress = MacAddress.valueOf(entry.getMacAddress());
} catch (IllegalArgumentException e) {
log.warn("Bad format for MAC address in config: {}",
entry.getMacAddress());
}
}
PortAddresses addresses = new PortAddresses(cp,
interfaceIpAddresses, macAddress);
hostAdminService.bindAddressesToPort(addresses);
}
applyNetworkConfig(config);
}
}
......@@ -114,12 +74,17 @@ public class NetworkConfigReader {
log.info("Stopped");
}
/**
* Reads the network configuration.
*
* @return the network configuration on success, otherwise null
*/
private AddressConfiguration readNetworkConfig() {
File configFile = new File(configFileName);
File configFile = new File(CONFIG_DIR, configFileName);
ObjectMapper mapper = new ObjectMapper();
try {
log.info("Loading config: {}", configFile.getAbsolutePath());
AddressConfiguration config =
mapper.readValue(configFile, AddressConfiguration.class);
......@@ -127,12 +92,58 @@ public class NetworkConfigReader {
} catch (FileNotFoundException e) {
log.warn("Configuration file not found: {}", configFileName);
} catch (IOException e) {
log.error("Unable to read config from file:", e);
log.error("Error loading configuration", e);
}
return null;
}
/**
* Applies the network configuration.
*
* @param config the network configuration to apply
*/
private void applyNetworkConfig(AddressConfiguration config) {
for (AddressEntry entry : config.getAddresses()) {
ConnectPoint cp = new ConnectPoint(
DeviceId.deviceId(dpidToUri(entry.getDpid())),
PortNumber.portNumber(entry.getPortNumber()));
Set<InterfaceIpAddress> interfaceIpAddresses = new HashSet<>();
for (String strIp : entry.getIpAddresses()) {
// Get the IP address and the subnet mask length
try {
String[] splits = strIp.split("/");
if (splits.length != 2) {
throw new IllegalArgumentException("Invalid IP address and prefix length format");
}
// NOTE: IpPrefix will mask-out the bits after the prefix length.
IpPrefix subnet = IpPrefix.valueOf(strIp);
IpAddress addr = IpAddress.valueOf(splits[0]);
InterfaceIpAddress ia =
new InterfaceIpAddress(addr, subnet);
interfaceIpAddresses.add(ia);
} catch (IllegalArgumentException e) {
log.warn("Bad format for IP address in config: {}", strIp);
}
}
MacAddress macAddress = null;
if (entry.getMacAddress() != null) {
try {
macAddress = MacAddress.valueOf(entry.getMacAddress());
} catch (IllegalArgumentException e) {
log.warn("Bad format for MAC address in config: {}",
entry.getMacAddress());
}
}
PortAddresses addresses = new PortAddresses(cp,
interfaceIpAddresses, macAddress);
hostAdminService.bindAddressesToPort(addresses);
}
}
private static String dpidToUri(String dpid) {
return "of:" + dpid.replace(":", "");
}
......
{
"interfaces" : [
{
"dpid" : "00:00:00:00:00:00:01",
"port" : "1",
"ips" : ["192.168.10.101/24"],
"mac" : "00:00:00:11:22:33"
},
{
"dpid" : "00:00:00:00:00:00:02",
"port" : "1",
"ips" : ["192.168.20.101/24", "192.168.30.101/24"]
},
{
"dpid" : "00:00:00:00:00:00:03",
"port" : "1",
"ips" : ["10.1.0.1/16"],
"mac" : "00:00:00:00:00:01"
}
]
}
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<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-demo</artifactId>
<packaging>bundle</packaging>
<description>ONOS demo app bundle</description>
<properties>
<web.context>/onos/demo</web.context>
</properties>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onlab-rest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onlab.onos</groupId>
<artifactId>onos-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>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>
</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.slf4j,
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,
com.fasterxml.jackson.databind,
com.fasterxml.jackson.databind.node,
com.google.common.*,
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.demo;
/**
* Simple demo api interface.
*/
public interface DemoAPI {
enum InstallType { MESH, RANDOM };
/**
* Installs intents based on the installation type.
* @param type the installation type.
*/
void setup(InstallType type);
/**
* Uninstalls all existing intents.
*/
void tearDown();
}
/*
* 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.demo;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
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.core.ApplicationId;
import org.onlab.onos.core.CoreService;
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.host.HostService;
import org.onlab.onos.net.intent.HostToHostIntent;
import org.onlab.onos.net.intent.Intent;
import org.onlab.onos.net.intent.IntentService;
import org.slf4j.Logger;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Application to set up demos.
*/
@Component(immediate = true)
@Service
public class DemoInstaller implements DemoAPI {
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected IntentService intentService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
private ExecutorService worker;
private ApplicationId appId;
private final Set<Intent> existingIntents = new HashSet<>();
@Activate
public void activate() {
appId = coreService.registerApplication("org.onlab.onos.demo.installer");
worker = Executors.newFixedThreadPool(1,
new ThreadFactoryBuilder()
.setNameFormat("demo-app-worker")
.build());
log.info("Started with Application ID {}", appId.id());
}
@Deactivate
public void deactivate() {
worker.shutdownNow();
log.info("Stopped");
}
@Override
public void setup(InstallType type) {
switch (type) {
case MESH:
log.debug("Installing mesh intents");
worker.execute(new MeshInstaller());
break;
case RANDOM:
throw new IllegalArgumentException("Not yet implemented.");
default:
throw new IllegalArgumentException("What is it you want exactly?");
}
}
@Override
public void tearDown() {
worker.submit(new UnInstaller());
}
private class MeshInstaller implements Runnable {
@Override
public void run() {
TrafficSelector selector = DefaultTrafficSelector.builder().build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
List<Host> hosts = Lists.newArrayList(hostService.getHosts());
while (!hosts.isEmpty()) {
Host src = hosts.remove(0);
for (Host dst : hosts) {
HostToHostIntent intent = new HostToHostIntent(appId, src.id(), dst.id(),
selector, treatment,
null);
existingIntents.add(intent);
intentService.submit(intent);
}
}
}
}
private class UnInstaller implements Runnable {
@Override
public void run() {
for (Intent i : existingIntents) {
intentService.withdraw(i);
}
}
}
}
package org.onlab.onos.demo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.onlab.rest.BaseResource;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
/**
* Rest API for demos.
*/
@Path("intents")
public class DemoResource extends BaseResource {
@POST
@Path("setup")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response setup(InputStream input) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode cfg = mapper.readTree(input);
if (!cfg.has("type")) {
return Response.status(Response.Status.BAD_REQUEST)
.entity("Expected type field containing either mesh or random.").build();
}
DemoAPI.InstallType type = DemoAPI.InstallType.valueOf(
cfg.get("type").asText().toUpperCase());
DemoAPI demo = get(DemoAPI.class);
demo.setup(type);
return Response.ok(mapper.createObjectNode().toString()).build();
}
@GET
@Path("teardown")
@Produces(MediaType.APPLICATION_JSON)
public Response tearDown() throws IOException {
ObjectMapper mapper = new ObjectMapper();
DemoAPI demo = get(DemoAPI.class);
demo.tearDown();
return Response.ok(mapper.createObjectNode().toString()).build();
}
}
/*
* 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.
*/
/**
* Demo applications live here.
*/
package org.onlab.onos.demo;
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<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 DEMO APP API v1.0</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.resourceConfigClass</param-name>
<param-value>com.sun.jersey.api.core.ClassNamesResourceConfig</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.config.property.classnames</param-name>
<param-value>
org.onlab.onos.demo.DemoResource
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JAX-RS Service</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
\ No newline at end of file
......@@ -44,6 +44,7 @@
<module>optical</module>
<module>metrics</module>
<module>oecfg</module>
<module>demo</module>
</modules>
<properties>
......
......@@ -37,24 +37,31 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*/
public class SdnIpConfigReader implements SdnIpConfigService {
private static final Logger log = LoggerFactory.getLogger(SdnIpConfigReader.class);
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String DEFAULT_CONFIG_FILE = "config/sdnip.json";
// Current working dir seems to be /opt/onos/apache-karaf-3.0.2
// TODO: Set the path to /opt/onos/config
private static final String CONFIG_DIR = "../config";
private static final String DEFAULT_CONFIG_FILE = "sdnip.json";
private String configFileName = DEFAULT_CONFIG_FILE;
private Map<String, BgpSpeaker> bgpSpeakers = new ConcurrentHashMap<>();
private Map<IpAddress, BgpPeer> bgpPeers = new ConcurrentHashMap<>();
/**
* Reads the info contained in the configuration file.
* Reads SDN-IP related information contained in the configuration file.
*
* @param configFilename The name of configuration file for SDN-IP application.
* @param configFilename the name of the configuration file for the SDN-IP
* application
*/
private void readConfiguration(String configFilename) {
File gatewaysFile = new File(configFilename);
File configFile = new File(CONFIG_DIR, configFilename);
ObjectMapper mapper = new ObjectMapper();
try {
Configuration config = mapper.readValue(gatewaysFile, Configuration.class);
log.info("Loading config: {}", configFile.getAbsolutePath());
Configuration config = mapper.readValue(configFile,
Configuration.class);
for (BgpSpeaker speaker : config.getBgpSpeakers()) {
bgpSpeakers.put(speaker.name(), speaker);
}
......@@ -64,13 +71,11 @@ public class SdnIpConfigReader implements SdnIpConfigService {
} catch (FileNotFoundException e) {
log.warn("Configuration file not found: {}", configFileName);
} catch (IOException e) {
log.error("Error reading JSON file", e);
log.error("Error loading configuration", e);
}
}
public void init() {
log.debug("Config file set to {}", configFileName);
readConfiguration(configFileName);
}
......
ONOS looks for these config files by default in $KARAF_LOG/config/
\ No newline at end of file
The SDN-IP configuration files should be copied to directory
$ONOS_HOME/tools/package/config
After deployment and starting up the ONOS cluster, ONOS looks for these
configuration files in /opt/onos/config on each cluster member.
......
......@@ -27,6 +27,7 @@ import org.onlab.onos.net.intent.constraint.BandwidthConstraint;
import org.onlab.onos.net.intent.constraint.LambdaConstraint;
import org.onlab.onos.net.resource.Bandwidth;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import static com.google.common.base.Strings.isNullOrEmpty;
......@@ -48,6 +49,26 @@ public abstract class ConnectivityIntentCommand extends AbstractShellCommand {
required = false, multiValued = false)
private String ethTypeString = "";
@Option(name = "--ipProto", description = "IP Protocol",
required = false, multiValued = false)
private String ipProtoString = null;
@Option(name = "--ipSrc", description = "Source IP Address",
required = false, multiValued = false)
private String srcIpString = null;
@Option(name = "--ipDst", description = "Destination IP Address",
required = false, multiValued = false)
private String dstIpString = null;
@Option(name = "--tcpSrc", description = "Source TCP Port",
required = false, multiValued = false)
private String srcTcpString = null;
@Option(name = "--tcpDst", description = "Destination TCP Port",
required = false, multiValued = false)
private String dstTcpString = null;
@Option(name = "-b", aliases = "--bandwidth", description = "Bandwidth",
required = false, multiValued = false)
private String bandwidthString = "";
......@@ -79,6 +100,26 @@ public abstract class ConnectivityIntentCommand extends AbstractShellCommand {
selectorBuilder.matchEthDst(MacAddress.valueOf(dstMacString));
}
if (!isNullOrEmpty(ipProtoString)) {
selectorBuilder.matchIPProtocol((byte) Short.parseShort(ipProtoString));
}
if (!isNullOrEmpty(srcIpString)) {
selectorBuilder.matchIPSrc(IpPrefix.valueOf(srcIpString));
}
if (!isNullOrEmpty(dstIpString)) {
selectorBuilder.matchIPDst(IpPrefix.valueOf(dstIpString));
}
if (!isNullOrEmpty(srcTcpString)) {
selectorBuilder.matchTcpSrc((short) Integer.parseInt(srcTcpString));
}
if (!isNullOrEmpty(dstTcpString)) {
selectorBuilder.matchTcpSrc((short) Integer.parseInt(dstTcpString));
}
return selectorBuilder.build();
}
......
/*
* 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.net;
/**
* Collection of keys for annotation.
* Definitions of annotation keys needs to be here to avoid scattering.
*/
public final class AnnotationKeys {
// Prohibit instantiation
private AnnotationKeys() {}
/**
* Annotation key for latency.
*/
public static final String LATENCY = "latency";
/**
* Returns the value annotated object for the specified annotation key.
* The annotated value is expected to be String that can be parsed as double.
* If parsing fails, the returned value will be 1.0.
*
* @param annotated annotated object whose annotated value is obtained
* @param key key of annotation
* @return double value of annotated object for the specified key
*/
public static double getAnnotatedValue(Annotated annotated, String key) {
double value;
try {
value = Double.parseDouble(annotated.annotations().value(key));
} catch (NumberFormatException e) {
value = 1.0;
}
return value;
}
}
......@@ -80,6 +80,7 @@ public class DefaultFlowEntry extends DefaultFlowRule
this.state = FlowEntryState.FAILED;
this.errType = errType;
this.errCode = errCode;
this.lastSeen = System.currentTimeMillis();
}
@Override
......
......@@ -58,7 +58,7 @@ public class DefaultFlowRule implements FlowRule {
}
public DefaultFlowRule(DeviceId deviceId, TrafficSelector selector,
TrafficTreatment treatement, int priority, ApplicationId appId,
TrafficTreatment treatment, int priority, ApplicationId appId,
int timeout, boolean permanent) {
if (priority < FlowRule.MIN_PRIORITY) {
......@@ -68,7 +68,7 @@ public class DefaultFlowRule implements FlowRule {
this.deviceId = deviceId;
this.priority = priority;
this.selector = selector;
this.treatment = treatement;
this.treatment = treatment;
this.appId = appId.id();
this.timeout = timeout;
this.permanent = permanent;
......
......@@ -63,7 +63,7 @@ public final class DefaultTrafficSelector implements TrafficSelector {
@Override
public int hashCode() {
return Objects.hash(criteria);
return criteria.hashCode();
}
@Override
......
......@@ -18,7 +18,7 @@ package org.onlab.onos.net.flow;
import org.onlab.onos.core.ApplicationId;
import org.onlab.onos.net.provider.Provider;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.Future;
/**
* Abstraction of a flow rule provider.
......@@ -58,6 +58,6 @@ public interface FlowRuleProvider extends Provider {
* @param batch a batch of flow rules
* @return a future indicating the status of this execution
*/
ListenableFuture<CompletedBatchOperation> executeBatch(BatchOperation<FlowRuleBatchEntry> batch);
Future<CompletedBatchOperation> executeBatch(BatchOperation<FlowRuleBatchEntry> batch);
}
......
......@@ -196,7 +196,7 @@ public final class Criteria {
@Override
public int hashCode() {
return Objects.hash(port, type());
return Objects.hash(type(), port);
}
@Override
......@@ -242,7 +242,7 @@ public final class Criteria {
@Override
public int hashCode() {
return Objects.hash(mac, type);
return Objects.hash(type, mac);
}
@Override
......@@ -288,7 +288,7 @@ public final class Criteria {
@Override
public int hashCode() {
return Objects.hash(ethType, type());
return Objects.hash(type(), ethType);
}
@Override
......@@ -336,7 +336,7 @@ public final class Criteria {
@Override
public int hashCode() {
return Objects.hash(ip, type);
return Objects.hash(type, ip);
}
@Override
......@@ -382,7 +382,7 @@ public final class Criteria {
@Override
public int hashCode() {
return Objects.hash(proto, type());
return Objects.hash(type(), proto);
}
@Override
......@@ -427,7 +427,7 @@ public final class Criteria {
@Override
public int hashCode() {
return Objects.hash(vlanPcp);
return Objects.hash(type(), vlanPcp);
}
@Override
......@@ -474,7 +474,7 @@ public final class Criteria {
@Override
public int hashCode() {
return Objects.hash(vlanId, type());
return Objects.hash(type(), vlanId);
}
@Override
......@@ -522,7 +522,7 @@ public final class Criteria {
@Override
public int hashCode() {
return Objects.hash(tcpPort, type);
return Objects.hash(type, tcpPort);
}
@Override
......@@ -568,7 +568,7 @@ public final class Criteria {
@Override
public int hashCode() {
return Objects.hash(lambda, type);
return Objects.hash(type, lambda);
}
@Override
......@@ -612,7 +612,7 @@ public final class Criteria {
@Override
public int hashCode() {
return Objects.hash(signalType, type);
return Objects.hash(type, signalType);
}
@Override
......
......@@ -190,7 +190,7 @@ public final class Instructions {
@Override
public int hashCode() {
return Objects.hash(port, type());
return Objects.hash(type(), port);
}
@Override
......
......@@ -70,7 +70,7 @@ public abstract class L0ModificationInstruction implements Instruction {
@Override
public int hashCode() {
return Objects.hash(lambda, type(), subtype);
return Objects.hash(type(), subtype, lambda);
}
@Override
......
......@@ -93,7 +93,7 @@ public abstract class L2ModificationInstruction implements Instruction {
@Override
public int hashCode() {
return Objects.hash(mac, type(), subtype);
return Objects.hash(type(), subtype, mac);
}
@Override
......@@ -142,7 +142,7 @@ public abstract class L2ModificationInstruction implements Instruction {
@Override
public int hashCode() {
return Objects.hash(vlanId, type(), subtype());
return Objects.hash(type(), subtype(), vlanId);
}
@Override
......@@ -191,7 +191,7 @@ public abstract class L2ModificationInstruction implements Instruction {
@Override
public int hashCode() {
return Objects.hash(vlanPcp, type(), subtype());
return Objects.hash(type(), subtype(), vlanPcp);
}
@Override
......
......@@ -85,7 +85,7 @@ public abstract class L3ModificationInstruction implements Instruction {
@Override
public int hashCode() {
return Objects.hash(ip, type(), subtype());
return Objects.hash(type(), subtype(), ip);
}
@Override
......
......@@ -23,6 +23,7 @@ import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
......@@ -61,7 +62,7 @@ public abstract class ConnectivityIntent extends Intent {
Collection<NetworkResource> resources,
TrafficSelector selector,
TrafficTreatment treatment) {
this(id, appId, resources, selector, treatment, null);
this(id, appId, resources, selector, treatment, Collections.emptyList());
}
/**
......@@ -87,7 +88,7 @@ public abstract class ConnectivityIntent extends Intent {
super(id, appId, resources);
this.selector = checkNotNull(selector);
this.treatment = checkNotNull(treatment);
this.constraints = constraints;
this.constraints = checkNotNull(constraints);
}
/**
......@@ -97,7 +98,7 @@ public abstract class ConnectivityIntent extends Intent {
super();
this.selector = null;
this.treatment = null;
this.constraints = null;
this.constraints = Collections.emptyList();
}
/**
......
......@@ -21,6 +21,7 @@ import org.onlab.onos.net.HostId;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import java.util.Collections;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
......@@ -46,7 +47,7 @@ public final class HostToHostIntent extends ConnectivityIntent {
public HostToHostIntent(ApplicationId appId, HostId one, HostId two,
TrafficSelector selector,
TrafficTreatment treatment) {
this(appId, one, two, selector, treatment, null);
this(appId, one, two, selector, treatment, Collections.emptyList());
}
/**
......
......@@ -22,6 +22,7 @@ import org.onlab.onos.net.Link;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import java.util.Collections;
import java.util.List;
import java.util.Set;
......@@ -51,7 +52,7 @@ public final class LinkCollectionIntent extends ConnectivityIntent {
TrafficTreatment treatment,
Set<Link> links,
ConnectPoint egressPoint) {
this(appId, selector , treatment, links, egressPoint, null);
this(appId, selector , treatment, links, egressPoint, Collections.emptyList());
}
/**
......
......@@ -22,6 +22,7 @@ import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import java.util.Collections;
import java.util.List;
import java.util.Set;
......@@ -55,14 +56,7 @@ public final class MultiPointToSinglePointIntent extends ConnectivityIntent {
TrafficTreatment treatment,
Set<ConnectPoint> ingressPoints,
ConnectPoint egressPoint) {
super(id(MultiPointToSinglePointIntent.class, selector, treatment,
ingressPoints, egressPoint), appId, null, selector, treatment);
checkNotNull(ingressPoints);
checkArgument(!ingressPoints.isEmpty(), "Ingress point set cannot be empty");
this.ingressPoints = Sets.newHashSet(ingressPoints);
this.egressPoint = checkNotNull(egressPoint);
this(appId, selector, treatment, ingressPoints, egressPoint, Collections.emptyList());
}
/**
......
......@@ -15,6 +15,7 @@
*/
package org.onlab.onos.net.intent;
import java.util.Collections;
import java.util.List;
import com.google.common.base.MoreObjects;
......@@ -42,9 +43,7 @@ public class PathIntent extends ConnectivityIntent {
*/
public PathIntent(ApplicationId appId, TrafficSelector selector,
TrafficTreatment treatment, Path path) {
super(id(PathIntent.class, selector, treatment, path), appId,
resources(path.links()), selector, treatment);
this.path = path;
this(appId, selector, treatment, path, Collections.emptyList());
}
/**
......
......@@ -21,6 +21,8 @@ import org.onlab.onos.net.resource.LinkResourceService;
import java.util.Objects;
import static org.onlab.onos.net.AnnotationKeys.getAnnotatedValue;
/**
* Constraint that evaluates an arbitrary link annotated value is under the specified threshold.
*/
......@@ -41,6 +43,12 @@ public class AnnotationConstraint extends BooleanConstraint {
this.threshold = threshold;
}
// Constructor for serialization
private AnnotationConstraint() {
this.key = "";
this.threshold = 0;
}
/**
* Returns the key of link annotation this constraint designates.
* @return key of link annotation
......@@ -65,25 +73,6 @@ public class AnnotationConstraint extends BooleanConstraint {
return value <= threshold;
}
/**
* Returns the annotated value of the specified link. The annotated value
* is expected to be String that can be parsed as double. If parsing fails,
* the returned value will be 1.0.
*
* @param link link whose annotated value is obtained
* @param key key of link annotation
* @return double value of link annotation for the specified key
*/
private double getAnnotatedValue(Link link, String key) {
double value;
try {
value = Double.parseDouble(link.annotations().value(key));
} catch (NumberFormatException e) {
value = 1.0;
}
return value;
}
@Override
public double cost(Link link, LinkResourceService resourceService) {
if (isValid(link, resourceService)) {
......
......@@ -25,14 +25,14 @@ import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
import static org.onlab.onos.net.AnnotationKeys.LATENCY;
import static org.onlab.onos.net.AnnotationKeys.getAnnotatedValue;
/**
* Constraint that evaluates the latency through a path.
*/
public class LatencyConstraint implements Constraint {
// TODO: formalize the key for latency all over the codes.
private static final String LATENCY_KEY = "latency";
private final Duration latency;
/**
......@@ -43,22 +43,18 @@ public class LatencyConstraint implements Constraint {
this.latency = latency;
}
// Constructor for serialization
private LatencyConstraint() {
this.latency = Duration.ZERO;
}
public Duration latency() {
return latency;
}
@Override
public double cost(Link link, LinkResourceService resourceService) {
String value = link.annotations().value(LATENCY_KEY);
double latencyInMicroSec;
try {
latencyInMicroSec = Double.parseDouble(value);
} catch (NumberFormatException e) {
latencyInMicroSec = 1.0;
}
return latencyInMicroSec;
return getAnnotatedValue(link, LATENCY);
}
@Override
......
......@@ -21,6 +21,7 @@ import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.resource.LinkResourceService;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
......@@ -39,6 +40,11 @@ public class ObstacleConstraint extends BooleanConstraint {
this.obstacles = ImmutableSet.copyOf(obstacles);
}
// Constructor for serialization
private ObstacleConstraint() {
this.obstacles = Collections.emptySet();
}
@Override
public boolean isValid(Link link, LinkResourceService resourceService) {
DeviceId src = link.src().deviceId();
......
......@@ -17,12 +17,13 @@ package org.onlab.onos.net.intent.constraint;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import org.onlab.onos.net.ElementId;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.Path;
import org.onlab.onos.net.intent.Constraint;
import org.onlab.onos.net.resource.LinkResourceService;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
......@@ -35,20 +36,25 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/
public class WaypointConstraint implements Constraint {
private final List<ElementId> waypoints;
private final List<DeviceId> waypoints;
/**
* Creates a new waypoint constraint.
*
* @param waypoints waypoints
*/
public WaypointConstraint(ElementId... waypoints) {
public WaypointConstraint(DeviceId... waypoints) {
checkNotNull(waypoints, "waypoints cannot be null");
checkArgument(waypoints.length > 0, "length of waypoints should be more than 0");
this.waypoints = ImmutableList.copyOf(waypoints);
}
public List<ElementId> waypoints() {
// Constructor for serialization
private WaypointConstraint() {
this.waypoints = Collections.emptyList();
}
public List<DeviceId> waypoints() {
return waypoints;
}
......@@ -60,8 +66,8 @@ public class WaypointConstraint implements Constraint {
@Override
public boolean validate(Path path, LinkResourceService resourceService) {
LinkedList<ElementId> waypoints = new LinkedList<>(this.waypoints);
ElementId current = waypoints.poll();
LinkedList<DeviceId> waypoints = new LinkedList<>(this.waypoints);
DeviceId current = waypoints.poll();
// This is safe because Path class ensures the number of links are more than 0
Link firstLink = path.links().get(0);
if (firstLink.src().elementId().equals(current)) {
......
......@@ -37,6 +37,7 @@ import static org.easymock.EasyMock.createMock;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.onlab.onos.net.AnnotationKeys.LATENCY;
import static org.onlab.onos.net.DefaultLinkTest.cp;
import static org.onlab.onos.net.DeviceId.deviceId;
import static org.onlab.onos.net.Link.Type.DIRECT;
......@@ -51,7 +52,6 @@ public class LatencyConstraintTest {
private static final PortNumber PN3 = PortNumber.portNumber(3);
private static final PortNumber PN4 = PortNumber.portNumber(4);
private static final ProviderId PROVIDER_ID = new ProviderId("of", "foo");
private static final String LATENCY_KEY = "latency";
private static final String LATENCY1 = "3.0";
private static final String LATENCY2 = "4.0";
......@@ -66,8 +66,8 @@ public class LatencyConstraintTest {
public void setUp() {
linkResourceService = createMock(LinkResourceService.class);
Annotations annotations1 = DefaultAnnotations.builder().set(LATENCY_KEY, LATENCY1).build();
Annotations annotations2 = DefaultAnnotations.builder().set(LATENCY_KEY, LATENCY2).build();
Annotations annotations1 = DefaultAnnotations.builder().set(LATENCY, LATENCY1).build();
Annotations annotations2 = DefaultAnnotations.builder().set(LATENCY, LATENCY2).build();
link1 = new DefaultLink(PROVIDER_ID, cp(DID1, PN1), cp(DID2, PN2), DIRECT, annotations1);
link2 = new DefaultLink(PROVIDER_ID, cp(DID2, PN3), cp(DID3, PN4), DIRECT, annotations2);
......
......@@ -18,9 +18,13 @@ package org.onlab.onos.net.device.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_MASTERSHIP_CHANGED;
import static org.onlab.onos.net.MastershipRole.*;
import static org.onlab.util.Tools.namedThreads;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
......@@ -83,6 +87,8 @@ public class DeviceManager
private final MastershipListener mastershipListener = new InternalMastershipListener();
private ScheduledExecutorService backgroundService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected DeviceStore store;
......@@ -102,15 +108,31 @@ public class DeviceManager
@Activate
public void activate() {
backgroundService = Executors.newSingleThreadScheduledExecutor(namedThreads("device-manager-background"));
store.setDelegate(delegate);
eventDispatcher.addSink(DeviceEvent.class, listenerRegistry);
mastershipService.addListener(mastershipListener);
termService = mastershipService.requestTermService();
backgroundService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
mastershipCheck();
} catch (Exception e) {
log.error("Exception thrown during integrity check", e);
}
}
}, 1, 1, TimeUnit.MINUTES);
log.info("Started");
}
@Deactivate
public void deactivate() {
backgroundService.shutdown();
store.unsetDelegate(delegate);
mastershipService.removeListener(mastershipListener);
eventDispatcher.removeSink(DeviceEvent.class);
......@@ -172,10 +194,6 @@ 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.
// FIXME: forward or broadcast and let the Master handler the event.
DeviceEvent event = store.removeDevice(deviceId);
if (event != null) {
log.info("Device {} administratively removed", deviceId);
......@@ -199,6 +217,31 @@ public class DeviceManager
return new InternalDeviceProviderService(provider);
}
/**
* Checks if all the reachable devices have a valid mastership role.
*/
private void mastershipCheck() {
log.debug("Checking mastership");
for (Device device : getDevices()) {
final DeviceId deviceId = device.id();
log.debug("Checking device {}", deviceId);
if (!isReachable(deviceId)) {
continue;
}
if (mastershipService.getLocalRole(deviceId) != NONE) {
continue;
}
log.info("{} is reachable but did not have a valid role, reasserting", deviceId);
// isReachable but was not MASTER or STANDBY, get a role and apply
// Note: NONE triggers request to MastershipService
reassertRole(deviceId, NONE);
}
}
// Personalized device provider service issued to the supplied provider.
private class InternalDeviceProviderService
extends AbstractProviderService<DeviceProvider>
......@@ -418,48 +461,112 @@ public class DeviceManager
}
}
// Intercepts mastership events
private class InternalMastershipListener implements MastershipListener {
// Applies the specified role to the device; ignores NONE
/**
* Apply role to device and send probe if MASTER.
*
* @param deviceId device identifier
* @param newRole new role to apply to the device
* @return true if the request was sent to provider
*/
private boolean applyRoleAndProbe(DeviceId deviceId, MastershipRole newRole) {
if (newRole.equals(MastershipRole.NONE)) {
//no-op
return true;
}
// Applies the specified role to the device; ignores NONE
/**
* Apply role in reaction to mastership event.
*
* @param deviceId device identifier
* @param newRole new role to apply to the device
* @return true if the request was sent to provider
*/
private boolean applyRole(DeviceId deviceId, MastershipRole newRole) {
if (newRole.equals(MastershipRole.NONE)) {
//no-op
return true;
}
Device device = store.getDevice(deviceId);
// FIXME: Device might not be there yet. (eventual consistent)
// FIXME relinquish role
if (device == null) {
log.warn("{} was not there. Cannot apply role {}", deviceId, newRole);
return false;
}
Device device = store.getDevice(deviceId);
// FIXME: Device might not be there yet. (eventual consistent)
// FIXME relinquish role
if (device == null) {
log.warn("{} was not there. Cannot apply role {}", deviceId, newRole);
return false;
}
DeviceProvider provider = getProvider(device.providerId());
if (provider == null) {
log.warn("Provider for {} was not found. Cannot apply role {}", deviceId, newRole);
return false;
}
provider.roleChanged(deviceId, newRole);
DeviceProvider provider = getProvider(device.providerId());
if (provider == null) {
log.warn("Provider for {} was not found. Cannot apply role {}", deviceId, newRole);
return false;
}
provider.roleChanged(deviceId, newRole);
if (newRole.equals(MastershipRole.MASTER)) {
// only trigger event when request was sent to provider
// TODO: consider removing this from Device event type?
post(new DeviceEvent(DEVICE_MASTERSHIP_CHANGED, device));
if (newRole.equals(MastershipRole.MASTER)) {
// only trigger event when request was sent to provider
// TODO: consider removing this from Device event type?
post(new DeviceEvent(DEVICE_MASTERSHIP_CHANGED, device));
provider.triggerProbe(device);
}
return true;
}
provider.triggerProbe(device);
/**
* Reaasert role for specified device connected to this node.
*
* @param did device identifier
* @param nextRole role to apply. If NONE is specified,
* it will ask mastership service for a role and apply it.
*/
private void reassertRole(final DeviceId did,
final MastershipRole nextRole) {
final NodeId myNodeId = clusterService.getLocalNode().id();
MastershipRole myNextRole = nextRole;
if (myNextRole == NONE) {
mastershipService.requestRoleFor(did);
MastershipTerm term = termService.getMastershipTerm(did);
if (myNodeId.equals(term.master())) {
myNextRole = MASTER;
} else {
myNextRole = STANDBY;
}
return true;
}
switch (myNextRole) {
case MASTER:
final Device device = getDevice(did);
if ((device != null) && !isAvailable(did)) {
//flag the device as online. Is there a better way to do this?
DefaultDeviceDescription deviceDescription
= new DefaultDeviceDescription(did.uri(),
device.type(),
device.manufacturer(),
device.hwVersion(),
device.swVersion(),
device.serialNumber(),
device.chassisId());
DeviceEvent devEvent =
store.createOrUpdateDevice(device.providerId(), did,
deviceDescription);
post(devEvent);
}
// TODO: should apply role only if there is mismatch
log.info("Applying role {} to {}", myNextRole, did);
if (!applyRoleAndProbe(did, MASTER)) {
// immediately failed to apply role
mastershipService.relinquishMastership(did);
// FIXME disconnect?
}
break;
case STANDBY:
log.info("Applying role {} to {}", myNextRole, did);
if (!applyRoleAndProbe(did, STANDBY)) {
// immediately failed to apply role
mastershipService.relinquishMastership(did);
// FIXME disconnect?
}
break;
case NONE:
default:
// should never reach here
log.error("You didn't see anything. I did not exist.");
break;
}
}
// Intercepts mastership events
private class InternalMastershipListener implements MastershipListener {
@Override
public void event(MastershipEvent event) {
......@@ -499,55 +606,12 @@ public class DeviceManager
+ "Relinquishing role. ",
myNextRole, did);
mastershipService.relinquishMastership(did);
// FIXME disconnect?
}
return;
}
// device is connected to this node:
if (myNextRole == NONE) {
mastershipService.requestRoleFor(did);
MastershipTerm term = termService.getMastershipTerm(did);
if (myNodeId.equals(term.master())) {
myNextRole = MASTER;
} else {
myNextRole = STANDBY;
}
}
switch (myNextRole) {
case MASTER:
final Device device = getDevice(did);
if ((device != null) && !isAvailable(did)) {
//flag the device as online. Is there a better way to do this?
DefaultDeviceDescription deviceDescription
= new DefaultDeviceDescription(did.uri(),
device.type(),
device.manufacturer(),
device.hwVersion(),
device.swVersion(),
device.serialNumber(),
device.chassisId());
DeviceEvent devEvent =
store.createOrUpdateDevice(device.providerId(), did,
deviceDescription);
post(devEvent);
}
// TODO: should apply role only if there is mismatch
log.info("Applying role {} to {}", myNextRole, did);
applyRole(did, MASTER);
break;
case STANDBY:
log.info("Applying role {} to {}", myNextRole, did);
applyRole(did, STANDBY);
break;
case NONE:
default:
// should never reach here
log.error("You didn't see anything. I did not exist.");
break;
}
reassertRole(did, myNextRole);
}
}
......
......@@ -15,21 +15,12 @@
*/
package org.onlab.onos.net.flow.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
import static org.onlab.util.Tools.namedThreads;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
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;
......@@ -64,14 +55,22 @@ import org.onlab.onos.net.provider.AbstractProviderRegistry;
import org.onlab.onos.net.provider.AbstractProviderService;
import org.slf4j.Logger;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.util.Tools.namedThreads;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provides implementation of the flow NB &amp; SB APIs.
......@@ -92,8 +91,7 @@ public class FlowRuleManager
private final FlowRuleStoreDelegate delegate = new InternalStoreDelegate();
private final ExecutorService futureListeners =
Executors.newCachedThreadPool(namedThreads("provider-future-listeners"));
private ExecutorService futureService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowRuleStore store;
......@@ -106,6 +104,7 @@ public class FlowRuleManager
@Activate
public void activate() {
futureService = Executors.newCachedThreadPool(namedThreads("provider-future-listeners"));
store.setDelegate(delegate);
eventDispatcher.addSink(FlowRuleEvent.class, listenerRegistry);
log.info("Started");
......@@ -113,7 +112,7 @@ public class FlowRuleManager
@Deactivate
public void deactivate() {
futureListeners.shutdownNow();
futureService.shutdownNow();
store.unsetDelegate(delegate);
eventDispatcher.removeSink(FlowRuleEvent.class);
......@@ -364,6 +363,9 @@ public class FlowRuleManager
// Store delegate to re-post events emitted from the store.
private class InternalStoreDelegate implements FlowRuleStoreDelegate {
private static final int TIMEOUT = 5000; // ms
// TODO: Right now we only dispatch events at individual flowEntry level.
// It may be more efficient for also dispatch events as a batch.
@Override
......@@ -384,15 +386,28 @@ public class FlowRuleManager
FlowRuleProvider flowRuleProvider =
getProvider(batchOperation.getOperations().get(0).getTarget().deviceId());
final ListenableFuture<CompletedBatchOperation> result =
final Future<CompletedBatchOperation> result =
flowRuleProvider.executeBatch(batchOperation);
result.addListener(new Runnable() {
futureService.submit(new Runnable() {
@Override
public void run() {
store.batchOperationComplete(FlowRuleBatchEvent.completed(request,
Futures.getUnchecked(result)));
CompletedBatchOperation res;
try {
res = result.get(TIMEOUT, TimeUnit.MILLISECONDS);
store.batchOperationComplete(FlowRuleBatchEvent.completed(request, res));
} catch (TimeoutException | InterruptedException | ExecutionException e) {
log.warn("Something went wrong with the batch operation {}",
request.batchId(), e);
Set<FlowRule> failures = new HashSet<>(batchOperation.size());
for (FlowRuleBatchEntry op : batchOperation.getOperations()) {
failures.add(op.getTarget());
}
res = new CompletedBatchOperation(false, failures);
store.batchOperationComplete(FlowRuleBatchEvent.completed(request, res));
}
}
}, futureListeners);
});
break;
case BATCH_OPERATION_COMPLETED:
......
......@@ -15,6 +15,8 @@
*/
package org.onlab.onos.net.intent.impl;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
......@@ -94,11 +96,19 @@ public abstract class ConnectivityIntentCompiler<T extends ConnectivityIntent>
protected Path getPath(ConnectivityIntent intent,
ElementId one, ElementId two) {
Set<Path> paths = pathService.getPaths(one, two, weight(intent.constraints()));
if (paths.isEmpty()) {
throw new PathNotFoundException("No packet path from " + one + " to " + two);
final List<Constraint> constraints = intent.constraints();
ImmutableList<Path> filtered = FluentIterable.from(paths)
.filter(new Predicate<Path>() {
@Override
public boolean apply(Path path) {
return checkPath(path, constraints);
}
}).toList();
if (filtered.isEmpty()) {
throw new PathNotFoundException("No packet path form " + one + " to " + two);
}
// TODO: let's be more intelligent about this eventually
return paths.iterator().next();
return filtered.iterator().next();
}
/**
......
/*
* 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.net.flow;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.onlab.onos.net.intent.IntentTestsMocks;
import com.google.common.testing.EqualsTester;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.onlab.onos.net.NetTestTools.did;
/**
* Unit tests for the DefaultFlowEntry class.
*/
public class DefaultFlowEntryTest {
private static final IntentTestsMocks.MockSelector SELECTOR =
new IntentTestsMocks.MockSelector();
private static final IntentTestsMocks.MockTreatment TREATMENT =
new IntentTestsMocks.MockTreatment();
private static DefaultFlowEntry makeFlowEntry(int uniqueValue) {
return new DefaultFlowEntry(did("id" + Integer.toString(uniqueValue)),
SELECTOR,
TREATMENT,
uniqueValue,
FlowEntry.FlowEntryState.ADDED,
uniqueValue,
uniqueValue,
uniqueValue,
uniqueValue,
uniqueValue);
}
final DefaultFlowEntry defaultFlowEntry1 = makeFlowEntry(1);
final DefaultFlowEntry sameAsDefaultFlowEntry1 = makeFlowEntry(1);
final DefaultFlowEntry defaultFlowEntry2 = makeFlowEntry(2);
/**
* Tests the equals, hashCode and toString methods using Guava EqualsTester.
*/
@Test
public void testEquals() {
new EqualsTester()
.addEqualityGroup(defaultFlowEntry1, sameAsDefaultFlowEntry1)
.addEqualityGroup(defaultFlowEntry2)
.testEquals();
}
/**
* Tests the construction of a default flow entry from a device id.
*/
@Test
public void testDeviceBasedObject() {
assertThat(defaultFlowEntry1.deviceId(), is(did("id1")));
assertThat(defaultFlowEntry1.selector(), is(SELECTOR));
assertThat(defaultFlowEntry1.treatment(), is(TREATMENT));
assertThat(defaultFlowEntry1.timeout(), is(1));
assertThat(defaultFlowEntry1.life(), is(1L));
assertThat(defaultFlowEntry1.packets(), is(1L));
assertThat(defaultFlowEntry1.bytes(), is(1L));
assertThat(defaultFlowEntry1.state(), is(FlowEntry.FlowEntryState.ADDED));
assertThat(defaultFlowEntry1.lastSeen(),
greaterThan(System.currentTimeMillis() -
TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
}
/**
* Tests the setters on a default flow entry object.
*/
@Test
public void testSetters() {
final DefaultFlowEntry entry = makeFlowEntry(1);
entry.setLastSeen();
entry.setState(FlowEntry.FlowEntryState.PENDING_REMOVE);
entry.setPackets(11);
entry.setBytes(22);
entry.setLife(33);
assertThat(entry.deviceId(), is(did("id1")));
assertThat(entry.selector(), is(SELECTOR));
assertThat(entry.treatment(), is(TREATMENT));
assertThat(entry.timeout(), is(1));
assertThat(entry.life(), is(33L));
assertThat(entry.packets(), is(11L));
assertThat(entry.bytes(), is(22L));
assertThat(entry.state(), is(FlowEntry.FlowEntryState.PENDING_REMOVE));
assertThat(entry.lastSeen(),
greaterThan(System.currentTimeMillis() -
TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
}
/**
* Tests a default flow rule built for an error.
*/
@Test
public void testErrorObject() {
final DefaultFlowEntry errorEntry =
new DefaultFlowEntry(new IntentTestsMocks.MockFlowRule(1),
111,
222);
assertThat(errorEntry.errType(), is(111));
assertThat(errorEntry.errCode(), is(222));
assertThat(errorEntry.state(), is(FlowEntry.FlowEntryState.FAILED));
assertThat(errorEntry.lastSeen(),
greaterThan(System.currentTimeMillis() -
TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
}
/**
* Tests a default flow entry constructed from a flow rule.
*/
@Test
public void testFlowBasedObject() {
final DefaultFlowEntry entry =
new DefaultFlowEntry(new IntentTestsMocks.MockFlowRule(1));
assertThat(entry.priority(), is(1));
assertThat(entry.appId(), is((short) 0));
assertThat(entry.lastSeen(),
greaterThan(System.currentTimeMillis() -
TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
}
/**
* Tests a default flow entry constructed from a flow rule plus extra
* parameters.
*/
@Test
public void testFlowBasedObjectWithParameters() {
final DefaultFlowEntry entry =
new DefaultFlowEntry(new IntentTestsMocks.MockFlowRule(33),
FlowEntry.FlowEntryState.REMOVED,
101, 102, 103);
assertThat(entry.state(), is(FlowEntry.FlowEntryState.REMOVED));
assertThat(entry.life(), is(101L));
assertThat(entry.packets(), is(102L));
assertThat(entry.bytes(), is(103L));
assertThat(entry.lastSeen(),
greaterThan(System.currentTimeMillis() -
TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS)));
}
}
/*
* 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.net.flow;
import org.junit.Test;
import org.onlab.onos.net.intent.IntentTestsMocks;
import com.google.common.testing.EqualsTester;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
import static org.onlab.onos.net.NetTestTools.APP_ID;
import static org.onlab.onos.net.NetTestTools.did;
/**
* Unit tests for the default flow rule class.
*/
public class DefaultFlowRuleTest {
private static final IntentTestsMocks.MockSelector SELECTOR =
new IntentTestsMocks.MockSelector();
private static final IntentTestsMocks.MockTreatment TREATMENT =
new IntentTestsMocks.MockTreatment();
final FlowRule flowRule1 = new IntentTestsMocks.MockFlowRule(1);
final FlowRule sameAsFlowRule1 = new IntentTestsMocks.MockFlowRule(1);
final FlowRule flowRule2 = new IntentTestsMocks.MockFlowRule(2);
final DefaultFlowRule defaultFlowRule1 = new DefaultFlowRule(flowRule1);
final DefaultFlowRule sameAsDefaultFlowRule1 = new DefaultFlowRule(sameAsFlowRule1);
final DefaultFlowRule defaultFlowRule2 = new DefaultFlowRule(flowRule2);
/**
* Checks that the DefaultFlowRule class is immutable but can be inherited
* from.
*/
@Test
public void testImmutability() {
assertThatClassIsImmutableBaseClass(DefaultFlowRule.class);
}
/**
* Tests the equals, hashCode and toString methods using Guava EqualsTester.
*/
@Test
public void testEquals() {
new EqualsTester()
.addEqualityGroup(defaultFlowRule1, sameAsDefaultFlowRule1)
.addEqualityGroup(defaultFlowRule2)
.testEquals();
}
/**
* Tests creation of a DefaultFlowRule using a FlowRule constructor.
*/
@Test
public void testCreationFromFlowRule() {
assertThat(defaultFlowRule1.deviceId(), is(flowRule1.deviceId()));
assertThat(defaultFlowRule1.appId(), is(flowRule1.appId()));
assertThat(defaultFlowRule1.id(), is(flowRule1.id()));
assertThat(defaultFlowRule1.isPermanent(), is(flowRule1.isPermanent()));
assertThat(defaultFlowRule1.priority(), is(flowRule1.priority()));
assertThat(defaultFlowRule1.selector(), is(flowRule1.selector()));
assertThat(defaultFlowRule1.treatment(), is(flowRule1.treatment()));
assertThat(defaultFlowRule1.timeout(), is(flowRule1.timeout()));
}
/**
* Tests creation of a DefaultFlowRule using a FlowId constructor.
*/
@Test
public void testCreationWithFlowId() {
final DefaultFlowRule rule =
new DefaultFlowRule(did("1"), SELECTOR,
TREATMENT, 22, 33,
44, false);
assertThat(rule.deviceId(), is(did("1")));
assertThat(rule.id().value(), is(33L));
assertThat(rule.isPermanent(), is(false));
assertThat(rule.priority(), is(22));
assertThat(rule.selector(), is(SELECTOR));
assertThat(rule.treatment(), is(TREATMENT));
assertThat(rule.timeout(), is(44));
}
/**
* Tests the creation of a DefaultFlowRule using an AppId constructor.
*/
@Test
public void testCreationWithAppId() {
final DefaultFlowRule rule =
new DefaultFlowRule(did("1"), SELECTOR,
TREATMENT, 22, APP_ID,
44, false);
assertThat(rule.deviceId(), is(did("1")));
assertThat(rule.isPermanent(), is(false));
assertThat(rule.priority(), is(22));
assertThat(rule.selector(), is(SELECTOR));
assertThat(rule.treatment(), is(TREATMENT));
assertThat(rule.timeout(), is(44));
}
}
......@@ -16,6 +16,7 @@
package org.onlab.onos.net.intent;
import static org.onlab.onos.net.NetTestTools.createPath;
import static org.onlab.onos.net.NetTestTools.did;
import static org.onlab.onos.net.NetTestTools.link;
import java.util.ArrayList;
......@@ -31,6 +32,8 @@ import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.ElementId;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.Path;
import org.onlab.onos.net.flow.FlowId;
import org.onlab.onos.net.flow.FlowRule;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.flow.criteria.Criterion;
......@@ -271,4 +274,60 @@ public class IntentTestsMocks {
}
}
private static final IntentTestsMocks.MockSelector SELECTOR =
new IntentTestsMocks.MockSelector();
private static final IntentTestsMocks.MockTreatment TREATMENT =
new IntentTestsMocks.MockTreatment();
public static class MockFlowRule implements FlowRule {
int priority;
public MockFlowRule(int priority) {
this.priority = priority;
}
@Override
public FlowId id() {
return FlowId.valueOf(1);
}
@Override
public short appId() {
return 0;
}
@Override
public int priority() {
return priority;
}
@Override
public DeviceId deviceId() {
return did("1");
}
@Override
public TrafficSelector selector() {
return SELECTOR;
}
@Override
public TrafficTreatment treatment() {
return TREATMENT;
}
@Override
public int timeout() {
return 0;
}
@Override
public boolean isPermanent() {
return false;
}
}
}
......
......@@ -85,6 +85,8 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
......@@ -132,8 +134,7 @@ public class DistributedFlowRuleStore
private Cache<Integer, SettableFuture<CompletedBatchOperation>> pendingFutures =
CacheBuilder.newBuilder()
.expireAfterWrite(pendingFutureTimeoutMinutes, TimeUnit.MINUTES)
// TODO Explicitly fail the future if expired?
//.removalListener(listener)
.removalListener(new TimeoutFuture())
.build();
// Cache of SMaps used for backup data. each SMap contain device flow table
......@@ -541,6 +542,17 @@ public class DistributedFlowRuleStore
log.debug("removedFromPrimary {}", removed);
}
private static final class TimeoutFuture
implements RemovalListener<Integer, SettableFuture<CompletedBatchOperation>> {
@Override
public void onRemoval(RemovalNotification<Integer, SettableFuture<CompletedBatchOperation>> notification) {
// wrapping in ExecutionException to support Future.get
notification.getValue()
.setException(new ExecutionException("Timed out",
new TimeoutException()));
}
}
private final class OnStoreBatch implements ClusterMessageHandler {
private final NodeId local;
......@@ -580,7 +592,18 @@ public class DistributedFlowRuleStore
@Override
public void run() {
CompletedBatchOperation result = Futures.getUnchecked(f);
CompletedBatchOperation result;
try {
result = f.get();
} catch (InterruptedException | ExecutionException e) {
log.error("Batch operation failed", e);
// create everything failed response
Set<FlowRule> failures = new HashSet<>(operation.size());
for (FlowRuleBatchEntry op : operation.getOperations()) {
failures.add(op.getTarget());
}
result = new CompletedBatchOperation(false, failures);
}
try {
message.respond(SERIALIZER.encode(result));
} catch (IOException e) {
......
......@@ -72,7 +72,7 @@ public class DatabaseManager implements DatabaseService, DatabaseAdminService {
public static final String LOG_FILE_PREFIX = "/tmp/onos-copy-cat-log_";
// Current working dir seems to be /opt/onos/apache-karaf-3.0.2
// TODO: Get the path to /opt/onos/config
// TODO: Set the path to /opt/onos/config
private static final String CONFIG_DIR = "../config";
private static final String DEFAULT_MEMBER_FILE = "tablets.json";
......
......@@ -75,10 +75,14 @@ import org.onlab.onos.net.intent.OpticalConnectivityIntent;
import org.onlab.onos.net.intent.OpticalPathIntent;
import org.onlab.onos.net.intent.PathIntent;
import org.onlab.onos.net.intent.PointToPointIntent;
import org.onlab.onos.net.intent.constraint.AnnotationConstraint;
import org.onlab.onos.net.intent.constraint.BandwidthConstraint;
import org.onlab.onos.net.intent.constraint.BooleanConstraint;
import org.onlab.onos.net.intent.constraint.LambdaConstraint;
import org.onlab.onos.net.intent.constraint.LatencyConstraint;
import org.onlab.onos.net.intent.constraint.LinkTypeConstraint;
import org.onlab.onos.net.intent.constraint.ObstacleConstraint;
import org.onlab.onos.net.intent.constraint.WaypointConstraint;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.packet.DefaultOutboundPacket;
import org.onlab.onos.net.provider.ProviderId;
......@@ -208,9 +212,14 @@ public final class KryoNamespaces {
LinkResourceRequest.class,
Lambda.class,
Bandwidth.class,
// Constraints
LambdaConstraint.class,
BandwidthConstraint.class,
LinkTypeConstraint.class,
LatencyConstraint.class,
WaypointConstraint.class,
ObstacleConstraint.class,
AnnotationConstraint.class,
BooleanConstraint.class
)
.register(DefaultApplicationId.class, new DefaultApplicationIdSerializer())
......
......@@ -18,6 +18,8 @@ package org.onlab.onos.store.trivial.impl;
import com.google.common.base.Function;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.collect.FluentIterable;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
......@@ -53,8 +55,10 @@ import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
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.AtomicInteger;
import static org.apache.commons.lang3.concurrent.ConcurrentUtils.createIfAbsentUnchecked;
......@@ -86,8 +90,7 @@ public class SimpleFlowRuleStore
private Cache<Integer, SettableFuture<CompletedBatchOperation>> pendingFutures =
CacheBuilder.newBuilder()
.expireAfterWrite(pendingFutureTimeoutMinutes, TimeUnit.MINUTES)
// TODO Explicitly fail the future if expired?
//.removalListener(listener)
.removalListener(new TimeoutFuture())
.build();
@Activate
......@@ -303,4 +306,15 @@ public class SimpleFlowRuleStore
}
notifyDelegate(event);
}
private static final class TimeoutFuture
implements RemovalListener<Integer, SettableFuture<CompletedBatchOperation>> {
@Override
public void onRemoval(RemovalNotification<Integer, SettableFuture<CompletedBatchOperation>> notification) {
// wrapping in ExecutionException to support Future.get
notification.getValue()
.setException(new ExecutionException("Timed out",
new TimeoutException()));
}
}
}
......
......@@ -197,6 +197,8 @@
<feature name="onos-app-sdnip" version="1.0.0"
description="SDN-IP peering application">
<feature>onos-api</feature>
<feature>onos-app-proxyarp</feature>
<feature>onos-app-config</feature>
<bundle>mvn:org.onlab.onos/onos-app-sdnip/1.0.0-SNAPSHOT</bundle>
</feature>
......@@ -225,4 +227,12 @@
<bundle>mvn:org.onlab.onos/onos-app-metrics-topology/1.0.0-SNAPSHOT</bundle>
</feature>
<feature name="onos-app-demo" version="1.0.0"
description="ONOS demo applications">
<feature>onos-api</feature>
<bundle>mvn:org.onlab.onos/onos-app-demo/1.0.0-SNAPSHOT</bundle>
</feature>
</features>
......
......@@ -15,21 +15,11 @@
*/
package org.onlab.onos.provider.of.flow.impl;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.Collections;
import java.util.HashMap;
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.Executor;
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.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ExecutionList;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
......@@ -80,15 +70,23 @@ import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg;
import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyActions;
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.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ExecutionList;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.HashMap;
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 java.util.concurrent.atomic.AtomicLong;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provider which uses an OpenFlow controller to detect network
......@@ -124,6 +122,8 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
private final Map<Dpid, FlowStatsCollector> collectors = Maps.newHashMap();
private final AtomicLong xidCounter = new AtomicLong(0);
/**
* Creates an OpenFlow host provider.
*/
......@@ -154,6 +154,7 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
log.info("Stopped");
}
@Override
public void applyFlowRule(FlowRule... flowRules) {
for (int i = 0; i < flowRules.length; i++) {
......@@ -167,7 +168,6 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
}
@Override
public void removeFlowRule(FlowRule... flowRules) {
for (int i = 0; i < flowRules.length; i++) {
......@@ -188,11 +188,15 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
}
@Override
public ListenableFuture<CompletedBatchOperation> executeBatch(BatchOperation<FlowRuleBatchEntry> batch) {
public Future<CompletedBatchOperation> executeBatch(BatchOperation<FlowRuleBatchEntry> batch) {
final Set<Dpid> sws =
Collections.newSetFromMap(new ConcurrentHashMap<Dpid, Boolean>());
final Map<Long, FlowRuleBatchEntry> fmXids = new HashMap<Long, FlowRuleBatchEntry>();
OFFlowMod mod = null;
/*
* Use identity hash map for reference equality as we could have equal
* flow mods for different switches.
*/
Map<OFFlowMod, OpenFlowSwitch> mods = Maps.newIdentityHashMap();
for (FlowRuleBatchEntry fbe : batch.getOperations()) {
FlowRule flowRule = fbe.getTarget();
OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId().uri()));
......@@ -208,6 +212,7 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
}
sws.add(new Dpid(sw.getId()));
FlowModBuilder builder = FlowModBuilder.builder(flowRule, sw.factory());
OFFlowMod mod = null;
switch (fbe.getOperator()) {
case ADD:
mod = builder.buildFlowAdd();
......@@ -222,25 +227,29 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
log.error("Unsupported batch operation {}", fbe.getOperator());
}
if (mod != null) {
sw.sendMsg(mod);
fmXids.put(mod.getXid(), fbe);
mods.put(mod, sw);
fmXids.put(xidCounter.getAndIncrement(), fbe);
} else {
log.error("Conversion of flowrule {} failed.", flowRule);
}
}
InstallationFuture installation = new InstallationFuture(sws, fmXids);
for (Long xid : fmXids.keySet()) {
pendingFMs.put(xid, installation);
}
pendingFutures.put(U32.f(batch.hashCode()), installation);
installation.verify(U32.f(batch.hashCode()));
pendingFutures.put(installation.xid(), installation);
for (Map.Entry<OFFlowMod, OpenFlowSwitch> entry : mods.entrySet()) {
OpenFlowSwitch sw = entry.getValue();
OFFlowMod mod = entry.getKey();
sw.sendMsg(mod);
}
installation.verify();
return installation;
}
private class InternalFlowProvider
implements OpenFlowSwitchListener, OpenFlowEventListener {
implements OpenFlowSwitchListener, OpenFlowEventListener {
private final Multimap<DeviceId, FlowEntry> completeEntries =
......@@ -274,36 +283,36 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
public void handleMessage(Dpid dpid, OFMessage msg) {
InstallationFuture future = null;
switch (msg.getType()) {
case FLOW_REMOVED:
OFFlowRemoved removed = (OFFlowRemoved) msg;
FlowEntry fr = new FlowEntryBuilder(dpid, removed).build();
providerService.flowRemoved(fr);
break;
case STATS_REPLY:
pushFlowMetrics(dpid, (OFStatsReply) msg);
break;
case BARRIER_REPLY:
future = pendingFutures.get(msg.getXid());
if (future != null) {
future.satisfyRequirement(dpid);
}
break;
case ERROR:
future = pendingFMs.get(msg.getXid());
if (future != null) {
future.fail((OFErrorMsg) msg, dpid);
}
break;
default:
log.debug("Unhandled message type: {}", msg.getType());
case FLOW_REMOVED:
OFFlowRemoved removed = (OFFlowRemoved) msg;
FlowEntry fr = new FlowEntryBuilder(dpid, removed).build();
providerService.flowRemoved(fr);
break;
case STATS_REPLY:
pushFlowMetrics(dpid, (OFStatsReply) msg);
break;
case BARRIER_REPLY:
future = pendingFutures.get(msg.getXid());
if (future != null) {
future.satisfyRequirement(dpid);
}
break;
case ERROR:
future = pendingFMs.get(msg.getXid());
if (future != null) {
future.fail((OFErrorMsg) msg, dpid);
}
break;
default:
log.debug("Unhandled message type: {}", msg.getType());
}
}
@Override
public void receivedRoleReply(Dpid dpid, RoleState requested,
RoleState response) {
RoleState response) {
// Do nothing here for now.
}
......@@ -352,8 +361,9 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
}
private class InstallationFuture implements ListenableFuture<CompletedBatchOperation> {
private class InstallationFuture implements Future<CompletedBatchOperation> {
private final Long xid;
private final Set<Dpid> sws;
private final AtomicBoolean ok = new AtomicBoolean(true);
private final Map<Long, FlowRuleBatchEntry> fms;
......@@ -361,18 +371,22 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
private final Set<FlowEntry> offendingFlowMods = Sets.newHashSet();
private final CountDownLatch countDownLatch;
private Long pendingXid;
private BatchState state;
private final ExecutionList executionList = new ExecutionList();
public InstallationFuture(Set<Dpid> sws, Map<Long, FlowRuleBatchEntry> fmXids) {
this.xid = xidCounter.getAndIncrement();
this.state = BatchState.STARTED;
this.sws = sws;
this.fms = fmXids;
countDownLatch = new CountDownLatch(sws.size());
}
public Long xid() {
return xid;
}
public void fail(OFErrorMsg msg, Dpid dpid) {
ok.set(false);
......@@ -385,27 +399,27 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
case BAD_ACTION:
OFBadActionErrorMsg bad = (OFBadActionErrorMsg) msg;
fe = new DefaultFlowEntry(offending, bad.getErrType().ordinal(),
bad.getCode().ordinal());
bad.getCode().ordinal());
break;
case BAD_INSTRUCTION:
OFBadInstructionErrorMsg badins = (OFBadInstructionErrorMsg) msg;
fe = new DefaultFlowEntry(offending, badins.getErrType().ordinal(),
badins.getCode().ordinal());
badins.getCode().ordinal());
break;
case BAD_MATCH:
OFBadMatchErrorMsg badMatch = (OFBadMatchErrorMsg) msg;
fe = new DefaultFlowEntry(offending, badMatch.getErrType().ordinal(),
badMatch.getCode().ordinal());
badMatch.getCode().ordinal());
break;
case BAD_REQUEST:
OFBadRequestErrorMsg badReq = (OFBadRequestErrorMsg) msg;
fe = new DefaultFlowEntry(offending, badReq.getErrType().ordinal(),
badReq.getCode().ordinal());
badReq.getCode().ordinal());
break;
case FLOW_MOD_FAILED:
OFFlowModFailedErrorMsg fmFail = (OFFlowModFailedErrorMsg) msg;
fe = new DefaultFlowEntry(offending, fmFail.getErrType().ordinal(),
fmFail.getCode().ordinal());
fmFail.getCode().ordinal());
break;
case EXPERIMENTER:
case GROUP_MOD_FAILED:
......@@ -434,13 +448,12 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
}
public void verify(Long id) {
pendingXid = id;
public void verify() {
for (Dpid dpid : sws) {
OpenFlowSwitch sw = controller.getSwitch(dpid);
OFBarrierRequest.Builder builder = sw.factory()
.buildBarrierRequest()
.setXid(id);
.setXid(xid);
sw.sendMsg(builder.build());
}
}
......@@ -462,7 +475,6 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
}
}
invokeCallbacks();
return true;
}
......@@ -481,6 +493,7 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
countDownLatch.await();
this.state = BatchState.FINISHED;
CompletedBatchOperation result = new CompletedBatchOperation(ok.get(), offendingFlowMods);
//FIXME do cleanup here
return result;
}
......@@ -491,6 +504,7 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
if (countDownLatch.await(timeout, unit)) {
this.state = BatchState.FINISHED;
CompletedBatchOperation result = new CompletedBatchOperation(ok.get(), offendingFlowMods);
// FIXME do cleanup here
return result;
}
throw new TimeoutException();
......@@ -498,9 +512,7 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
private void cleanUp() {
if (isDone() || isCancelled()) {
if (pendingXid != null) {
pendingFutures.remove(pendingXid);
}
pendingFutures.remove(xid);
for (Long xid : fms.keySet()) {
pendingFMs.remove(xid);
}
......@@ -509,21 +521,10 @@ public class OpenFlowRuleProvider extends AbstractProvider implements FlowRulePr
private void removeRequirement(Dpid dpid) {
countDownLatch.countDown();
if (countDownLatch.getCount() == 0) {
invokeCallbacks();
}
sws.remove(dpid);
//FIXME don't do cleanup here
cleanUp();
}
@Override
public void addListener(Runnable runnable, Executor executor) {
executionList.add(runnable, executor);
}
private void invokeCallbacks() {
executionList.execute();
}
}
}
......
onos-config command will copy files contained in this directory to ONOS instances according to cell definition
The onos-config command will copy files contained in this directory to ONOS
instances according to cell definition.
......
......@@ -10,4 +10,5 @@
[ "$1" = "-w" ] && shift && onos-wait-for-start $1
[ -n "$1" ] && OCI=$(find_node $1) && shift
unset KARAF_HOME
client -h $OCI -u karaf "$@" 2>/dev/null
......
......@@ -3,10 +3,7 @@
# Launches ONOS GUI on the specified node.
# -----------------------------------------------------------------------------
[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
. $ONOS_ROOT/tools/build/envDefaults
host=${1:-$OCI}
host=${host:-localhost}
open http://$host:8181/onos/tvue
open http://$host:8181/onos/ui
......
......@@ -40,6 +40,7 @@ fi
# Load the cell setup
. $ONOS_ROOT/tools/test/cells/${cell}
echo "ONOS_CELL=${ONOS_CELL}"
echo "ONOS_NIC=${ONOS_NIC}"
for n in {0..9}; do
ocn="OC${n}"
......
......@@ -108,33 +108,26 @@
],
"links" : [
{ "src": "of:0000ffffffffff01/10", "dst": "of:0000ffffffffff02/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff02/10", "dst": "of:0000ffffffffff03/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff03/30", "dst": "of:0000ffffffffff04/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff02/20", "dst": "of:0000ffffffffff05/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff03/20", "dst": "of:0000ffffffffff06/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff05/30", "dst": "of:0000ffffffffff06/20", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff05/20", "dst": "of:0000ffffffffff07/21", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff06/30", "dst": "of:0000ffffffffff08/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff07/30", "dst": "of:0000ffffffffff08/20", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff07/20", "dst": "of:0000ffffffffff09/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff08/30", "dst": "of:0000ffffffffff0A/10", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff01/50", "dst": "of:0000ffffffffff02/30","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff02/50", "dst": "of:0000ffffffffff03/30","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff03/50", "dst": "of:0000ffffffffff04/50","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff01/20", "dst": "of:0000ffffffffff05/50","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff02/20", "dst": "of:0000ffffffffff05/20","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff03/20", "dst": "of:0000ffffffffff06/50","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff04/20", "dst": "of:0000ffffffffff06/20","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff05/30", "dst": "of:0000ffffffffff06/40","type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff05/40", "dst": "of:0000ffffffffff07/50", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff06/30", "dst": "of:0000ffffffffff08/50", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff07/20", "dst": "of:0000ffffffffff08/30", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff07/30", "dst": "of:0000ffffffffff09/50", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff08/20", "dst": "of:0000ffffffffff0A/50", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffffff09/20", "dst": "of:0000ffffffffff0A/20", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM", "optical.kms": 1000, "durable": "true" } },
{ "src": "of:0000ffffffff0001/2", "dst": "of:0000ffffffffff01/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
{ "src": "of:0000ffffffff0003/2", "dst": "of:0000ffffffffff03/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
{ "src": "of:0000ffffffff0004/2", "dst": "of:0000ffffffffff04/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
{ "src": "of:0000ffffffff0007/2", "dst": "of:0000ffffffffff07/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
{ "src": "of:0000ffffffff0009/2", "dst": "of:0000ffffffffff09/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
{ "src": "of:0000ffffffff000A/2", "dst": "of:0000ffffffffff0A/1", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } }
],
"hosts" : [
{ "mac": "00:00:00:00:00:01", "vlan": -1, "location": "of:0000ffffffff0001/1", "ip": "10.0.0.1" },
{ "mac": "00:00:00:00:00:03", "vlan": -1, "location": "of:0000ffffffff0003/1", "ip": "10.0.0.3" },
{ "mac": "00:00:00:00:00:04", "vlan": -1, "location": "of:0000ffffffff0004/1", "ip": "10.0.0.4" },
{ "mac": "00:00:00:00:00:07", "vlan": -1, "location": "of:0000ffffffff0007/1", "ip": "10.0.0.7" },
{ "mac": "00:00:00:00:00:09", "vlan": -1, "location": "of:0000ffffffff0009/1", "ip": "10.0.0.9" },
{ "mac": "00:00:00:00:00:0A", "vlan": -1, "location": "of:0000ffffffff000A/1", "ip": "10.0.0.10" }
{ "src": "of:0000ffffffff0001/2", "dst": "of:0000ffffffffff01/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
{ "src": "of:0000ffffffff0002/2", "dst": "of:0000ffffffffff04/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
{ "src": "of:0000ffffffff0003/2", "dst": "of:0000ffffffffff06/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
{ "src": "of:0000ffffffff0004/2", "dst": "of:0000ffffffffff07/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
{ "src": "of:0000ffffffff0005/2", "dst": "of:0000ffffffffff09/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } },
{ "src": "of:0000ffffffff0006/2", "dst": "of:0000ffffffffff0A/10", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect", "durable": "true" } }
]
}
\ No newline at end of file
}
......
......@@ -43,9 +43,9 @@ public class ImmutableClassChecker {
* @param clazz the class to check
* @return true if the given class is a properly specified immutable class.
*/
private boolean isImmutableClass(Class<?> clazz) {
private boolean isImmutableClass(Class<?> clazz, boolean allowNonFinalClass) {
// class must be declared final
if (!Modifier.isFinal(clazz.getModifiers())) {
if (!allowNonFinalClass && !Modifier.isFinal(clazz.getModifiers())) {
failureReason = "a class that is not final";
return false;
}
......@@ -113,16 +113,16 @@ public class ImmutableClassChecker {
}
/**
* Assert that the given class adheres to the utility class rules.
* Assert that the given class adheres to the immutable class rules.
*
* @param clazz the class to check
*
* @throws java.lang.AssertionError if the class is not a valid
* utility class
* @throws java.lang.AssertionError if the class is not an
* immutable class
*/
public static void assertThatClassIsImmutable(Class<?> clazz) {
final ImmutableClassChecker checker = new ImmutableClassChecker();
if (!checker.isImmutableClass(clazz)) {
if (!checker.isImmutableClass(clazz, false)) {
final Description toDescription = new StringDescription();
final Description mismatchDescription = new StringDescription();
......@@ -136,4 +136,31 @@ public class ImmutableClassChecker {
throw new AssertionError(reason);
}
}
/**
* Assert that the given class adheres to the immutable class rules, but
* is not declared final. Classes that need to be inherited from cannot be
* declared final.
*
* @param clazz the class to check
*
* @throws java.lang.AssertionError if the class is not an
* immutable class
*/
public static void assertThatClassIsImmutableBaseClass(Class<?> clazz) {
final ImmutableClassChecker checker = new ImmutableClassChecker();
if (!checker.isImmutableClass(clazz, true)) {
final Description toDescription = new StringDescription();
final Description mismatchDescription = new StringDescription();
checker.describeTo(toDescription);
checker.describeMismatch(mismatchDescription);
final String reason =
"\n" +
"Expected: is \"" + toDescription.toString() + "\"\n" +
" but : was \"" + mismatchDescription.toString() + "\"";
throw new AssertionError(reason);
}
}
}
......
......@@ -24,6 +24,7 @@ import org.onlab.onos.cluster.ClusterService;
import org.onlab.onos.cluster.ControllerNode;
import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.mastership.MastershipService;
import org.onlab.onos.net.Annotated;
import org.onlab.onos.net.Annotations;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.DefaultEdgeLink;
......@@ -45,6 +46,8 @@ import org.onlab.onos.net.link.LinkService;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.osgi.ServiceDirectory;
import org.onlab.packet.IpAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Iterator;
import java.util.Map;
......@@ -68,6 +71,8 @@ import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
*/
public abstract class TopologyMessages {
protected static final Logger log = LoggerFactory.getLogger(TopologyMessages.class);
private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
private static final String COMPACT = "%s/%s-%s/%s";
......@@ -195,7 +200,7 @@ public abstract class TopologyMessages {
.put("id", device.id().toString())
.put("type", device.type().toString().toLowerCase())
.put("online", deviceService.isAvailable(device.id()))
.put("master", mastershipService.getMasterFor(device.id()).toString());
.put("master", master(device.id()));
// Generate labels: id, chassis id, no-label, optional-name
ArrayNode labels = mapper.createArrayNode();
......@@ -207,6 +212,7 @@ public abstract class TopologyMessages {
// Add labels, props and stuff the payload into envelope.
payload.set("labels", labels);
payload.set("props", props(device.annotations()));
addGeoLocation(device, payload);
addMetaUi(device.id().toString(), payload);
String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
......@@ -220,6 +226,7 @@ public abstract class TopologyMessages {
ObjectNode payload = mapper.createObjectNode()
.put("id", compactLinkString(link))
.put("type", link.type().toString().toLowerCase())
.put("online", true) // TODO: add link state field
.put("linkWidth", 2)
.put("src", link.src().deviceId().toString())
.put("srcPort", link.src().port().toString())
......@@ -237,10 +244,11 @@ public abstract class TopologyMessages {
.put("id", host.id().toString())
.put("ingress", compactLinkString(edgeLink(host, true)))
.put("egress", compactLinkString(edgeLink(host, false)));
payload.set("cp", location(mapper, host.location()));
payload.set("cp", hostConnect(mapper, host.location()));
payload.set("labels", labels(mapper, ip(host.ipAddresses()),
host.mac().toString()));
payload.set("props", props(host.annotations()));
addGeoLocation(host, payload);
addMetaUi(host.id().toString(), payload);
String type = (event.type() == HOST_ADDED) ? "addHost" :
......@@ -249,7 +257,7 @@ public abstract class TopologyMessages {
}
// Encodes the specified host location into a JSON object.
private ObjectNode location(ObjectMapper mapper, HostLocation location) {
private ObjectNode hostConnect(ObjectMapper mapper, HostLocation location) {
return mapper.createObjectNode()
.put("device", location.deviceId().toString())
.put("port", location.port().toLong());
......@@ -264,6 +272,12 @@ public abstract class TopologyMessages {
return json;
}
// Returns the name of the master node for the specified device id.
private String master(DeviceId deviceId) {
NodeId master = mastershipService.getMasterFor(deviceId);
return master != null ? master.toString() : "";
}
// Generates an edge link from the specified host location.
private EdgeLink edgeLink(Host host, boolean ingress) {
return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
......@@ -278,10 +292,28 @@ public abstract class TopologyMessages {
}
}
// Adds a geo location JSON to the specified payload object.
private void addGeoLocation(Annotated annotated, ObjectNode payload) {
Annotations annotations = annotated.annotations();
String slat = annotations.value("latitude");
String slng = annotations.value("longitude");
try {
if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
double lat = Double.parseDouble(slat);
double lng = Double.parseDouble(slng);
ObjectNode loc = mapper.createObjectNode()
.put("type", "latlng").put("lat", lat).put("lng", lng);
payload.set("location", loc);
}
} catch (NumberFormatException e) {
log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
}
}
// Updates meta UI information for the specified object.
protected void updateMetaUi(ObjectNode event) {
ObjectNode payload = payload(event);
metaUi.put(string(payload, "id"), payload);
metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento"));
}
// Returns device details response.
......@@ -289,7 +321,6 @@ public abstract class TopologyMessages {
Device device = deviceService.getDevice(deviceId);
Annotations annot = device.annotations();
int portCount = deviceService.getPorts(deviceId).size();
NodeId master = mastershipService.getMasterFor(device.id());
return envelope("showDetails", sid,
json(deviceId.toString(),
device.type().toString().toLowerCase(),
......@@ -303,7 +334,7 @@ public abstract class TopologyMessages {
new Prop("Longitude", annot.value("longitude")),
new Prop("Ports", Integer.toString(portCount)),
new Separator(),
new Prop("Master", master.toString())));
new Prop("Master", master(deviceId))));
}
// Returns host details response.
......@@ -319,16 +350,15 @@ public abstract class TopologyMessages {
new Prop("Longitude", annot.value("longitude"))));
}
// Produces a path message to the client.
protected ObjectNode pathMessage(Path path) {
protected ObjectNode pathMessage(Path path, String type) {
ObjectNode payload = mapper.createObjectNode();
ArrayNode links = mapper.createArrayNode();
for (Link link : path.links()) {
links.add(compactLinkString(link));
}
payload.set("links", links);
payload.put("type", type).set("links", links);
return payload;
}
......
......@@ -110,8 +110,8 @@ public class TopologyWebSocket
try {
ObjectNode event = (ObjectNode) mapper.reader().readTree(data);
String type = string(event, "event", "unknown");
if (type.equals("showDetails")) {
showDetails(event);
if (type.equals("requestDetails")) {
requestDetails(event);
} else if (type.equals("updateMeta")) {
updateMetaUi(event);
} else if (type.equals("requestPath")) {
......@@ -122,7 +122,7 @@ public class TopologyWebSocket
cancelTraffic(event);
}
} catch (Exception e) {
System.out.println("WTF?! " + data);
log.warn("Unable to parse GUI request {} due to {}", data, e);
e.printStackTrace();
}
}
......@@ -165,9 +165,9 @@ public class TopologyWebSocket
}
// Sends back device or host details.
private void showDetails(ObjectNode event) {
private void requestDetails(ObjectNode event) {
ObjectNode payload = payload(event);
String type = string(payload, "type", "unknown");
String type = string(payload, "class", "unknown");
if (type.equals("device")) {
sendMessage(deviceDetails(deviceId(string(payload, "id")),
number(event, "sid")));
......@@ -282,7 +282,8 @@ public class TopologyWebSocket
if (installable != null && !installable.isEmpty()) {
PathIntent pathIntent = (PathIntent) installable.iterator().next();
Path path = pathIntent.path();
ObjectNode payload = pathMessage(path).put("intentId", intent.id().toString());
ObjectNode payload = pathMessage(path, "host")
.put("intentId", intent.id().toString());
sendMessage(envelope("showPath", sid, payload));
}
}
......
......@@ -21,7 +21,7 @@
<display-name>ONOS GUI</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index2.html</welcome-file>
</welcome-file-list>
<servlet>
......
/*
* 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.
*/
/*
ONOS GUI -- Floating Panels -- CSS file
@author Simon Hunt
*/
.fpanel {
position: absolute;
z-index: 100;
display: block;
top: 10%;
width: 280px;
right: -300px;
opacity: 0;
background-color: rgba(255,255,255,0.8);
padding: 10px;
color: black;
font-size: 10pt;
box-shadow: 2px 2px 16px #777;
}
/* TODO: light/dark themes */
.light .fpanel {
}
.dark .fpanel {
}
......@@ -40,6 +40,7 @@
<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="onos2.css">
<link rel="stylesheet" href="mast2.css">
<link rel="stylesheet" href="floatPanel.css">
<!-- This is where contributed stylesheets get INJECTED -->
<!-- TODO: replace with template marker and inject refs server-side -->
......@@ -62,8 +63,9 @@
<div id="view">
<!-- NOTE: views injected here by onos.js -->
</div>
<div id="overlays">
<!-- NOTE: overlays injected here, as needed -->
<div id="floatPanels">
<!-- NOTE: floating panels injected here, as needed -->
<!-- see onos.ui.addFloatingPanel -->
</div>
<div id="alerts">
<!-- NOTE: alert content injected here, as needed -->
......
......@@ -10,6 +10,10 @@
"",
null
],
"props": {}
"props": {
"latitude": 123.5,
"longitude": 67.8,
"anotherProp": "foobar"
}
}
}
......
{
"event": "addDevice",
"payload": {
"id": "of:0000000000000003",
"type": "switch",
"online": true,
"labels": [
"of:0000000000000003",
"3",
"",
null
],
"props": {
"latitude": 123.5,
"longitude": 67.8,
"anotherProp": "foobar"
},
"metaUi": {
"xpc": 57.3,
"ypc": 24.86,
"and": "other properties the UI wishes to remember..."
}
}
}
{
"event": "showDetails",
"sid": 9,
"payload": {
"id": "CA:4B:EE:A4:B0:33/-1",
"type": "host",
"propOrder": [
"MAC",
"IP",
"-",
"Latitude",
"Longitude"
],
"props": {
"MAC": "CA:4B:EE:A4:B0:33",
"IP": "[10.0.0.1]",
"-": "",
"Latitude": null,
"Longitude": null
}
}
}
{
"event": "showDetails",
"sid": 37,
"payload": {
"id": "of:000000000000000a",
"type": "switch",
"propOrder": [
"Name",
"Vendor",
"H/W Version",
"S/W Version",
"Serial Number",
"-",
"Latitude",
"Longitude",
"Ports",
"-",
"Master"
],
"props": {
"Name": null,
"Vendor": "Nicira, Inc.",
"H/W Version": "Open vSwitch",
"S/W Version": "2.0.1",
"Serial Number": "None",
"-": "",
"Latitude": null,
"Longitude": null,
"Ports": "5",
"Master":"local"
}
}
}
{
"event": "noop",
"event": "requestDetails",
"sid": 15,
"payload": {
"id": "xyyzy"
"id": "of:0000000000000003",
"class": "device"
}
}
......
{
"event": "requestDetails",
"sid": 9,
"payload": {
"id": "CA:4B:EE:A4:B0:33/-1",
"class": "host"
}
}
......@@ -4,7 +4,11 @@
"payload": {
"id": "62:4F:65:BF:FF:B3/-1",
"class": "host",
"x": 197,
"y": 177
"memento": {
"xpc": 57.3,
"ypc": 24.86,
"and": "other properties the UI wishes to remember..."
}
}
}
......
......@@ -5,5 +5,9 @@
"title": "Host Intent Scenario",
"params": {
"lastAuto": 0
}
},
"description": [
"Currently this is just a sketch of the event sequence,",
" but is NOT YET a runnable scenario."
]
}
\ No newline at end of file
......
{
"event": "removeHost",
"payload": {
"id": "A6:96:E5:03:52:5F/-1",
"ingress": "A6:96:E5:03:52:5F/-1/0-of:0000ffffffff0008/1",
"egress": "of:0000ffffffff0008/1-A6:96:E5:03:52:5F/-1/0",
"cp": {
"device": "of:0000ffffffff0008",
"port": 1
},
"labels": [
"10.0.0.17",
"A6:96:E5:03:52:5F"
],
"props": {}
}
}
......@@ -4,6 +4,11 @@
"id": "of:0000ffffffff0008",
"type": "switch",
"online": false,
"location": {
"type": "latlng",
"lat": 37.6,
"lng": 122.3
},
"labels": [
"0000ffffffff0008",
"FF:FF:FF:FF:00:08",
......
......@@ -20,6 +20,6 @@
"10. update link (increase width, update props)",
"11. update link (reduce width, update props)",
"12. remove link",
""
"13. remove host (10.0.0.17)"
]
}
\ No newline at end of file
}
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffffff04",
"type": "roadm",
"online": false,
"online": true,
"labels": [
"0000ffffffffff04",
"FF:FF:FF:FF:FF:04",
......
......@@ -3,15 +3,15 @@
"payload": {
"id": "of:0000ffffffff000A",
"type": "switch",
"online": false,
"online": true,
"labels": [
"0000ffffffff000A",
"FF:FF:FF:FF:00:0A",
"?"
],
"metaUi": {
"Zx": 832,
"Zy": 223
"x": 832,
"y": 223
}
}
}
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffff0001",
"type": "switch",
"online": false,
"online": true,
"labels": [
"0000ffffffff0001",
"FF:FF:FF:FF:00:01",
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffffff01",
"type": "roadm",
"online": false,
"online": true,
"labels": [
"0000ffffffffff01",
"FF:FF:FF:FF:FF:01",
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffff0004",
"type": "switch",
"online": false,
"online": true,
"labels": [
"0000ffffffff0004",
"FF:FF:FF:FF:00:04",
......
......@@ -3,15 +3,15 @@
"payload": {
"id": "of:0000ffffffffff0A",
"type": "roadm",
"online": false,
"online": true,
"labels": [
"0000ffffffffff0A",
"FF:FF:FF:FF:FF:0A",
"?"
],
"metaUi": {
"Zx": 840,
"Zy": 290
"x": 840,
"y": 290
}
}
}
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffffff09",
"type": "roadm",
"online": false,
"online": true,
"labels": [
"0000ffffffffff09",
"FF:FF:FF:FF:FF:09",
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff02/20-of:0000ffffffffff05/10",
"type": "optical",
"linkWidth": 4,
"src": "of:0000ffffffffff02",
"srcPort": "20",
"dst": "of:0000ffffffffff05",
"dstPort": "10",
"type": "optical",
"linkWidth": 6,
"props" : {
"BW": "80 G"
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff000A/2-of:0000ffffffffff0A/1",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffff000A",
"srcPort": "2",
"dst": "of:0000ffffffffff0A",
"dstPort": "1",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "100 G"
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff03/10-of:0000ffffffffff02/10",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffffff03",
"srcPort": "10",
"dst": "of:0000ffffffffff02",
"dstPort": "10",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
}
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffffff08",
"type": "roadm",
"online": false,
"online": true,
"labels": [
"0000ffffffffff08",
"FF:FF:FF:FF:FF:08",
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff07/21-of:0000ffffffffff05/20",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffffff07",
"srcPort": "21",
"dst": "of:0000ffffffffff05",
"dstPort": "20",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0001/2-of:0000ffffffffff01/1",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffff0001",
"srcPort": "2",
"dst": "of:0000ffffffffff01",
"dstPort": "1",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff09/20-of:0000ffffffffff0A/20",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffffff09",
"srcPort": "20",
"dst": "of:0000ffffffffff0A",
"dstPort": "20",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"src": "of:0000ffffffffff06",
"srcPort": "20",
"dst": "of:0000ffffffffff05",
"dstPort": "30",
"id": "of:0000ffffffffff07/30-of:0000ffffffffff08/20",
"type": "optical",
"linkWidth": 6,
"linkWidth": 4,
"src": "of:0000ffffffffff07",
"srcPort": "30",
"dst": "of:0000ffffffffff08",
"dstPort": "20",
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"src": "of:0000ffffffffff07",
"srcPort": "30",
"dst": "of:0000ffffffffff08",
"dstPort": "20",
"id": "of:0000ffffffffff02/10-of:0000ffffffffff01/10",
"type": "optical",
"linkWidth": 6,
"linkWidth": 2,
"src": "of:0000ffffffffff02",
"srcPort": "10",
"dst": "of:0000ffffffffff01",
"dstPort": "10",
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"src": "of:0000ffffffffff03",
"srcPort": "20",
"dst": "of:0000ffffffffff06",
"id": "of:0000ffffffffff04/27-of:0000ffffffffff08/10",
"src": "of:0000ffffffffff04",
"srcPort": "27",
"dst": "of:0000ffffffffff08",
"dstPort": "10",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
"BW": "30 G"
}
}
}
......
{
"event": "addLink",
"payload": {
"src": "of:0000ffffffffff02",
"srcPort": "10",
"dst": "of:0000ffffffffff01",
"dstPort": "10",
"id": "of:0000ffffffff0003/2-of:0000ffffffffff03/1",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffff0003",
"srcPort": "2",
"dst": "of:0000ffffffffff03",
"dstPort": "1",
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff09/1-of:0000ffffffff0009/2",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffffff09",
"srcPort": "1",
"dst": "of:0000ffffffff0009",
"dstPort": "2",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff03/30-of:0000ffffffffff04/10",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffffff03",
"srcPort": "30",
"dst": "of:0000ffffffffff04",
"dstPort": "10",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff07/20-of:0000ffffffffff09/10",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffffff07",
"srcPort": "20",
"dst": "of:0000ffffffffff09",
"dstPort": "10",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
}
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffffff03",
"type": "roadm",
"online": false,
"online": true,
"labels": [
"0000ffffffffff03",
"FF:FF:FF:FF:FF:03",
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff0A/10-of:0000ffffffffff08/30",
"type": "optical",
"linkWidth": 4,
"src": "of:0000ffffffffff0A",
"srcPort": "10",
"dst": "of:0000ffffffffff08",
"dstPort": "30",
"type": "optical",
"linkWidth": 6,
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0004/2-of:0000ffffffffff04/1",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffff0004",
"srcPort": "2",
"dst": "of:0000ffffffffff04",
"dstPort": "1",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff07/1-of:0000ffffffff0007/2",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffffff07",
"srcPort": "1",
"dst": "of:0000ffffffff0007",
"dstPort": "2",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"event": "updateDevice",
"payload": {
"src": "of:0000ffffffff0003",
"srcPort": "2",
"dst": "of:0000ffffffffff03",
"dstPort": "1",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "70 G"
"id": "of:0000ffffffffff06",
"type": "roadm",
"online": true,
"labels": [
"0000ffffffffff06",
"FF:FF:FF:FF:FF:06",
"?"
],
"metaUi": {
"x": 336,
"y": 254
}
}
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff06/20-of:0000ffffffffff05/30",
"src": "of:0000ffffffffff06",
"srcPort": "30",
"dst": "of:0000ffffffffff08",
"dstPort": "10",
"srcPort": "20",
"dst": "of:0000ffffffffff05",
"dstPort": "30",
"type": "optical",
"linkWidth": 6,
"linkWidth": 4,
"props" : {
"BW": "70 G"
}
......
{
"event": "addLink",
"payload": {
"src": "of:0000ffffffffff04",
"srcPort": "27",
"dst": "of:0000ffffffffff08",
"dstPort": "10",
"id": "of:0000ffffffffff03/20-of:0000ffffffffff06/10",
"type": "optical",
"linkWidth": 2,
"src": "of:0000ffffffffff03",
"srcPort": "20",
"dst": "of:0000ffffffffff06",
"dstPort": "10",
"props" : {
"BW": "30 G"
"BW": "70 G"
}
}
}
......
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffffff06/30-of:0000ffffffffff08/10",
"type": "optical",
"linkWidth": 4,
"src": "of:0000ffffffffff06",
"srcPort": "30",
"dst": "of:0000ffffffffff08",
"dstPort": "10",
"props" : {
"BW": "70 G"
}
}
}
{
"event": "updateDevice",
"payload": {
"id": "of:0000ffffffffff08",
"type": "roadm",
"online": false,
"labels": [
"0000ffffffffff08",
"FF:FF:FF:FF:FF:08",
"?"
],
"metaUi": {
"x": 539,
"y": 186
}
}
}
{
"event": "removeLink",
"payload": {
"id": "of:0000ffffffffff07/30-of:0000ffffffffff08/20",
"type": "optical",
"linkWidth": 4,
"src": "of:0000ffffffffff07",
"srcPort": "30",
"dst": "of:0000ffffffffff08",
"dstPort": "20",
"props" : {
"BW": "70 G"
}
}
}
{
"event": "removeLink",
"payload": {
"id": "of:0000ffffffffff04/27-of:0000ffffffffff08/10",
"src": "of:0000ffffffffff04",
"srcPort": "27",
"dst": "of:0000ffffffffff08",
"dstPort": "10",
"type": "optical",
"linkWidth": 2,
"props" : {
"BW": "30 G"
}
}
}
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffff0007",
"type": "switch",
"online": false,
"online": true,
"labels": [
"0000ffffffff0007",
"FF:FF:FF:FF:00:07",
......
{
"event": "removeLink",
"payload": {
"id": "of:0000ffffffffff0A/10-of:0000ffffffffff08/30",
"type": "optical",
"linkWidth": 4,
"src": "of:0000ffffffffff0A",
"srcPort": "10",
"dst": "of:0000ffffffffff08",
"dstPort": "30",
"props" : {
"BW": "70 G"
}
}
}
{
"event": "removeLink",
"payload": {
"id": "of:0000ffffffffff06/30-of:0000ffffffffff08/10",
"type": "optical",
"linkWidth": 4,
"src": "of:0000ffffffffff06",
"srcPort": "30",
"dst": "of:0000ffffffffff08",
"dstPort": "10",
"props" : {
"BW": "70 G"
}
}
}
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffff0009",
"type": "switch",
"online": false,
"online": true,
"labels": [
"0000ffffffff0009",
"FF:FF:FF:FF:00:09",
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffffff02",
"type": "roadm",
"online": false,
"online": true,
"labels": [
"0000ffffffffff02",
"FF:FF:FF:FF:FF:02",
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffff0003",
"type": "switch",
"online": false,
"online": true,
"labels": [
"0000ffffffff0003",
"FF:FF:FF:FF:00:03",
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffffff07",
"type": "roadm",
"online": false,
"online": true,
"labels": [
"0000ffffffffff07",
"FF:FF:FF:FF:FF:07",
......
......@@ -3,7 +3,7 @@
"payload": {
"id": "of:0000ffffffffff05",
"type": "roadm",
"online": false,
"online": true,
"labels": [
"0000ffffffffff05",
"FF:FF:FF:FF:FF:05",
......
......@@ -6,5 +6,15 @@
"title": "Startup Scenario",
"params": {
"lastAuto": 32
}
},
"description": [
"Loads 16 devices (10 optical, 6 packet)",
" and their associated links.",
"",
"Press 'S' to load initial events.",
"",
"Press spacebar to complete the scenario...",
" * 4 events - device online, add 3 links",
" * 5 events - device offline, remove 4 links"
]
}
\ No newline at end of file
......
......@@ -3,6 +3,11 @@
"id": "of:0000000000000001",
"type": "roadm",
"propOrder": [ "name", "type", "-", "dpid", "latitude", "longitude", "allowed" ],
"location": {
"type": "latlng",
"lat": 37.6,
"lng": 122.3
},
"props": {
"allowed": true,
"latitude": 37.6,
......
......@@ -3,6 +3,11 @@
"id": "of:0000000000000002",
"type": "switch",
"propOrder": [ "name", "type", "dpid", "latitude", "longitude", "allowed" ],
"location": {
"type": "latlng",
"lat": 37.6,
"lng": 122.3
},
"props": {
"allowed": true,
"latitude": 37.3,
......
......@@ -3,6 +3,11 @@
"id": "of:0000000000000003",
"type": "switch",
"propOrder": [ "name", "type", "dpid", "latitude", "longitude", "allowed" ],
"location": {
"type": "latlng",
"lat": 33.9,
"lng": 118.4
},
"props": {
"allowed": true,
"latitude": 33.9,
......
......@@ -3,6 +3,11 @@
"id": "of:0000000000000004",
"type": "switch",
"propOrder": [ "name", "type", "dpid", "latitude", "longitude", "allowed" ],
"location": {
"type": "latlng",
"lat": 32.8,
"lng": 117.1
},
"props": {
"allowed": true,
"latitude": 32.8,
......
......@@ -50,6 +50,7 @@
// internal state
var views = {},
fpanels = {},
current = {
view: null,
ctx: '',
......@@ -57,7 +58,7 @@
theme: settings.theme
},
built = false,
errorCount = 0,
buildErrors = [],
keyHandler = {
globalKeys: {},
maskedKeys: {},
......@@ -70,7 +71,11 @@
};
// DOM elements etc.
var $view,
// TODO: verify existence of following elements...
var $view = d3.select('#view'),
$floatPanels = d3.select('#floatPanels'),
$alerts = d3.select('#alerts'),
// note, following elements added programmatically...
$mastRadio;
......@@ -241,10 +246,22 @@
setView(view, hash, t);
}
function buildError(msg) {
buildErrors.push(msg);
}
function reportBuildErrors() {
traceFn('reportBuildErrors');
// TODO: validate registered views / nav-item linkage etc.
console.log('(no build errors)');
var nerr = buildErrors.length,
errmsg;
if (!nerr) {
console.log('(no build errors)');
} else {
errmsg = 'Build errors: ' + nerr + ' found...\n\n' +
buildErrors.join('\n');
doAlert(errmsg);
console.error(errmsg);
}
}
// returns the reference if it is a function, null otherwise
......@@ -449,22 +466,20 @@
}
function createAlerts() {
var al = d3.select('#alerts')
.style('display', 'block');
al.append('span')
$alerts.style('display', 'block');
$alerts.append('span')
.attr('class', 'close')
.text('X')
.on('click', closeAlerts);
al.append('pre');
al.append('p').attr('class', 'footnote')
$alerts.append('pre');
$alerts.append('p').attr('class', 'footnote')
.text('Press ESCAPE to close');
alerts.open = true;
alerts.count = 0;
}
function closeAlerts() {
d3.select('#alerts')
.style('display', 'none')
$alerts.style('display', 'none')
.html('');
alerts.open = false;
}
......@@ -474,7 +489,7 @@
oldContent;
if (alerts.count) {
oldContent = d3.select('#alerts pre').html();
oldContent = $alerts.select('pre').html();
}
lines = msg.split('\n');
......@@ -485,7 +500,7 @@
lines += '\n----\n' + oldContent;
}
d3.select('#alerts pre').html(lines);
$alerts.select('pre').html(lines);
alerts.count++;
}
......@@ -691,6 +706,53 @@
libApi[libName] = api;
},
// TODO: implement floating panel as a class
// TODO: parameterize position (currently hard-coded to TopRight)
/*
* Creates div in floating panels block, with the given id.
* Returns panel token used to interact with the panel
*/
addFloatingPanel: function (id, position) {
var pos = position || 'TR',
el,
fp;
if (fpanels[id]) {
buildError('Float panel with id "' + id + '" already exists.');
return null;
}
el = $floatPanels.append('div')
.attr('id', id)
.attr('class', 'fpanel');
fp = {
id: id,
el: el,
pos: pos,
show: function () {
console.log('show pane: ' + id);
el.transition().duration(750)
.style('right', '20px')
.style('opacity', 1);
},
hide: function () {
console.log('hide pane: ' + id);
el.transition().duration(750)
.style('right', '-320px')
.style('opacity', 0);
},
empty: function () {
return el.html('');
},
append: function (what) {
return el.append(what);
}
};
fpanels[id] = fp;
return fp;
},
// TODO: it remains to be seen whether we keep this style of docs
/** @api ui addView( vid, nid, cb )
* Adds a view to the UI.
......@@ -782,7 +844,6 @@
}
built = true;
$view = d3.select('#view');
$mastRadio = d3.select('#mastRadio');
$(window).on('hashchange', hash);
......
......@@ -96,3 +96,45 @@
fill: white;
stroke: red;
}
/* detail topo-detail pane */
#topo-detail {
/* gets base CSS from .fpanel in floatPanel.css */
}
#topo-detail h2 {
margin: 8px 4px;
color: black;
vertical-align: middle;
}
#topo-detail h2 img {
height: 32px;
padding-right: 8px;
vertical-align: middle;
}
#topo-detail p, table {
margin: 4px 4px;
}
#topo-detail td.label {
font-style: italic;
color: #777;
padding-right: 12px;
}
#topo-detail td.value {
}
#topo-detail hr {
height: 1px;
color: #ccc;
background-color: #ccc;
border: 0;
}
......
......@@ -30,6 +30,7 @@
// configuration data
var config = {
useLiveData: true,
fnTrace: true,
debugOn: false,
debug: {
showNodeXY: true,
......@@ -151,7 +152,7 @@
webSock,
deviceLabelIndex = 0,
hostLabelIndex = 0,
detailPane,
selectOrder = [],
selections = {},
......@@ -180,12 +181,21 @@
return config.debugOn && config.debug[what];
}
function fnTrace(msg, id) {
if (config.fnTrace) {
console.log('FN: ' + msg + ' [' + id + ']');
}
}
// ==============================
// Key Callbacks
function testMe(view) {
view.alert('test');
detailPane.show();
setTimeout(function () {
detailPane.hide();
}, 3000);
}
function abortIfLive() {
......@@ -220,7 +230,7 @@
var v = scenario.view,
frame;
if (stack.length === 0) {
v.alert('Error:\n\nNo event #' + evn + ' found.');
v.alert('Oops!\n\nNo event #' + evn + ' found.');
return;
}
frame = stack.shift();
......@@ -279,14 +289,6 @@
view.alert('unpin() callback')
}
function requestPath(view) {
var payload = {
one: selections[selectOrder[0]].obj.id,
two: selections[selectOrder[1]].obj.id
}
sendMessage('requestPath', payload);
}
// ==============================
// Radio Button Callbacks
......@@ -334,6 +336,7 @@
function logicError(msg) {
// TODO, report logic error to server, via websock, so it can be logged
network.view.alert('Logic Error:\n\n' + msg);
console.warn(msg);
}
var eventDispatch = {
......@@ -345,11 +348,13 @@
updateHost: updateHost,
removeDevice: stillToImplement,
removeLink: removeLink,
removeHost: stillToImplement,
removeHost: removeHost,
showDetails: showDetails,
showPath: showPath
};
function addDevice(data) {
fnTrace('addDevice', data.payload.id);
var device = data.payload,
nodeData = createDeviceNode(device);
network.nodes.push(nodeData);
......@@ -359,6 +364,7 @@
}
function addLink(data) {
fnTrace('addLink', data.payload.id);
var link = data.payload,
lnk = createLink(link);
if (lnk) {
......@@ -370,6 +376,7 @@
}
function addHost(data) {
fnTrace('addHost', data.payload.id);
var host = data.payload,
node = createHostNode(host),
lnk;
......@@ -379,6 +386,7 @@
lnk = createHostLink(host);
if (lnk) {
node.linkData = lnk; // cache ref on its host
network.links.push(lnk);
network.lookup[host.ingress] = lnk;
network.lookup[host.egress] = lnk;
......@@ -387,7 +395,9 @@
network.force.start();
}
// TODO: fold updateX(...) methods into one base method; remove duplication
function updateDevice(data) {
fnTrace('updateDevice', data.payload.id);
var device = data.payload,
id = device.id,
nodeData = network.lookup[id];
......@@ -400,6 +410,7 @@
}
function updateLink(data) {
fnTrace('updateLink', data.payload.id);
var link = data.payload,
id = link.id,
linkData = network.lookup[id];
......@@ -412,6 +423,7 @@
}
function updateHost(data) {
fnTrace('updateHost', data.payload.id);
var host = data.payload,
id = host.id,
hostData = network.lookup[id];
......@@ -423,7 +435,9 @@
}
}
// TODO: fold removeX(...) methods into base method - remove dup code
function removeLink(data) {
fnTrace('removeLink', data.payload.id);
var link = data.payload,
id = link.id,
linkData = network.lookup[id];
......@@ -434,7 +448,26 @@
}
}
function removeHost(data) {
fnTrace('removeHost', data.payload.id);
var host = data.payload,
id = host.id,
hostData = network.lookup[id];
if (hostData) {
removeHostElement(hostData);
} else {
logicError('removeHost lookup fail. ID = "' + id + '"');
}
}
function showDetails(data) {
fnTrace('showDetails', data.payload.id);
populateDetails(data.payload);
detailPane.show();
}
function showPath(data) {
fnTrace('showPath', data.payload.id);
var links = data.payload.links,
s = [ data.event + "\n" + links.length ];
links.forEach(function (d, i) {
......@@ -470,6 +503,32 @@
}
// ==============================
// Out-going messages...
function getSel(idx) {
return selections[selectOrder[idx]];
}
// for now, just a host-to-host intent, (and implicit start-monitoring)
function requestPath() {
var payload = {
one: getSel(0).obj.id,
two: getSel(1).obj.id
};
sendMessage('requestPath', payload);
}
// request details for the selected element
function requestDetails() {
var data = getSel(0).obj,
payload = {
id: data.id,
class: data.class
};
sendMessage('requestDetails', payload);
}
// ==============================
// force layout modification functions
function translate(x, y) {
......@@ -589,19 +648,18 @@
//link .foo() .bar() ...
// operate on exiting links:
// TODO: better transition (longer as a dashed, grey line)
link.exit()
.attr({
'stroke-dasharray': '3, 3'
})
.style('opacity', 0.4)
.transition()
.duration(2000)
.duration(1500)
.attr({
'stroke-dasharray': '3, 12'
})
.transition()
.duration(1000)
.duration(500)
.style('opacity', 0.0)
.remove();
}
......@@ -855,17 +913,37 @@
//node .foo() .bar() ...
// operate on exiting nodes:
// TODO: figure out how to remove the node 'g' AND its children
node.exit()
// Note that the node is removed after 2 seconds.
// Sub element animations should be shorter than 2 seconds.
var exiting = node.exit()
.transition()
.duration(750)
.attr({
opacity: 0,
cx: 0,
cy: 0,
r: 0
})
.duration(2000)
.style('opacity', 0)
.remove();
// host node exits....
exiting.filter('.host').each(function (d) {
var node = d3.select(this);
node.select('text')
.style('opacity', 0.5)
.transition()
.duration(1000)
.style('opacity', 0);
// note, leave <g>.remove to remove this element
node.select('circle')
.style('stroke-fill', '#555')
.style('fill', '#888')
.style('opacity', 0.5)
.transition()
.duration(1500)
.attr('r', 0);
// note, leave <g>.remove to remove this element
});
// TODO: device node exits
}
function find(id, array) {
......@@ -882,12 +960,27 @@
delete network.lookup[linkData.id];
// remove from links array
var idx = find(linkData.id, network.links);
network.links.splice(linkData.index, 1);
network.links.splice(idx, 1);
// remove from SVG
updateLinks();
network.force.resume();
}
function removeHostElement(hostData) {
// first, remove associated hostLink...
removeLinkElement(hostData.linkData);
// remove from lookup cache
delete network.lookup[hostData.id];
// remove from nodes array
var idx = find(hostData.id, network.nodes);
network.nodes.splice(idx, 1);
// remove from SVG
updateNodes();
network.force.resume();
}
function tick() {
node.attr({
transform: function (d) { return translate(d.x, d.y); }
......@@ -951,6 +1044,8 @@
var sid = 0;
// TODO: use cache of pending messages (key = sid) to reconcile responses
function sendMessage(evType, payload) {
var toSend = {
event: evType,
......@@ -969,7 +1064,6 @@
wsTrace('rx', msg);
}
function wsTrace(rxtx, msg) {
console.log('[' + rxtx + '] ' + msg);
// TODO: integrate with trace view
//if (trace) {
......@@ -998,7 +1092,7 @@
if (meta && n.classed('selected')) {
deselectObject(obj.id);
//flyinPane(null);
updateDetailPane();
return;
}
......@@ -1010,17 +1104,16 @@
selectOrder.push(obj.id);
n.classed('selected', true);
//flyinPane(obj);
updateDetailPane();
}
function deselectObject(id) {
var obj = selections[id];
if (obj) {
d3.select(obj.el).classed('selected', false);
selections[id] = null;
// TODO: use splice to remove element
delete selections[id];
}
//flyinPane(null);
updateDetailPane();
}
function deselectAll() {
......@@ -1028,10 +1121,10 @@
node.classed('selected', false);
selections = {};
selectOrder = [];
//flyinPane(null);
updateDetailPane();
}
// TODO: this click handler does not get unloaded when the view does
// FIXME: this click handler does not get unloaded when the view does
$('#view').on('click', function(e) {
if (!$(e.target).closest('.node').length) {
if (!e.metaKey) {
......@@ -1040,6 +1133,66 @@
}
});
// update the state of the detail pane, based on current selections
function updateDetailPane() {
var nSel = selectOrder.length;
if (!nSel) {
detailPane.hide();
} else if (nSel === 1) {
singleSelect();
} else {
multiSelect();
}
}
function singleSelect() {
requestDetails();
// NOTE: detail pane will be shown from showDetails event.
}
function multiSelect() {
// TODO: use detail pane for multi-select view.
//detailPane.show();
}
function populateDetails(data) {
detailPane.empty();
var title = detailPane.append("h2"),
table = detailPane.append("table"),
tbody = table.append("tbody");
$('<img src="img/' + data.type + '.png">').appendTo(title);
$('<span>').attr('class', 'icon').text(data.id).appendTo(title);
data.propOrder.forEach(function(p) {
if (p === '-') {
addSep(tbody);
} else {
addProp(tbody, p, data.props[p]);
}
});
function addSep(tbody) {
var tr = tbody.append('tr');
$('<hr>').appendTo(tr.append('td').attr('colspan', 2));
}
function addProp(tbody, label, value) {
var tr = tbody.append('tr');
tr.append('td')
.attr('class', 'label')
.text(label + ' :');
tr.append('td')
.attr('class', 'value')
.text(value);
}
}
// ==============================
// Test harness code
function prepareScenario(view, ctx, dbg) {
var sc = scenario,
......@@ -1058,13 +1211,12 @@
d3.json(urlSc, function(err, data) {
var p = data && data.params || {},
desc = data && data.description || null,
intro;
intro = data && data.title;
if (err) {
view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
} else {
sc.params = p;
intro = "Scenario loaded: " + ctx + '\n\n' + data.title;
if (desc) {
intro += '\n\n ' + desc.join('\n ');
}
......@@ -1140,16 +1292,18 @@
d.fixed = true;
d3.select(self).classed('fixed', true);
if (config.useLiveData) {
tellServerCoords(d);
sendUpdateMeta(d);
}
}
function tellServerCoords(d) {
function sendUpdateMeta(d) {
sendMessage('updateMeta', {
id: d.id,
'class': d.class,
x: Math.floor(d.x),
y: Math.floor(d.y)
'memento': {
x: Math.floor(d.x),
y: Math.floor(d.y)
}
});
}
......@@ -1207,4 +1361,6 @@
resize: resize
});
detailPane = onos.ui.addFloatingPanel('topo-detail');
}(ONOS));
......