Resolving merge conflicts
diff --git a/apps/metrics/intent/pom.xml b/apps/metrics/intent/pom.xml
new file mode 100644
index 0000000..4d42065
--- /dev/null
+++ b/apps/metrics/intent/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onos-app-metrics</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>onos-app-metrics-intent</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>ONOS intent metrics application</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onlab.onos</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java
new file mode 100644
index 0000000..38366b6
--- /dev/null
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java
@@ -0,0 +1,319 @@
+package org.onlab.onos.metrics.intent;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import com.google.common.collect.ImmutableList;
+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.metrics.MetricsComponent;
+import org.onlab.metrics.MetricsFeature;
+import org.onlab.metrics.MetricsService;
+import org.onlab.onos.net.intent.IntentEvent;
+import org.onlab.onos.net.intent.IntentListener;
+import org.onlab.onos.net.intent.IntentService;
+import org.slf4j.Logger;
+
+/**
+ * ONOS Intent Metrics Application that collects intent-related metrics.
+ */
+@Component(immediate = true)
+@Service
+public class IntentMetrics implements IntentMetricsService,
+ IntentListener {
+ private static final Logger log = getLogger(IntentMetrics.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected IntentService intentService;
+ private LinkedList<IntentEvent> lastEvents = new LinkedList<>();
+ private static final int LAST_EVENTS_MAX_N = 100;
+
+ //
+ // Metrics
+ //
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MetricsService metricsService;
+ //
+ private static final String COMPONENT_NAME = "Intent";
+ private static final String FEATURE_SUBMITTED_NAME = "Submitted";
+ private static final String FEATURE_INSTALLED_NAME = "Installed";
+ private static final String FEATURE_WITHDRAW_REQUESTED_NAME =
+ "WithdrawRequested";
+ private static final String FEATURE_WITHDRAWN_NAME = "Withdrawn";
+ private static final String GAUGE_TIMESTAMP_NAME = "Timestamp.EpochMs";
+ private static final String METER_RATE_NAME = "Rate";
+ //
+ private MetricsComponent metricsComponent;
+ private MetricsFeature metricsFeatureSubmitted;
+ private MetricsFeature metricsFeatureInstalled;
+ private MetricsFeature metricsFeatureWithdrawRequested;
+ private MetricsFeature metricsFeatureWithdrawn;
+ //
+ // Timestamps:
+ // - Intent Submitted API operation (ms from the Epoch)
+ // - Intent Installed operation completion (ms from the Epoch)
+ // - Intent Withdraw Requested API operation (ms from the Epoch)
+ // - Intent Withdrawn operation completion (ms from the Epoch)
+ //
+ private volatile long intentSubmittedTimestampEpochMs = 0;
+ private volatile long intentInstalledTimestampEpochMs = 0;
+ private volatile long intentWithdrawRequestedTimestampEpochMs = 0;
+ private volatile long intentWithdrawnTimestampEpochMs = 0;
+ //
+ private Gauge<Long> intentSubmittedTimestampEpochMsGauge;
+ private Gauge<Long> intentInstalledTimestampEpochMsGauge;
+ private Gauge<Long> intentWithdrawRequestedTimestampEpochMsGauge;
+ private Gauge<Long> intentWithdrawnTimestampEpochMsGauge;
+ //
+ // Rate meters:
+ // - Rate of the Submitted Intent API operations
+ // - Rate of the Installed Intent operations
+ // - Rate of the Withdrawn Requested Intent API operations
+ // - Rate of the Withdrawn Intent operations
+ //
+ private Meter intentSubmittedRateMeter;
+ private Meter intentInstalledRateMeter;
+ private Meter intentWithdrawRequestedRateMeter;
+ private Meter intentWithdrawnRateMeter;
+
+ @Activate
+ protected void activate() {
+ clear();
+ registerMetrics();
+ intentService.addListener(this);
+ log.info("ONOS Intent Metrics started.");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ intentService.removeListener(this);
+ removeMetrics();
+ clear();
+ log.info("ONOS Intent Metrics stopped.");
+ }
+
+ @Override
+ public List<IntentEvent> getEvents() {
+ synchronized (lastEvents) {
+ return ImmutableList.<IntentEvent>copyOf(lastEvents);
+ }
+ }
+
+ @Override
+ public Gauge<Long> intentSubmittedTimestampEpochMsGauge() {
+ return intentSubmittedTimestampEpochMsGauge;
+ }
+
+ @Override
+ public Gauge<Long> intentInstalledTimestampEpochMsGauge() {
+ return intentInstalledTimestampEpochMsGauge;
+ }
+
+ @Override
+ public Gauge<Long> intentWithdrawRequestedTimestampEpochMsGauge() {
+ return intentWithdrawRequestedTimestampEpochMsGauge;
+ }
+
+ @Override
+ public Gauge<Long> intentWithdrawnTimestampEpochMsGauge() {
+ return intentWithdrawnTimestampEpochMsGauge;
+ }
+
+ @Override
+ public Meter intentSubmittedRateMeter() {
+ return intentSubmittedRateMeter;
+ }
+
+ @Override
+ public Meter intentInstalledRateMeter() {
+ return intentInstalledRateMeter;
+ }
+
+ @Override
+ public Meter intentWithdrawRequestedRateMeter() {
+ return intentWithdrawRequestedRateMeter;
+ }
+
+ @Override
+ public Meter intentWithdrawnRateMeter() {
+ return intentWithdrawnRateMeter;
+ }
+
+ @Override
+ public void event(IntentEvent event) {
+ synchronized (lastEvents) {
+ //
+ // TODO: The processing below is incomplete: we don't have
+ // an event equivalent of "Withdraw Requested"
+ //
+ switch (event.type()) {
+ case SUBMITTED:
+ intentSubmittedTimestampEpochMs = System.currentTimeMillis();
+ intentSubmittedRateMeter.mark(1);
+ break;
+ case INSTALLED:
+ intentInstalledTimestampEpochMs = System.currentTimeMillis();
+ intentInstalledRateMeter.mark(1);
+ break;
+ case FAILED:
+ // TODO: Just ignore?
+ break;
+ /*
+ case WITHDRAW_REQUESTED:
+ intentWithdrawRequestedTimestampEpochMs =
+ System.currentTimeMillis();
+ intentWithdrawRequestedRateMeter.mark(1);
+ break;
+ */
+ case WITHDRAWN:
+ intentWithdrawnTimestampEpochMs = System.currentTimeMillis();
+ intentWithdrawnRateMeter.mark(1);
+ break;
+ default:
+ break;
+ }
+
+ //
+ // Keep only the last N events, where N = LAST_EVENTS_MAX_N
+ //
+ while (lastEvents.size() >= LAST_EVENTS_MAX_N) {
+ lastEvents.remove();
+ }
+ lastEvents.add(event);
+ }
+
+ log.debug("Intent Event: time = {} type = {} event = {}",
+ event.time(), event.type(), event);
+ }
+
+ /**
+ * Clears the internal state.
+ */
+ private void clear() {
+ synchronized (lastEvents) {
+ intentSubmittedTimestampEpochMs = 0;
+ intentInstalledTimestampEpochMs = 0;
+ intentWithdrawRequestedTimestampEpochMs = 0;
+ intentWithdrawnTimestampEpochMs = 0;
+ lastEvents.clear();
+ }
+ }
+
+ /**
+ * Registers the metrics.
+ */
+ private void registerMetrics() {
+ metricsComponent = metricsService.registerComponent(COMPONENT_NAME);
+ //
+ metricsFeatureSubmitted =
+ metricsComponent.registerFeature(FEATURE_SUBMITTED_NAME);
+ metricsFeatureInstalled =
+ metricsComponent.registerFeature(FEATURE_INSTALLED_NAME);
+ metricsFeatureWithdrawRequested =
+ metricsComponent.registerFeature(FEATURE_WITHDRAW_REQUESTED_NAME);
+ metricsFeatureWithdrawn =
+ metricsComponent.registerFeature(FEATURE_WITHDRAWN_NAME);
+ //
+ intentSubmittedTimestampEpochMsGauge =
+ metricsService.registerMetric(metricsComponent,
+ metricsFeatureSubmitted,
+ GAUGE_TIMESTAMP_NAME,
+ new Gauge<Long>() {
+ @Override
+ public Long getValue() {
+ return intentSubmittedTimestampEpochMs;
+ }
+ });
+ //
+ intentInstalledTimestampEpochMsGauge =
+ metricsService.registerMetric(metricsComponent,
+ metricsFeatureInstalled,
+ GAUGE_TIMESTAMP_NAME,
+ new Gauge<Long>() {
+ @Override
+ public Long getValue() {
+ return intentInstalledTimestampEpochMs;
+ }
+ });
+ //
+ intentWithdrawRequestedTimestampEpochMsGauge =
+ metricsService.registerMetric(metricsComponent,
+ metricsFeatureWithdrawRequested,
+ GAUGE_TIMESTAMP_NAME,
+ new Gauge<Long>() {
+ @Override
+ public Long getValue() {
+ return intentWithdrawRequestedTimestampEpochMs;
+ }
+ });
+ //
+ intentWithdrawnTimestampEpochMsGauge =
+ metricsService.registerMetric(metricsComponent,
+ metricsFeatureWithdrawn,
+ GAUGE_TIMESTAMP_NAME,
+ new Gauge<Long>() {
+ @Override
+ public Long getValue() {
+ return intentWithdrawnTimestampEpochMs;
+ }
+ });
+ //
+ intentSubmittedRateMeter =
+ metricsService.createMeter(metricsComponent,
+ metricsFeatureSubmitted,
+ METER_RATE_NAME);
+ //
+ intentInstalledRateMeter =
+ metricsService.createMeter(metricsComponent,
+ metricsFeatureInstalled,
+ METER_RATE_NAME);
+ //
+ intentWithdrawRequestedRateMeter =
+ metricsService.createMeter(metricsComponent,
+ metricsFeatureWithdrawRequested,
+ METER_RATE_NAME);
+ //
+ intentWithdrawnRateMeter =
+ metricsService.createMeter(metricsComponent,
+ metricsFeatureWithdrawn,
+ METER_RATE_NAME);
+ }
+
+ /**
+ * Removes the metrics.
+ */
+ private void removeMetrics() {
+ metricsService.removeMetric(metricsComponent,
+ metricsFeatureSubmitted,
+ GAUGE_TIMESTAMP_NAME);
+ metricsService.removeMetric(metricsComponent,
+ metricsFeatureInstalled,
+ GAUGE_TIMESTAMP_NAME);
+ metricsService.removeMetric(metricsComponent,
+ metricsFeatureWithdrawRequested,
+ GAUGE_TIMESTAMP_NAME);
+ metricsService.removeMetric(metricsComponent,
+ metricsFeatureWithdrawn,
+ GAUGE_TIMESTAMP_NAME);
+ metricsService.removeMetric(metricsComponent,
+ metricsFeatureSubmitted,
+ METER_RATE_NAME);
+ metricsService.removeMetric(metricsComponent,
+ metricsFeatureInstalled,
+ METER_RATE_NAME);
+ metricsService.removeMetric(metricsComponent,
+ metricsFeatureWithdrawRequested,
+ METER_RATE_NAME);
+ metricsService.removeMetric(metricsComponent,
+ metricsFeatureWithdrawn,
+ METER_RATE_NAME);
+ }
+}
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java
new file mode 100644
index 0000000..4acd00f
--- /dev/null
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java
@@ -0,0 +1,85 @@
+package org.onlab.onos.metrics.intent;
+
+import java.util.List;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import org.onlab.onos.net.intent.IntentEvent;
+
+/**
+ * Service interface exported by IntentMetrics.
+ */
+public interface IntentMetricsService {
+ /**
+ * Gets the last saved intent events.
+ *
+ * @return the last saved intent events.
+ */
+ public List<IntentEvent> getEvents();
+
+ /**
+ * Gets the Metrics' Gauge for the intent SUBMITTED event timestamp
+ * (ms from the epoch).
+ *
+ * @return the Metrics' Gauge for the intent SUBMITTED event timestamp
+ * (ms from the epoch)
+ */
+ public Gauge<Long> intentSubmittedTimestampEpochMsGauge();
+
+ /**
+ * Gets the Metrics' Gauge for the intent INSTALLED event timestamp
+ * (ms from the epoch).
+ *
+ * @return the Metrics' Gauge for the intent INSTALLED event timestamp
+ * (ms from the epoch)
+ */
+ public Gauge<Long> intentInstalledTimestampEpochMsGauge();
+
+ /**
+ * Gets the Metrics' Gauge for the intent WITHDRAW_REQUESTED event
+ * timestamp (ms from the epoch).
+ *
+ * TODO: This intent event is not implemented yet.
+ *
+ * @return the Metrics' Gauge for the intent WITHDRAW_REQUESTED event
+ * timestamp (ms from the epoch)
+ */
+ public Gauge<Long> intentWithdrawRequestedTimestampEpochMsGauge();
+
+ /**
+ * Gets the Metrics' Gauge for the intent WITHDRAWN event timestamp
+ * (ms from the epoch).
+ *
+ * @return the Metrics' Gauge for the intent WITHDRAWN event timestamp
+ * (ms from the epoch)
+ */
+ public Gauge<Long> intentWithdrawnTimestampEpochMsGauge();
+
+ /**
+ * Gets the Metrics' Meter for the submitted intents event rate.
+ *
+ * @return the Metrics' Meter for the submitted intents event rate
+ */
+ public Meter intentSubmittedRateMeter();
+
+ /**
+ * Gets the Metrics' Meter for the installed intents event rate.
+ *
+ * @return the Metrics' Meter for the installed intent event rate
+ */
+ public Meter intentInstalledRateMeter();
+
+ /**
+ * Gets the Metrics' Meter for the withdraw requested intents event rate.
+ *
+ * @return the Metrics' Meter for the withdraw requested intents event rate
+ */
+ public Meter intentWithdrawRequestedRateMeter();
+
+ /**
+ * Gets the Metrics' Meter for the withdraw completed intents event rate.
+ *
+ * @return the Metrics' Meter for the withdraw completed intents event rate
+ */
+ public Meter intentWithdrawnRateMeter();
+}
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsListCommand.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsListCommand.java
new file mode 100644
index 0000000..e07c3a5
--- /dev/null
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsListCommand.java
@@ -0,0 +1,68 @@
+package org.onlab.onos.metrics.intent.cli;
+
+import java.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.metrics.intent.IntentMetricsService;
+import org.onlab.onos.net.intent.IntentEvent;
+
+/**
+ * Command to show the list of last intent events.
+ */
+@Command(scope = "onos", name = "intents-events",
+ description = "Lists the last intent events")
+public class IntentEventsListCommand extends AbstractShellCommand {
+
+ private static final String FORMAT_EVENT = "Event=%s";
+
+ @Override
+ protected void execute() {
+ IntentMetricsService service = get(IntentMetricsService.class);
+
+ if (outputJson()) {
+ print("%s", json(service.getEvents()));
+ } else {
+ for (IntentEvent event : service.getEvents()) {
+ print(FORMAT_EVENT, event);
+ print(""); // Extra empty line for clarity
+ }
+ }
+ }
+
+ /**
+ * Produces a JSON array of intent events.
+ *
+ * @param intentEvents the intent events with the data
+ * @return JSON array with the intent events
+ */
+ private JsonNode json(List<IntentEvent> intentEvents) {
+ ObjectMapper mapper = new ObjectMapper();
+ ArrayNode result = mapper.createArrayNode();
+
+ for (IntentEvent event : intentEvents) {
+ result.add(json(mapper, event));
+ }
+ return result;
+ }
+
+ /**
+ * Produces JSON object for a intent event.
+ *
+ * @param mapper the JSON object mapper to use
+ * @param intentEvent the intent event with the data
+ * @return JSON object for the intent event
+ */
+ private ObjectNode json(ObjectMapper mapper, IntentEvent intentEvent) {
+ ObjectNode result = mapper.createObjectNode();
+
+ result.put("time", intentEvent.time())
+ .put("type", intentEvent.type().toString())
+ .put("event", intentEvent.toString());
+ return result;
+ }
+}
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java
new file mode 100644
index 0000000..204cfd6
--- /dev/null
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java
@@ -0,0 +1,132 @@
+package org.onlab.onos.metrics.intent.cli;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.json.MetricsModule;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.metrics.intent.IntentMetricsService;
+
+/**
+ * Command to show the intent events metrics.
+ */
+@Command(scope = "onos", name = "intents-events-metrics",
+ description = "Lists intent events metrics")
+public class IntentEventsMetricsCommand extends AbstractShellCommand {
+
+ private static final String FORMAT_GAUGE =
+ "Intent %s Event Timestamp (ms from epoch)=%d";
+ private static final String FORMAT_METER =
+ "Intent %s Events count=%d rate(events/sec) mean=%f m1=%f m5=%f m15=%f";
+
+ @Override
+ protected void execute() {
+ IntentMetricsService service = get(IntentMetricsService.class);
+ Gauge<Long> gauge;
+ Meter meter;
+
+ if (outputJson()) {
+ ObjectMapper mapper = new ObjectMapper()
+ .registerModule(new MetricsModule(TimeUnit.SECONDS,
+ TimeUnit.MILLISECONDS,
+ false));
+ ObjectNode result = mapper.createObjectNode();
+ //
+ gauge = service.intentSubmittedTimestampEpochMsGauge();
+ result.put("intentSubmittedTimestamp", json(mapper, gauge));
+ gauge = service.intentInstalledTimestampEpochMsGauge();
+ result.put("intentInstalledTimestamp", json(mapper, gauge));
+ gauge = service.intentWithdrawRequestedTimestampEpochMsGauge();
+ result.put("intentWithdrawRequestedTimestamp",
+ json(mapper, gauge));
+ gauge = service.intentWithdrawnTimestampEpochMsGauge();
+ result.put("intentWithdrawnTimestamp", json(mapper, gauge));
+ //
+ meter = service.intentSubmittedRateMeter();
+ result.put("intentSubmittedRate", json(mapper, meter));
+ meter = service.intentInstalledRateMeter();
+ result.put("intentInstalledRate", json(mapper, meter));
+ meter = service.intentWithdrawRequestedRateMeter();
+ result.put("intentWithdrawRequestedRate", json(mapper, meter));
+ meter = service.intentWithdrawnRateMeter();
+ result.put("intentWithdrawnRate", json(mapper, meter));
+ //
+ print("%s", result);
+ } else {
+ gauge = service.intentSubmittedTimestampEpochMsGauge();
+ printGauge("Submitted", gauge);
+ gauge = service.intentInstalledTimestampEpochMsGauge();
+ printGauge("Installed", gauge);
+ gauge = service.intentWithdrawRequestedTimestampEpochMsGauge();
+ printGauge("Withdraw Requested", gauge);
+ gauge = service.intentWithdrawnTimestampEpochMsGauge();
+ printGauge("Withdrawn", gauge);
+ //
+ meter = service.intentSubmittedRateMeter();
+ printMeter("Submitted", meter);
+ meter = service.intentInstalledRateMeter();
+ printMeter("Installed", meter);
+ meter = service.intentWithdrawRequestedRateMeter();
+ printMeter("Withdraw Requested", meter);
+ meter = service.intentWithdrawnRateMeter();
+ printMeter("Withdrawn", meter);
+ }
+ }
+
+ /**
+ * Produces JSON node for an Object.
+ *
+ * @param mapper the JSON object mapper to use
+ * @param object the Object with the data
+ * @return JSON node for the Object
+ */
+ private JsonNode json(ObjectMapper mapper, Object object) {
+ //
+ // NOTE: The API for custom serializers is incomplete,
+ // hence we have to parse the JSON string to create JsonNode.
+ //
+ try {
+ final String objectJson = mapper.writeValueAsString(object);
+ JsonNode objectNode = mapper.readTree(objectJson);
+ return objectNode;
+ } catch (JsonProcessingException e) {
+ log.error("Error writing value as JSON string", e);
+ } catch (IOException e) {
+ log.error("Error writing value as JSON string", e);
+ }
+ return null;
+ }
+
+ /**
+ * Prints a Gauge.
+ *
+ * @param operationStr the string with the intent operation to print
+ * @param gauge the Gauge to print
+ */
+ private void printGauge(String operationStr, Gauge<Long> gauge) {
+ print(FORMAT_GAUGE, operationStr, gauge.getValue());
+ }
+
+ /**
+ * Prints a Meter.
+ *
+ * @param operationStr the string with the intent operation to print
+ * @param meter the Meter to print
+ */
+ private void printMeter(String operationStr, Meter meter) {
+ TimeUnit rateUnit = TimeUnit.SECONDS;
+ double rateFactor = rateUnit.toSeconds(1);
+ print(FORMAT_METER, operationStr, meter.getCount(),
+ meter.getMeanRate() * rateFactor,
+ meter.getOneMinuteRate() * rateFactor,
+ meter.getFiveMinuteRate() * rateFactor,
+ meter.getFifteenMinuteRate() * rateFactor);
+ }
+}
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/package-info.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/package-info.java
new file mode 100644
index 0000000..5bba126
--- /dev/null
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * ONOS Intent Metrics Application that collects intent-related metrics.
+ */
+package org.onlab.onos.metrics.intent;
diff --git a/apps/metrics/intent/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/metrics/intent/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..04f179a
--- /dev/null
+++ b/apps/metrics/intent/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,12 @@
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+ <command>
+ <action class="org.onlab.onos.metrics.intent.cli.IntentEventsListCommand"/>
+ </command>
+ <command>
+ <action class="org.onlab.onos.metrics.intent.cli.IntentEventsMetricsCommand"/>
+ </command>
+ </command-bundle>
+
+</blueprint>
diff --git a/apps/metrics/pom.xml b/apps/metrics/pom.xml
index 0ce3913..67085c2 100644
--- a/apps/metrics/pom.xml
+++ b/apps/metrics/pom.xml
@@ -17,6 +17,7 @@
<description>ONOS metrics applications</description>
<modules>
+ <module>intent</module>
<module>topology</module>
</modules>
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
index e2a4532..32cf0cf 100644
--- a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
@@ -18,6 +18,15 @@
import org.onlab.metrics.MetricsFeature;
import org.onlab.metrics.MetricsService;
import org.onlab.onos.event.Event;
+import org.onlab.onos.net.device.DeviceEvent;
+import org.onlab.onos.net.device.DeviceListener;
+import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.host.HostEvent;
+import org.onlab.onos.net.host.HostListener;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.link.LinkEvent;
+import org.onlab.onos.net.link.LinkListener;
+import org.onlab.onos.net.link.LinkService;
import org.onlab.onos.net.topology.TopologyEvent;
import org.onlab.onos.net.topology.TopologyListener;
import org.onlab.onos.net.topology.TopologyService;
@@ -28,14 +37,26 @@
*/
@Component(immediate = true)
@Service
-public class TopologyMetrics implements TopologyMetricsService,
- TopologyListener {
+public class TopologyMetrics implements TopologyMetricsService {
private static final Logger log = getLogger(TopologyMetrics.class);
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected LinkService linkService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected TopologyService topologyService;
- private LinkedList<TopologyEvent> lastEvents = new LinkedList<>();
- private static final int LAST_EVENTS_MAX_N = 10;
+
+ private LinkedList<Event> lastEvents = new LinkedList<>();
+ private static final int LAST_EVENTS_MAX_N = 100;
+
+ private final DeviceListener deviceListener = new InnerDeviceListener();
+ private final HostListener hostListener = new InnerHostListener();
+ private final LinkListener linkListener = new InnerLinkListener();
+ private final TopologyListener topologyListener =
+ new InnerTopologyListener();
//
// Metrics
@@ -61,22 +82,33 @@
protected void activate() {
clear();
registerMetrics();
- topologyService.addListener(this);
+
+ // Register for all topology-related events
+ deviceService.addListener(deviceListener);
+ hostService.addListener(hostListener);
+ linkService.addListener(linkListener);
+ topologyService.addListener(topologyListener);
+
log.info("ONOS Topology Metrics started.");
}
@Deactivate
public void deactivate() {
- topologyService.removeListener(this);
+ // De-register from all topology-related events
+ deviceService.removeListener(deviceListener);
+ hostService.removeListener(hostListener);
+ linkService.removeListener(linkListener);
+ topologyService.removeListener(topologyListener);
+
removeMetrics();
clear();
log.info("ONOS Topology Metrics stopped.");
}
@Override
- public List<TopologyEvent> getEvents() {
+ public List<Event> getEvents() {
synchronized (lastEvents) {
- return ImmutableList.<TopologyEvent>copyOf(lastEvents);
+ return ImmutableList.<Event>copyOf(lastEvents);
}
}
@@ -90,27 +122,22 @@
return eventRateMeter;
}
- @Override
- public void event(TopologyEvent event) {
- lastEventTimestampEpochMs = System.currentTimeMillis();
- //
- // NOTE: If we want to count each "reason" as a separate event,
- // then we should use 'event.reason().size()' instead of '1' to
- // mark the meter below.
- //
- eventRateMeter.mark(1);
-
- log.debug("Topology Event: time = {} type = {} subject = {}",
- event.time(), event.type(), event.subject());
- for (Event reason : event.reasons()) {
- log.debug("Topology Event Reason: time = {} type = {} subject = {}",
- reason.time(), reason.type(), reason.subject());
- }
-
- //
- // Keep only the last N events, where N = LAST_EVENTS_MAX_N
- //
+ /**
+ * Records an event.
+ *
+ * @param event the event to record
+ * @param updateEventRateMeter if true, update the Event Rate Meter
+ */
+ private void recordEvent(Event event, boolean updateEventRateMeter) {
synchronized (lastEvents) {
+ lastEventTimestampEpochMs = System.currentTimeMillis();
+ if (updateEventRateMeter) {
+ eventRateMeter.mark(1);
+ }
+
+ //
+ // Keep only the last N events, where N = LAST_EVENTS_MAX_N
+ //
while (lastEvents.size() >= LAST_EVENTS_MAX_N) {
lastEvents.remove();
}
@@ -119,11 +146,67 @@
}
/**
+ * Inner Device Event Listener class.
+ */
+ private class InnerDeviceListener implements DeviceListener {
+ @Override
+ public void event(DeviceEvent event) {
+ recordEvent(event, true);
+ log.debug("Device Event: time = {} type = {} event = {}",
+ event.time(), event.type(), event);
+ }
+ }
+
+ /**
+ * Inner Host Event Listener class.
+ */
+ private class InnerHostListener implements HostListener {
+ @Override
+ public void event(HostEvent event) {
+ recordEvent(event, true);
+ log.debug("Host Event: time = {} type = {} event = {}",
+ event.time(), event.type(), event);
+ }
+ }
+
+ /**
+ * Inner Link Event Listener class.
+ */
+ private class InnerLinkListener implements LinkListener {
+ @Override
+ public void event(LinkEvent event) {
+ recordEvent(event, true);
+ log.debug("Link Event: time = {} type = {} event = {}",
+ event.time(), event.type(), event);
+ }
+ }
+
+ /**
+ * Inner Topology Event Listener class.
+ */
+ private class InnerTopologyListener implements TopologyListener {
+ @Override
+ public void event(TopologyEvent event) {
+ //
+ // NOTE: Don't update the eventRateMeter, because the real
+ // events are already captured/counted.
+ //
+ recordEvent(event, false);
+ log.debug("Topology Event: time = {} type = {} event = {}",
+ event.time(), event.type(), event);
+ for (Event reason : event.reasons()) {
+ log.debug("Topology Event Reason: time = {} type = {} event = {}",
+ reason.time(), reason.type(), reason);
+ }
+ }
+ }
+
+ /**
* Clears the internal state.
*/
private void clear() {
- lastEventTimestampEpochMs = 0;
synchronized (lastEvents) {
+ lastEventTimestampEpochMs = 0;
lastEvents.clear();
}
}
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
index cc370fa..aeb2e32 100644
--- a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
@@ -4,7 +4,7 @@
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
-import org.onlab.onos.net.topology.TopologyEvent;
+import org.onlab.onos.event.Event;
/**
* Service interface exported by TopologyMetrics.
@@ -15,7 +15,7 @@
*
* @return the last saved topology events.
*/
- public List<TopologyEvent> getEvents();
+ public List<Event> getEvents();
/**
* Gets the Metrics' Gauge for the last topology event timestamp
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java
index 8bab4d0..f8d0c1a 100644
--- a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java
@@ -19,10 +19,8 @@
description = "Lists the last topology events")
public class TopologyEventsListCommand extends AbstractShellCommand {
- private static final String FORMAT_EVENT =
- "Topology Event time=%d type=%s subject=%s";
- private static final String FORMAT_REASON =
- " Reason time=%d type=%s subject=%s";
+ private static final String FORMAT_EVENT = "Event=%s";
+ private static final String FORMAT_REASON = " Reason=%s";
@Override
protected void execute() {
@@ -31,12 +29,13 @@
if (outputJson()) {
print("%s", json(service.getEvents()));
} else {
- for (TopologyEvent event : service.getEvents()) {
- print(FORMAT_EVENT, event.time(), event.type(),
- event.subject());
- for (Event reason : event.reasons()) {
- print(FORMAT_REASON, reason.time(), reason.type(),
- reason.subject());
+ for (Event event : service.getEvents()) {
+ print(FORMAT_EVENT, event);
+ if (event instanceof TopologyEvent) {
+ TopologyEvent topologyEvent = (TopologyEvent) event;
+ for (Event reason : topologyEvent.reasons()) {
+ print(FORMAT_REASON, reason);
+ }
}
print(""); // Extra empty line for clarity
}
@@ -46,14 +45,14 @@
/**
* Produces a JSON array of topology events.
*
- * @param topologyEvents the topology events with the data
+ * @param events the topology events with the data
* @return JSON array with the topology events
*/
- private JsonNode json(List<TopologyEvent> topologyEvents) {
+ private JsonNode json(List<Event> events) {
ObjectMapper mapper = new ObjectMapper();
ArrayNode result = mapper.createArrayNode();
- for (TopologyEvent event : topologyEvents) {
+ for (Event event : events) {
result.add(json(mapper, event));
}
return result;
@@ -66,32 +65,23 @@
* @param topologyEvent the topology event with the data
* @return JSON object for the topology event
*/
- private ObjectNode json(ObjectMapper mapper, TopologyEvent topologyEvent) {
- ObjectNode result = mapper.createObjectNode();
- ArrayNode reasons = mapper.createArrayNode();
-
- for (Event reason : topologyEvent.reasons()) {
- reasons.add(json(mapper, reason));
- }
- result.put("time", topologyEvent.time())
- .put("type", topologyEvent.type().toString())
- .put("subject", topologyEvent.subject().toString())
- .put("reasons", reasons);
- return result;
- }
-
- /**
- * Produces JSON object for a generic event.
- *
- * @param event the generic event with the data
- * @return JSON object for the generic event
- */
private ObjectNode json(ObjectMapper mapper, Event event) {
ObjectNode result = mapper.createObjectNode();
result.put("time", event.time())
.put("type", event.type().toString())
- .put("subject", event.subject().toString());
+ .put("event", event.toString());
+
+ // Add the reasons if a TopologyEvent
+ if (event instanceof TopologyEvent) {
+ TopologyEvent topologyEvent = (TopologyEvent) event;
+ ArrayNode reasons = mapper.createArrayNode();
+ for (Event reason : topologyEvent.reasons()) {
+ reasons.add(json(mapper, reason));
+ }
+ result.put("reasons", reasons);
+ }
+
return result;
}
}
diff --git a/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java b/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java
index 86c1c0b..cfdeb1f 100644
--- a/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java
+++ b/apps/optical/src/main/java/org/onlab/onos/optical/cfg/OpticalConfigProvider.java
@@ -83,7 +83,7 @@
protected OpticalNetworkConfig opticalNetworkConfig;
public OpticalConfigProvider() {
- super(new ProviderId("of", "org.onlab.onos.provider.opticalConfig", true));
+ super(new ProviderId("optical", "org.onlab.onos.provider.opticalConfig", true));
}
@Activate
@@ -238,7 +238,7 @@
while (iterWdmNode.hasNext()) {
Roadm value = iterWdmNode.next();
DeviceId did = deviceId("of:" + value.getNodeId().replace(":", ""));
- ChassisId cid = new ChassisId(value.getNodeId());
+ ChassisId cid = new ChassisId();
DefaultAnnotations extendedAttributes = DefaultAnnotations.builder()
.set(OPTICAL_ANNOTATION + "switchType", "ROADM")
.set(OPTICAL_ANNOTATION + "switchName", value.getName())
@@ -284,7 +284,7 @@
DefaultLinkDescription linkDescription =
new DefaultLinkDescription(srcPoint,
snkPoint,
- Link.Type.DIRECT,
+ Link.Type.OPTICAL,
extendedAttributes);
linkProviderService.linkDetected(linkDescription);
@@ -315,7 +315,7 @@
DefaultLinkDescription linkDescription =
new DefaultLinkDescription(srcPoint,
snkPoint,
- Link.Type.DIRECT,
+ Link.Type.OPTICAL,
extendedAttributes);
linkProviderService.linkDetected(linkDescription);
diff --git a/apps/optical/src/main/resources/demo-3-roadm-2-ps.json b/apps/optical/src/main/resources/demo-3-roadm-2-ps.json
index 6f2c2f5..20b7db2 100644
--- a/apps/optical/src/main/resources/demo-3-roadm-2-ps.json
+++ b/apps/optical/src/main/resources/demo-3-roadm-2-ps.json
@@ -1,5 +1,5 @@
{
- "opticalSwitches": [
+ "opticalSwitches": [
{
"allowed": true,
"latitude": 37.6,
@@ -12,7 +12,7 @@
"type": "Roadm"
},
- {
+ {
"allowed": true,
"latitude": 37.3,
"longitude": 121.9,
@@ -22,9 +22,9 @@
"numRegen": 0
},
"type": "Roadm"
- },
+ },
- {
+ {
"allowed": true,
"latitude": 33.9,
"longitude": 118.4,
@@ -34,10 +34,10 @@
"numRegen": 2
},
"type": "Roadm"
- }
+ }
],
- "opticalLinks": [
+ "opticalLinks": [
{
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:01",
@@ -51,10 +51,38 @@
"port2": 30
},
"type": "wdmLink"
- },
-
- {
- "allowed": true,
+ },
+ {
+ "allowed": true,
+ "nodeDpid1": "00:00:ff:ff:ff:ff:ff:03",
+ "nodeDpid2": "00:00:ff:ff:ff:ff:ff:01",
+ "params": {
+ "distKms": 1000,
+ "nodeName1": "ROADM3",
+ "nodeName2": "ROADM1",
+ "numWaves": 80,
+ "port1": 30,
+ "port2": 10
+ },
+ "type": "wdmLink"
+ },
+
+ {
+ "allowed": true,
+ "nodeDpid1": "00:00:ff:ff:ff:ff:ff:02",
+ "nodeDpid2": "00:00:ff:ff:ff:ff:ff:03",
+ "params": {
+ "distKms": 2000,
+ "nodeName1": "ROADM2",
+ "nodeName2": "ROADM3",
+ "numWaves": 80,
+ "port1": 20,
+ "port2": 31
+ },
+ "type": "wdmLink"
+ },
+ {
+ "allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:ff:03",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:02",
"params": {
@@ -66,10 +94,9 @@
"port2": 20
},
"type": "wdmLink"
- },
+ },
-
- {
+ {
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:00:01",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:01",
@@ -82,8 +109,21 @@
},
"type": "pktOptLink"
},
+ {
+ "allowed": true,
+ "nodeDpid1": "00:00:ff:ff:ff:ff:ff:01",
+ "nodeDpid2": "00:00:ff:ff:ff:ff:00:01",
+ "params": {
+ "nodeName1": "ROADM1",
+ "nodeName2": "ROUTER1",
+ "bandWidth": 100000,
+ "port1": 11,
+ "port2": 10
+ },
+ "type": "pktOptLink"
+ },
- {
+ {
"allowed": true,
"nodeDpid1": "00:00:ff:ff:ff:ff:00:02",
"nodeDpid2": "00:00:ff:ff:ff:ff:ff:02",
@@ -95,7 +135,20 @@
"port2": 21
},
"type": "pktOptLink"
- }
+ },
+ {
+ "allowed": true,
+ "nodeDpid1": "00:00:ff:ff:ff:ff:ff:02",
+ "nodeDpid2": "00:00:ff:ff:ff:ff:00:02",
+ "params": {
+ "nodeName1": "ROADM2",
+ "nodeName2": "ROUTER2",
+ "bandWidth": 100000,
+ "port1": 21,
+ "port2": 10
+ },
+ "type": "pktOptLink"
+ }
]
}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java
index 7a6e6bb..c609b95 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/HostToInterfaceAdaptor.java
@@ -4,19 +4,17 @@
import java.util.Set;
-import org.apache.commons.lang.NotImplementedException;
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.host.PortAddresses;
import org.onlab.onos.sdnip.config.Interface;
import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
import com.google.common.collect.Sets;
-
-
/**
- * Provides IntefaceService using PortAddresses data from the HostService.
+ * Provides InterfaceService using PortAddresses data from the HostService.
*/
public class HostToInterfaceAdaptor implements InterfaceService {
@@ -52,8 +50,17 @@
@Override
public Interface getMatchingInterface(IpAddress ipAddress) {
- // TODO implement
- throw new NotImplementedException("getMatchingInteface is not yet implemented");
+ checkNotNull(ipAddress);
+
+ for (PortAddresses portAddresses : hostService.getAddressBindings()) {
+ for (IpPrefix p : portAddresses.ips()) {
+ if (p.contains(ipAddress)) {
+ return new Interface(portAddresses);
+ }
+ }
+ }
+
+ return null;
}
}
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
index ca25a76..ed5b8df 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/Router.java
@@ -1,14 +1,20 @@
package org.onlab.onos.sdnip;
-import com.google.common.base.Objects;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimaps;
-import com.google.common.collect.SetMultimap;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import com.googlecode.concurrenttrees.common.KeyValuePair;
-import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
-import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
-import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Semaphore;
+
import org.apache.commons.lang3.tuple.Pair;
import org.onlab.onos.ApplicationId;
import org.onlab.onos.net.ConnectPoint;
@@ -36,20 +42,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.Semaphore;
+import com.google.common.base.Objects;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.collect.SetMultimap;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.googlecode.concurrenttrees.common.KeyValuePair;
+import com.googlecode.concurrenttrees.radix.node.concrete.DefaultByteArrayNodeFactory;
+import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree;
+import com.googlecode.concurrenttrees.radixinverted.InvertedRadixTree;
/**
* This class processes BGP route update, translates each update into a intent
@@ -744,6 +745,21 @@
}
/**
+ * Gets the pushed route intents.
+ *
+ * @return the pushed route intents
+ */
+ public Collection<MultiPointToSinglePointIntent> getPushedRouteIntents() {
+ List<MultiPointToSinglePointIntent> pushedIntents = new LinkedList<>();
+
+ for (Map.Entry<IpPrefix, MultiPointToSinglePointIntent> entry :
+ pushedRouteIntents.entrySet()) {
+ pushedIntents.add(entry.getValue());
+ }
+ return pushedIntents;
+ }
+
+ /**
* Listener for host events.
*/
class InternalHostListener implements HostListener {
diff --git a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
index 54aa9e5..d66ec9c 100644
--- a/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
+++ b/apps/sdnip/src/main/java/org/onlab/onos/sdnip/SdnIp.java
@@ -64,6 +64,7 @@
bgpSessionManager.startUp(2000); // TODO
// TODO need to disable link discovery on external ports
+
}
@Deactivate
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java
new file mode 100644
index 0000000..5a3dc51
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/HostToInterfaceAdaptorTest.java
@@ -0,0 +1,177 @@
+package org.onlab.onos.sdnip;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.host.PortAddresses;
+import org.onlab.onos.sdnip.config.Interface;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+/**
+ * Unit tests for the HostToInterfaceAdaptor class.
+ */
+public class HostToInterfaceAdaptorTest {
+
+ private HostService hostService;
+ private HostToInterfaceAdaptor adaptor;
+
+ private Set<PortAddresses> portAddresses;
+ private Map<ConnectPoint, Interface> interfaces;
+
+ private static final ConnectPoint CP1 = new ConnectPoint(
+ DeviceId.deviceId("of:1"), PortNumber.portNumber(1));
+ private static final ConnectPoint CP2 = new ConnectPoint(
+ DeviceId.deviceId("of:1"), PortNumber.portNumber(2));
+ private static final ConnectPoint CP3 = new ConnectPoint(
+ DeviceId.deviceId("of:2"), PortNumber.portNumber(1));
+
+ private static final ConnectPoint NON_EXISTENT_CP = new ConnectPoint(
+ DeviceId.deviceId("doesnotexist"), PortNumber.portNumber(1));
+
+ private static final PortAddresses DEFAULT_PA = new PortAddresses(
+ NON_EXISTENT_CP, null, null);
+
+
+ @Before
+ public void setUp() throws Exception {
+ hostService = createMock(HostService.class);
+
+ portAddresses = Sets.newHashSet();
+ interfaces = Maps.newHashMap();
+
+ createPortAddressesAndInterface(CP1,
+ Sets.newHashSet(IpPrefix.valueOf("192.168.1.1/24")),
+ MacAddress.valueOf("00:00:00:00:00:01"));
+
+ // Two addresses in the same subnet
+ createPortAddressesAndInterface(CP2,
+ Sets.newHashSet(IpPrefix.valueOf("192.168.2.1/24"),
+ IpPrefix.valueOf("192.168.2.2/24")),
+ MacAddress.valueOf("00:00:00:00:00:02"));
+
+ // Two addresses in different subnets
+ createPortAddressesAndInterface(CP3,
+ Sets.newHashSet(IpPrefix.valueOf("192.168.3.1/24"),
+ IpPrefix.valueOf("192.168.4.1/24")),
+ MacAddress.valueOf("00:00:00:00:00:03"));
+
+ expect(hostService.getAddressBindings()).andReturn(portAddresses).anyTimes();
+
+ replay(hostService);
+
+ adaptor = new HostToInterfaceAdaptor(hostService);
+ }
+
+ /**
+ * Creates both a PortAddresses and an Interface for the given inputs and
+ * places them in the correct global data stores.
+ *
+ * @param cp the connect point
+ * @param ips the set of IP addresses
+ * @param mac the MAC address
+ */
+ private void createPortAddressesAndInterface(
+ ConnectPoint cp, Set<IpPrefix> ips, MacAddress mac) {
+ PortAddresses pa = new PortAddresses(cp, ips, mac);
+ portAddresses.add(pa);
+ expect(hostService.getAddressBindingsForPort(cp)).andReturn(pa).anyTimes();
+
+ Interface intf = new Interface(cp, ips, mac);
+ interfaces.put(cp, intf);
+ }
+
+ /**
+ * Tests {@link HostToInterfaceAdaptor#getInterfaces()}.
+ * Verifies that the set of interfaces returned matches what is expected
+ * based on the input PortAddresses data.
+ */
+ @Test
+ public void testGetInterfaces() {
+ Set<Interface> adaptorIntfs = adaptor.getInterfaces();
+
+ assertEquals(3, adaptorIntfs.size());
+ assertTrue(adaptorIntfs.contains(this.interfaces.get(CP1)));
+ assertTrue(adaptorIntfs.contains(this.interfaces.get(CP2)));
+ assertTrue(adaptorIntfs.contains(this.interfaces.get(CP3)));
+ }
+
+ /**
+ * Tests {@link HostToInterfaceAdaptor#getInterface(ConnectPoint)}.
+ * Verifies that the correct interface is returned for a given connect
+ * point.
+ */
+ @Test
+ public void testGetInterface() {
+ assertEquals(this.interfaces.get(CP1), adaptor.getInterface(CP1));
+ assertEquals(this.interfaces.get(CP2), adaptor.getInterface(CP2));
+ assertEquals(this.interfaces.get(CP3), adaptor.getInterface(CP3));
+
+ // Try and get an interface for a connect point with no addresses
+ reset(hostService);
+ expect(hostService.getAddressBindingsForPort(NON_EXISTENT_CP))
+ .andReturn(DEFAULT_PA).anyTimes();
+ replay(hostService);
+
+ assertNull(adaptor.getInterface(NON_EXISTENT_CP));
+ }
+
+ /**
+ * Tests {@link HostToInterfaceAdaptor#getInterface(ConnectPoint)} in the
+ * case that the input connect point is null.
+ * Verifies that a NullPointerException is thrown.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testGetInterfaceNull() {
+ adaptor.getInterface(null);
+ }
+
+ /**
+ * Tests {@link HostToInterfaceAdaptor#getMatchingInterface(IpAddress)}.
+ * Verifies that the correct interface is returned based on the given IP
+ * address.
+ */
+ @Test
+ public void testGetMatchingInterface() {
+ assertEquals(this.interfaces.get(CP1),
+ adaptor.getMatchingInterface(IpAddress.valueOf("192.168.1.100")));
+ assertEquals(this.interfaces.get(CP2),
+ adaptor.getMatchingInterface(IpAddress.valueOf("192.168.2.100")));
+ assertEquals(this.interfaces.get(CP3),
+ adaptor.getMatchingInterface(IpAddress.valueOf("192.168.3.100")));
+ assertEquals(this.interfaces.get(CP3),
+ adaptor.getMatchingInterface(IpAddress.valueOf("192.168.4.100")));
+
+ // Try and match an address we don't have subnet configured for
+ assertNull(adaptor.getMatchingInterface(IpAddress.valueOf("1.1.1.1")));
+ }
+
+ /**
+ * Tests {@link HostToInterfaceAdaptor#getMatchingInterface(IpAddress)} in the
+ * case that the input IP address is null.
+ * Verifies that a NullPointerException is thrown.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testGetMatchingInterfaceNull() {
+ adaptor.getMatchingInterface(null);
+ }
+
+}
diff --git a/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTest.java b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTest.java
new file mode 100644
index 0000000..9ab5916
--- /dev/null
+++ b/apps/sdnip/src/test/java/org/onlab/onos/sdnip/RouterTest.java
@@ -0,0 +1,465 @@
+package org.onlab.onos.sdnip;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.onos.ApplicationId;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultHost;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Host;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.HostLocation;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.DefaultTrafficSelector;
+import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.TrafficSelector;
+import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.onos.sdnip.config.BgpPeer;
+import org.onlab.onos.sdnip.config.Interface;
+import org.onlab.onos.sdnip.config.SdnIpConfigService;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.TestUtils;
+import org.onlab.util.TestUtils.TestUtilsException;
+
+import com.google.common.collect.Sets;
+
+/**
+ * This class tests adding a route, updating a route, deleting a route,
+ * and adding a route whose next hop is the local BGP speaker.
+ */
+public class RouterTest {
+
+ private SdnIpConfigService sdnIpConfigService;
+ private InterfaceService interfaceService;
+ private IntentService intentService;
+ private HostService hostService;
+
+ private Map<IpAddress, BgpPeer> bgpPeers;
+ private Map<IpAddress, BgpPeer> configuredPeers;
+ private Set<Interface> interfaces;
+ private Set<Interface> configuredInterfaces;
+
+ private static final ApplicationId APPID = new ApplicationId() {
+ @Override
+ public short id() {
+ return 1;
+ }
+
+ @Override
+ public String name() {
+ return "SDNIP";
+ }
+ };
+
+ private Router router;
+
+ @Before
+ public void setUp() throws Exception {
+ bgpPeers = setUpBgpPeers();
+ interfaces = setUpInterfaces();
+ initRouter();
+ }
+
+ /**
+ * Initializes Router class.
+ */
+ private void initRouter() {
+
+ intentService = createMock(IntentService.class);
+ hostService = createMock(HostService.class);
+
+ interfaceService = createMock(InterfaceService.class);
+ expect(interfaceService.getInterfaces()).andReturn(
+ interfaces).anyTimes();
+
+ Set<IpPrefix> ipAddressesOnSw1Eth1 = new HashSet<IpPrefix>();
+ ipAddressesOnSw1Eth1.add(IpPrefix.valueOf("192.168.10.0/24"));
+ Interface expectedInterface =
+ new Interface(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber("1")),
+ ipAddressesOnSw1Eth1,
+ MacAddress.valueOf("00:00:00:00:00:01"));
+ ConnectPoint egressPoint = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber(1));
+ expect(interfaceService.getInterface(egressPoint)).andReturn(
+ expectedInterface).anyTimes();
+
+ Set<IpPrefix> ipAddressesOnSw2Eth1 = new HashSet<IpPrefix>();
+ ipAddressesOnSw2Eth1.add(IpPrefix.valueOf("192.168.20.0/24"));
+ Interface expectedInterfaceNew =
+ new Interface(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber("1")),
+ ipAddressesOnSw2Eth1,
+ MacAddress.valueOf("00:00:00:00:00:02"));
+ ConnectPoint egressPointNew = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber(1));
+ expect(interfaceService.getInterface(egressPointNew)).andReturn(
+ expectedInterfaceNew).anyTimes();
+ replay(interfaceService);
+
+ sdnIpConfigService = createMock(SdnIpConfigService.class);
+ expect(sdnIpConfigService.getBgpPeers()).andReturn(bgpPeers).anyTimes();
+ replay(sdnIpConfigService);
+
+ router = new Router(APPID, intentService,
+ hostService, sdnIpConfigService, interfaceService);
+ }
+
+ /**
+ * Sets up BGP peers in external networks.
+ *
+ * @return configured BGP peers as a Map from peer IP address to BgpPeer
+ */
+ private Map<IpAddress, BgpPeer> setUpBgpPeers() {
+
+ configuredPeers = new HashMap<>();
+
+ String peerSw1Eth1 = "192.168.10.1";
+ configuredPeers.put(IpAddress.valueOf(peerSw1Eth1),
+ new BgpPeer("00:00:00:00:00:00:00:01", 1, peerSw1Eth1));
+
+ // Two BGP peers are connected to switch 2 port 1.
+ String peer1Sw2Eth1 = "192.168.20.1";
+ configuredPeers.put(IpAddress.valueOf(peer1Sw2Eth1),
+ new BgpPeer("00:00:00:00:00:00:00:02", 1, peer1Sw2Eth1));
+
+ String peer2Sw2Eth1 = "192.168.20.2";
+ configuredPeers.put(IpAddress.valueOf(peer2Sw2Eth1),
+ new BgpPeer("00:00:00:00:00:00:00:02", 1, peer2Sw2Eth1));
+
+ return configuredPeers;
+ }
+
+ /**
+ * Sets up logical interfaces, which emulate the configured interfaces
+ * in SDN-IP application.
+ *
+ * @return configured interfaces as a Set
+ */
+ private Set<Interface> setUpInterfaces() {
+
+ configuredInterfaces = Sets.newHashSet();
+
+ Set<IpPrefix> ipAddressesOnSw1Eth1 = new HashSet<IpPrefix>();
+ ipAddressesOnSw1Eth1.add(IpPrefix.valueOf("192.168.10.0/24"));
+ configuredInterfaces.add(
+ new Interface(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber(1)),
+ ipAddressesOnSw1Eth1,
+ MacAddress.valueOf("00:00:00:00:00:01")));
+
+ Set<IpPrefix> ipAddressesOnSw2Eth1 = new HashSet<IpPrefix>();
+ ipAddressesOnSw2Eth1.add(IpPrefix.valueOf("192.168.20.0/24"));
+ configuredInterfaces.add(
+ new Interface(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber(1)),
+ ipAddressesOnSw2Eth1,
+ MacAddress.valueOf("00:00:00:00:00:02")));
+
+ Set<IpPrefix> ipAddressesOnSw3Eth1 = new HashSet<IpPrefix>();
+ ipAddressesOnSw3Eth1.add(IpPrefix.valueOf("192.168.30.0/24"));
+ configuredInterfaces.add(
+ new Interface(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000003"),
+ PortNumber.portNumber(1)),
+ ipAddressesOnSw3Eth1,
+ MacAddress.valueOf("00:00:00:00:00:03")));
+
+ return configuredInterfaces;
+ }
+
+ /**
+ * This method tests adding a route entry.
+ */
+ @Test
+ public void testProcessRouteAdd() throws TestUtilsException {
+
+ // Construct a route entry
+ RouteEntry routeEntry = new RouteEntry(
+ IpPrefix.valueOf("1.1.1.0/24"),
+ IpAddress.valueOf("192.168.10.1"));
+
+ // Construct a MultiPointToSinglePointIntent intent
+ TrafficSelector.Builder selectorBuilder =
+ DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
+ routeEntry.prefix());
+
+ TrafficTreatment.Builder treatmentBuilder =
+ DefaultTrafficTreatment.builder();
+ treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
+
+ Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>();
+ ingressPoints.add(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber("1")));
+ ingressPoints.add(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000003"),
+ PortNumber.portNumber("1")));
+
+ ConnectPoint egressPoint = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber("1"));
+
+ MultiPointToSinglePointIntent intent =
+ new MultiPointToSinglePointIntent(APPID,
+ selectorBuilder.build(), treatmentBuilder.build(),
+ ingressPoints, egressPoint);
+
+ // Reset host service
+ reset(hostService);
+ Set<Host> hosts = new HashSet<Host>(1);
+ Set<IpPrefix> ipPrefixes = new HashSet<IpPrefix>();
+ ipPrefixes.add(IpPrefix.valueOf("192.168.10.1/32"));
+ hosts.add(new DefaultHost(ProviderId.NONE, HostId.NONE,
+ MacAddress.valueOf("00:00:00:00:00:01"), VlanId.NONE,
+ new HostLocation(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber(1), 1),
+ ipPrefixes));
+ expect(hostService.getHostsByIp(
+ IpPrefix.valueOf("192.168.10.1/32"))).andReturn(hosts);
+ replay(hostService);
+
+ // Set up test expectation
+ reset(intentService);
+ intentService.submit(intent);
+ replay(intentService);
+
+ // Call the processRouteAdd() method in Router class
+ router.leaderChanged(true);
+ TestUtils.setField(router, "isActivatedLeader", true);
+ router.processRouteAdd(routeEntry);
+
+ // Verify
+ assertEquals(router.getRoutes().size(), 1);
+ assertTrue(router.getRoutes().contains(routeEntry));
+ assertEquals(router.getPushedRouteIntents().size(), 1);
+ assertEquals(router.getPushedRouteIntents().iterator().next(),
+ intent);
+ verify(intentService);
+ }
+
+ /**
+ * This method tests updating a route entry.
+ *
+ * @throws TestUtilsException
+ */
+ @Test
+ public void testRouteUpdate() throws TestUtilsException {
+
+ // Firstly add a route
+ testProcessRouteAdd();
+
+ // Construct the existing route entry
+ RouteEntry routeEntry = new RouteEntry(
+ IpPrefix.valueOf("1.1.1.0/24"),
+ IpAddress.valueOf("192.168.10.1"));
+
+ // Construct the existing MultiPointToSinglePointIntent intent
+ TrafficSelector.Builder selectorBuilder =
+ DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
+ routeEntry.prefix());
+
+ TrafficTreatment.Builder treatmentBuilder =
+ DefaultTrafficTreatment.builder();
+ treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
+
+ ConnectPoint egressPoint = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber("1"));
+
+ Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>();
+ ingressPoints.add(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber("1")));
+ ingressPoints.add(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000003"),
+ PortNumber.portNumber("1")));
+
+ MultiPointToSinglePointIntent intent =
+ new MultiPointToSinglePointIntent(APPID,
+ selectorBuilder.build(), treatmentBuilder.build(),
+ ingressPoints, egressPoint);
+
+ // Start to construct a new route entry and new intent
+ RouteEntry routeEntryUpdate = new RouteEntry(
+ IpPrefix.valueOf("1.1.1.0/24"),
+ IpAddress.valueOf("192.168.20.1"));
+
+ // Construct a new MultiPointToSinglePointIntent intent
+ TrafficSelector.Builder selectorBuilderNew =
+ DefaultTrafficSelector.builder();
+ selectorBuilderNew.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
+ routeEntryUpdate.prefix());
+
+ TrafficTreatment.Builder treatmentBuilderNew =
+ DefaultTrafficTreatment.builder();
+ treatmentBuilderNew.setEthDst(MacAddress.valueOf("00:00:00:00:00:02"));
+
+ ConnectPoint egressPointNew = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber("1"));
+
+ Set<ConnectPoint> ingressPointsNew = new HashSet<ConnectPoint>();
+ ingressPointsNew.add(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber("1")));
+ ingressPointsNew.add(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000003"),
+ PortNumber.portNumber("1")));
+
+ MultiPointToSinglePointIntent intentNew =
+ new MultiPointToSinglePointIntent(APPID,
+ selectorBuilderNew.build(),
+ treatmentBuilderNew.build(),
+ ingressPointsNew, egressPointNew);
+
+ // Reset host service
+ reset(hostService);
+ Set<Host> hosts = new HashSet<Host>(1);
+ Set<IpPrefix> ipPrefixes = new HashSet<IpPrefix>();
+ ipPrefixes.add(IpPrefix.valueOf("192.168.20.1/32"));
+ hosts.add(new DefaultHost(ProviderId.NONE, HostId.NONE,
+ MacAddress.valueOf("00:00:00:00:00:02"), VlanId.NONE,
+ new HostLocation(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber(1), 1),
+ ipPrefixes));
+ expect(hostService.getHostsByIp(
+ IpPrefix.valueOf("192.168.20.1/32"))).andReturn(hosts);
+ replay(hostService);
+
+ // Set up test expectation
+ reset(intentService);
+ intentService.withdraw(intent);
+ intentService.submit(intentNew);
+ replay(intentService);
+
+ // Call the processRouteAdd() method in Router class
+ router.leaderChanged(true);
+ TestUtils.setField(router, "isActivatedLeader", true);
+ router.processRouteAdd(routeEntryUpdate);
+
+ // Verify
+ assertEquals(router.getRoutes().size(), 1);
+ assertTrue(router.getRoutes().contains(routeEntryUpdate));
+ assertEquals(router.getPushedRouteIntents().size(), 1);
+ assertEquals(router.getPushedRouteIntents().iterator().next(),
+ intentNew);
+ verify(intentService);
+ }
+
+ /**
+ * This method tests deleting a route entry.
+ */
+ @Test
+ public void testProcessRouteDelete() throws TestUtilsException {
+
+ // Firstly add a route
+ testProcessRouteAdd();
+
+ // Construct the existing route entry
+ RouteEntry routeEntry = new RouteEntry(
+ IpPrefix.valueOf("1.1.1.0/24"),
+ IpAddress.valueOf("192.168.10.1"));
+
+ // Construct the existing MultiPointToSinglePointIntent intent
+ TrafficSelector.Builder selectorBuilder =
+ DefaultTrafficSelector.builder();
+ selectorBuilder.matchEthType(Ethernet.TYPE_IPV4).matchIPDst(
+ routeEntry.prefix());
+
+ TrafficTreatment.Builder treatmentBuilder =
+ DefaultTrafficTreatment.builder();
+ treatmentBuilder.setEthDst(MacAddress.valueOf("00:00:00:00:00:01"));
+
+ ConnectPoint egressPoint = new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000001"),
+ PortNumber.portNumber("1"));
+
+ Set<ConnectPoint> ingressPoints = new HashSet<ConnectPoint>();
+ ingressPoints.add(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000002"),
+ PortNumber.portNumber("1")));
+ ingressPoints.add(new ConnectPoint(
+ DeviceId.deviceId("of:0000000000000003"),
+ PortNumber.portNumber("1")));
+
+ MultiPointToSinglePointIntent intent =
+ new MultiPointToSinglePointIntent(APPID,
+ selectorBuilder.build(), treatmentBuilder.build(),
+ ingressPoints, egressPoint);
+
+ // Set up expectation
+ reset(intentService);
+ intentService.withdraw(intent);
+ replay(intentService);
+
+ // Call route deleting method in Router class
+ router.leaderChanged(true);
+ TestUtils.setField(router, "isActivatedLeader", true);
+ router.processRouteDelete(routeEntry);
+
+ // Verify
+ assertEquals(router.getRoutes().size(), 0);
+ assertEquals(router.getPushedRouteIntents().size(), 0);
+ verify(intentService);
+ }
+
+ /**
+ * This method tests when the next hop of a route is the local BGP speaker.
+ *
+ * @throws TestUtilsException
+ */
+ @Test
+ public void testLocalRouteAdd() throws TestUtilsException {
+
+ // Construct a route entry, the next hop is the local BGP speaker
+ RouteEntry routeEntry = new RouteEntry(
+ IpPrefix.valueOf("1.1.1.0/24"), IpAddress.valueOf("0.0.0.0"));
+
+ // Reset intentService to check whether the submit method is called
+ reset(intentService);
+ replay(intentService);
+
+ // Call the processRouteAdd() method in Router class
+ router.leaderChanged(true);
+ TestUtils.setField(router, "isActivatedLeader", true);
+ router.processRouteAdd(routeEntry);
+
+ // Verify
+ assertEquals(router.getRoutes().size(), 1);
+ assertTrue(router.getRoutes().contains(routeEntry));
+ assertEquals(router.getPushedRouteIntents().size(), 0);
+ verify(intentService);
+ }
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java b/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
index 628754a..68b3244 100644
--- a/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/AbstractShellCommand.java
@@ -18,10 +18,13 @@
*/
package org.onlab.onos.cli;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.karaf.shell.commands.Option;
import org.apache.karaf.shell.console.OsgiCommandSupport;
import org.onlab.onos.ApplicationId;
import org.onlab.onos.CoreService;
+import org.onlab.onos.net.Annotations;
import org.onlab.osgi.DefaultServiceDirectory;
import org.onlab.osgi.ServiceNotFoundException;
@@ -76,6 +79,34 @@
}
/**
+ * Produces a string image of the specified key/value annotations.
+ *
+ * @param annotations key/value annotations
+ * @return string image with ", k1=v1, k2=v2, ..." pairs
+ */
+ public static String annotations(Annotations annotations) {
+ StringBuilder sb = new StringBuilder();
+ for (String key : annotations.keys()) {
+ sb.append(", ").append(key).append('=').append(annotations.value(key));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Produces a JSON object from the specified key/value annotations.
+ *
+ * @param annotations key/value annotations
+ * @return JSON object
+ */
+ public static ObjectNode annotations(ObjectMapper mapper, Annotations annotations) {
+ ObjectNode result = mapper.createObjectNode();
+ for (String key : annotations.keys()) {
+ result.put(key, annotations.value(key));
+ }
+ return result;
+ }
+
+ /**
* Executes this command.
*/
protected abstract void execute();
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
index f2c280e..3be8e5c 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicePortsListCommand.java
@@ -43,7 +43,7 @@
description = "Lists all ports or all ports of a device")
public class DevicePortsListCommand extends DevicesListCommand {
- private static final String FMT = " port=%s, state=%s";
+ private static final String FMT = " port=%s, state=%s%s";
@Option(name = "-e", aliases = "--enabled", description = "Show only enabled ports",
required = false, multiValued = false)
@@ -112,7 +112,8 @@
if (isIncluded(port)) {
ports.add(mapper.createObjectNode()
.put("port", port.number().toString())
- .put("isEnabled", port.isEnabled()));
+ .put("isEnabled", port.isEnabled())
+ .set("annotations", annotations(mapper, port.annotations())));
}
}
return result.put("device", device.id().toString()).set("ports", ports);
@@ -131,7 +132,8 @@
Collections.sort(ports, Comparators.PORT_COMPARATOR);
for (Port port : ports) {
if (isIncluded(port)) {
- print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled");
+ print(FMT, port.number(), port.isEnabled() ? "enabled" : "disabled",
+ annotations(port.annotations()));
}
}
}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
index 095e2c0..543954e 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/DevicesListCommand.java
@@ -41,7 +41,7 @@
public class DevicesListCommand extends AbstractShellCommand {
private static final String FMT =
- "id=%s, available=%s, role=%s, type=%s, mfr=%s, hw=%s, sw=%s, serial=%s";
+ "id=%s, available=%s, role=%s, type=%s, mfr=%s, hw=%s, sw=%s, serial=%s%s";
@Override
protected void execute() {
@@ -89,7 +89,8 @@
.put("mfr", device.manufacturer())
.put("hw", device.hwVersion())
.put("sw", device.swVersion())
- .put("serial", device.serialNumber());
+ .put("serial", device.serialNumber())
+ .set("annotations", annotations(mapper, device.annotations()));
}
return result;
}
@@ -117,7 +118,7 @@
print(FMT, device.id(), service.isAvailable(device.id()),
service.getRole(device.id()), device.type(),
device.manufacturer(), device.hwVersion(), device.swVersion(),
- device.serialNumber());
+ device.serialNumber(), annotations(device.annotations()));
}
}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/GetStatistics.java b/cli/src/main/java/org/onlab/onos/cli/net/GetStatistics.java
new file mode 100644
index 0000000..3745fa9
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/GetStatistics.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.PortNumber;
+
+import org.onlab.onos.net.statistic.Load;
+import org.onlab.onos.net.statistic.StatisticService;
+
+
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+/**
+ * Fetches statistics.
+ */
+@Command(scope = "onos", name = "get-stats",
+ description = "Fetches stats for a connection point")
+public class GetStatistics extends AbstractShellCommand {
+
+ @Argument(index = 0, name = "connectPoint",
+ description = "Device/Port Description",
+ required = true, multiValued = false)
+ String connectPoint = null;
+
+
+ @Override
+ protected void execute() {
+ StatisticService service = get(StatisticService.class);
+
+ DeviceId ingressDeviceId = deviceId(getDeviceId(connectPoint));
+ PortNumber ingressPortNumber = portNumber(getPortNumber(connectPoint));
+ ConnectPoint cp = new ConnectPoint(ingressDeviceId, ingressPortNumber);
+
+ Load load = service.load(cp);
+
+ print("Load on %s -> %s", cp, load);
+ }
+
+ /**
+ * Extracts the port number portion of the ConnectPoint.
+ *
+ * @param deviceString string representing the device/port
+ * @return port number as a string, empty string if the port is not found
+ */
+ private String getPortNumber(String deviceString) {
+ int slash = deviceString.indexOf('/');
+ if (slash <= 0) {
+ return "";
+ }
+ return deviceString.substring(slash + 1, deviceString.length());
+ }
+
+ /**
+ * Extracts the device ID portion of the ConnectPoint.
+ *
+ * @param deviceString string representing the device/port
+ * @return device ID string
+ */
+ private String getDeviceId(String deviceString) {
+ int slash = deviceString.indexOf('/');
+ if (slash <= 0) {
+ return "";
+ }
+ return deviceString.substring(0, slash);
+ }
+}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java
index 2291f4e..0265116 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/HostsListCommand.java
@@ -42,7 +42,7 @@
public class HostsListCommand extends AbstractShellCommand {
private static final String FMT =
- "id=%s, mac=%s, location=%s/%s, vlan=%s, ip(s)=%s";
+ "id=%s, mac=%s, location=%s/%s, vlan=%s, ip(s)=%s%s";
@Override
protected void execute() {
@@ -80,6 +80,7 @@
.put("vlan", host.vlan().toString());
result.set("location", loc);
result.set("ips", ips);
+ result.set("annotations", annotations(mapper, host.annotations()));
return result;
}
@@ -105,7 +106,8 @@
print(FMT, host.id(), host.mac(),
host.location().deviceId(),
host.location().port(),
- host.vlan(), host.ipAddresses());
+ host.vlan(), host.ipAddresses(),
+ annotations(host.annotations()));
}
}
}
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
index e6867f6..7889dcf 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/LinksListCommand.java
@@ -38,7 +38,7 @@
description = "Lists all infrastructure links")
public class LinksListCommand extends AbstractShellCommand {
- private static final String FMT = "src=%s/%s, dst=%s/%s, type=%s";
+ private static final String FMT = "src=%s/%s, dst=%s/%s, type=%s%s";
private static final String COMPACT = "%s/%s-%s/%s";
@Argument(index = 0, name = "uri", description = "Device ID",
@@ -85,6 +85,7 @@
ObjectNode result = mapper.createObjectNode();
result.set("src", json(mapper, link.src()));
result.set("dst", json(mapper, link.dst()));
+ result.set("annotations", annotations(mapper, link.annotations()));
return result;
}
@@ -109,7 +110,8 @@
*/
public static String linkString(Link link) {
return String.format(FMT, link.src().deviceId(), link.src().port(),
- link.dst().deviceId(), link.dst().port(), link.type());
+ link.dst().deviceId(), link.dst().port(), link.type(),
+ annotations(link.annotations()));
}
/**
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/TopologyCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/TopologyCommand.java
index ce3dc59..903b352 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/TopologyCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/TopologyCommand.java
@@ -20,8 +20,10 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
import org.onlab.onos.cli.AbstractShellCommand;
import org.onlab.onos.net.topology.Topology;
+import org.onlab.onos.net.topology.TopologyProvider;
import org.onlab.onos.net.topology.TopologyService;
/**
@@ -35,6 +37,10 @@
private static final String FMT =
"time=%s, devices=%d, links=%d, clusters=%d, paths=%d";
+ @Option(name = "-r", aliases = "--recompute", description = "Trigger topology re-computation",
+ required = false, multiValued = false)
+ private boolean recompute = false;
+
protected TopologyService service;
protected Topology topology;
@@ -49,7 +55,10 @@
@Override
protected void execute() {
init();
- if (outputJson()) {
+ if (recompute) {
+ get(TopologyProvider.class).triggerRecompute();
+
+ } else if (outputJson()) {
print("%s", new ObjectMapper().createObjectNode()
.put("time", topology.time())
.put("deviceCount", topology.deviceCount())
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 9018eb1..b01d412 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -119,6 +119,12 @@
</optional-completers>
</command>
<command>
+ <action class="org.onlab.onos.cli.net.GetStatistics"/>
+ <completers>
+ <ref component-id="connectPointCompleter"/>
+ </completers>
+ </command>
+ <command>
<action class="org.onlab.onos.cli.net.AddMultiPointToSinglePointIntentCommand"/>
<completers>
<ref component-id="connectPointCompleter"/>
diff --git a/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java b/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java
index dcb2d95..fdbb645 100644
--- a/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/mastership/MastershipEvent.java
@@ -1,6 +1,5 @@
package org.onlab.onos.mastership;
-import org.onlab.onos.cluster.NodeId;
import org.onlab.onos.cluster.RoleInfo;
import org.onlab.onos.event.AbstractEvent;
import org.onlab.onos.net.DeviceId;
@@ -56,19 +55,6 @@
}
/**
- * Returns the NodeID of the node associated with the event.
- * For MASTER_CHANGED this is the newly elected master, and for
- * BACKUPS_CHANGED, this is the node that was newly added, removed, or
- * whose position was changed in the list.
- *
- * @return node ID as a subject
- */
- //XXX to-be removed - or keep for convenience?
- public NodeId node() {
- return roleInfo.master();
- }
-
- /**
* Returns the current role state for the subject.
*
* @return RoleInfo associated with Device ID subject
diff --git a/core/api/src/main/java/org/onlab/onos/net/Link.java b/core/api/src/main/java/org/onlab/onos/net/Link.java
index 1ae5b9d..3e23dc1 100644
--- a/core/api/src/main/java/org/onlab/onos/net/Link.java
+++ b/core/api/src/main/java/org/onlab/onos/net/Link.java
@@ -25,7 +25,18 @@
/**
* Signifies that this link is an edge, i.e. host link.
*/
- EDGE
+ EDGE,
+
+ /**
+ * Signifies that this link represents a logical link backed by
+ * some form of a tunnel.
+ */
+ TUNNEL,
+
+ /**
+ * Signifies that this link is realized by optical connection.
+ */
+ OPTICAL
}
/**
@@ -49,6 +60,4 @@
*/
Type type();
- // LinkInfo info(); // Additional link information / decorations
-
}
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DeviceEvent.java b/core/api/src/main/java/org/onlab/onos/net/device/DeviceEvent.java
index 0a33777..07bd426 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DeviceEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DeviceEvent.java
@@ -4,6 +4,8 @@
import org.onlab.onos.net.Device;
import org.onlab.onos.net.Port;
+import static com.google.common.base.MoreObjects.toStringHelper;
+
/**
* Describes infrastructure device event.
*/
@@ -109,4 +111,12 @@
return port;
}
+ @Override
+ public String toString() {
+ if (port == null) {
+ return super.toString();
+ }
+ return toStringHelper(this).add("time", time()).add("type", type())
+ .add("subject", subject()).add("port", port).toString();
+ }
}
diff --git a/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java b/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java
index 9934b8d..ec73ce5 100644
--- a/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java
+++ b/core/api/src/main/java/org/onlab/onos/net/device/DeviceProvider.java
@@ -13,7 +13,7 @@
/**
* Triggers an asynchronous probe of the specified device, intended to
- * determine whether the host is present or not. An indirect result of this
+ * determine whether the device is present or not. An indirect result of this
* should be invocation of
* {@link org.onlab.onos.net.device.DeviceProviderService#deviceConnected} )} or
* {@link org.onlab.onos.net.device.DeviceProviderService#deviceDisconnected}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperation.java
similarity index 74%
rename from core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java
rename to core/api/src/main/java/org/onlab/onos/net/flow/BatchOperation.java
index 72a9847..38f0211 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperation.java
@@ -1,5 +1,22 @@
-package org.onlab.onos.net.intent;
-//TODO is this the right package?
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 static com.google.common.base.Preconditions.checkNotNull;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationEntry.java
similarity index 67%
rename from core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java
rename to core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationEntry.java
index 4e57d33..2ea7053 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationEntry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationEntry.java
@@ -1,5 +1,22 @@
-package org.onlab.onos.net.intent;
-//TODO is this the right package?
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.Objects;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationResult.java b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationResult.java
index 33f1845..d2db96b 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationResult.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationResult.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.Set;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationTarget.java b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationTarget.java
new file mode 100644
index 0000000..e73e3a8
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/BatchOperationTarget.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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;
+
+/**
+ * An interface of the class which is assigned to BatchOperation.
+ */
+public interface BatchOperationTarget {
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
index 4e671e3..363831c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/CompletedBatchOperation.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.Set;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
index cf448cb..59aed23 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowEntry.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 static com.google.common.base.MoreObjects.toStringHelper;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java
index a6593a8..53a94bb 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultFlowRule.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 static com.google.common.base.MoreObjects.toStringHelper;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
index f175bd3..abb29a6 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficSelector.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.HashMap;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
index 5e64c64..b4d8c3e 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/DefaultTrafficTreatment.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.LinkedList;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowEntry.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowEntry.java
index cdccaa9..1e6ce52 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowEntry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowEntry.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowId.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowId.java
index 52eac0a..142e352 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowId.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowId.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 com.google.common.base.Objects;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java
index 3ac277d..718edfe 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRule.java
@@ -1,7 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.onlab.onos.net.DeviceId;
-import org.onlab.onos.net.intent.BatchOperationTarget;
/**
* Represents a generalized match & action pair to be applied to
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEntry.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEntry.java
index d5a1472..ee85ea8 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEntry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchEntry.java
@@ -1,7 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.onlab.onos.net.flow.FlowRuleBatchEntry.FlowRuleOperation;
-import org.onlab.onos.net.intent.BatchOperationEntry;
public class FlowRuleBatchEntry
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchOperation.java
index 74ef165..f078fb9 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchOperation.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleBatchOperation.java
@@ -1,9 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.Collection;
-import org.onlab.onos.net.intent.BatchOperation;
-
public class FlowRuleBatchOperation
extends BatchOperation<FlowRuleBatchEntry> {
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleEvent.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleEvent.java
index 97efa5a..e511be6 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleEvent.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.onlab.onos.event.AbstractEvent;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleListener.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleListener.java
index 114732b..eddaf05 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleListener.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleListener.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.onlab.onos.event.EventListener;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java
index 5a57b88..ae74ac5 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProvider.java
@@ -1,7 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.onlab.onos.ApplicationId;
-import org.onlab.onos.net.intent.BatchOperation;
import org.onlab.onos.net.provider.Provider;
import com.google.common.util.concurrent.ListenableFuture;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderRegistry.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderRegistry.java
index 099d9f4..25b00c4 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderRegistry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderRegistry.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.onlab.onos.net.provider.ProviderRegistry;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderService.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderService.java
index 8164579..bacff0d 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleProviderService.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.onlab.onos.net.DeviceId;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleService.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleService.java
index 6d04810..0b6f6c7 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleService.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.Future;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java
index c53a32d..11bd4ad 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStore.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.Future;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStoreDelegate.java b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStoreDelegate.java
index 66973dd..fbd6b55 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStoreDelegate.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/FlowRuleStoreDelegate.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.onlab.onos.store.StoreDelegate;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/StoredFlowEntry.java b/core/api/src/main/java/org/onlab/onos/net/flow/StoredFlowEntry.java
index e68ed68..9ba53e9 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/StoredFlowEntry.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/StoredFlowEntry.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java b/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java
index 41bceb8..b4d566c 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/TrafficSelector.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.Set;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/TrafficTreatment.java b/core/api/src/main/java/org/onlab/onos/net/flow/TrafficTreatment.java
index fe21328..a576138 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/TrafficTreatment.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/TrafficTreatment.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.List;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/Treatment.java b/core/api/src/main/java/org/onlab/onos/net/flow/Treatment.java
index 8318b66..877da65 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/Treatment.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/Treatment.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.onlab.onos.net.PortNumber;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java
index ebd672c..fb5fb97 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criteria.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.criteria;
import static com.google.common.base.MoreObjects.toStringHelper;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criterion.java b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criterion.java
index 567e100..5337852 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criterion.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/Criterion.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.criteria;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/package-info.java b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/package-info.java
index 82eb210..fabf82e 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/criteria/package-info.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/criteria/package-info.java
@@ -1,3 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
/**
* Traffic selection criteria model.
*/
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instruction.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instruction.java
index f05d238..084ffe4 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instruction.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instruction.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.instructions;
/**
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
index b2ebdee..988c52f 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/Instructions.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.instructions;
import static com.google.common.base.MoreObjects.toStringHelper;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L2ModificationInstruction.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L2ModificationInstruction.java
index 2152532..35acd16 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L2ModificationInstruction.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L2ModificationInstruction.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.instructions;
import static com.google.common.base.MoreObjects.toStringHelper;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L3ModificationInstruction.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L3ModificationInstruction.java
index 7ec8c84..8aff5df 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L3ModificationInstruction.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/L3ModificationInstruction.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.instructions;
import static com.google.common.base.MoreObjects.toStringHelper;
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/package-info.java b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/package-info.java
index 01b68ad..067cd01 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/instructions/package-info.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/instructions/package-info.java
@@ -1,3 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
/**
* Traffic treatment model.
*/
diff --git a/core/api/src/main/java/org/onlab/onos/net/flow/package-info.java b/core/api/src/main/java/org/onlab/onos/net/flow/package-info.java
index 76bee78..8fd6008 100644
--- a/core/api/src/main/java/org/onlab/onos/net/flow/package-info.java
+++ b/core/api/src/main/java/org/onlab/onos/net/flow/package-info.java
@@ -1,3 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
/**
* Flow rule model & related services API definitions.
*/
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationTarget.java b/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationTarget.java
deleted file mode 100644
index c678f31..0000000
--- a/core/api/src/main/java/org/onlab/onos/net/intent/BatchOperationTarget.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.onlab.onos.net.intent;
-//TODO is this the right package?
-
-/**
- * An interface of the class which is assigned to BatchOperation.
- */
-public interface BatchOperationTarget {
-
-}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java
index 8d634a4..710ae4e 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/ConnectivityIntent.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import com.google.common.collect.ImmutableSet;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
index 2d42f58..795f681 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import com.google.common.base.MoreObjects;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java b/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java
index 4c11f57..b5fefb2 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/Intent.java
@@ -1,7 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import org.onlab.onos.ApplicationId;
import org.onlab.onos.net.NetworkResource;
+import org.onlab.onos.net.flow.BatchOperationTarget;
import java.util.Collection;
import java.util.Objects;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchOperation.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchOperation.java
deleted file mode 100644
index b450d71..0000000
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentBatchOperation.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.onlab.onos.net.intent;
-
-/**
- * A list of intent operations.
- */
-public class IntentBatchOperation extends
- BatchOperation<BatchOperationEntry<IntentBatchOperation.Operator, ?>> {
- /**
- * The intent operators.
- */
- public enum Operator {
- ADD,
- REMOVE,
- }
-
- /**
- * Adds an add-intent operation.
- *
- * @param intent the intent to be added
- * @return the IntentBatchOperation object if succeeded, null otherwise
- */
- public IntentBatchOperation addAddIntentOperation(Intent intent) {
- return (null == super.addOperation(
- new BatchOperationEntry<Operator, Intent>(Operator.ADD, intent)))
- ? null : this;
- }
-
- /**
- * Adds a remove-intent operation.
- *
- * @param id the ID of intent to be removed
- * @return the IntentBatchOperation object if succeeded, null otherwise
- */
- public IntentBatchOperation addRemoveIntentOperation(IntentId id) {
- return (null == super.addOperation(
- new BatchOperationEntry<Operator, IntentId>(Operator.REMOVE, id)))
- ? null : this;
- }
-}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java
index dbc3cc4..c54b601 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentCompiler.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import java.util.List;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java
index 742a590..22a24f9 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentEvent.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import org.onlab.onos.event.AbstractEvent;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java
index fff55be..55ca32e 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentException.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
/**
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java
index 50e803c..31db2ec 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentExtensionService.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import java.util.Map;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java
index 08ab7fa..8cfe6fd 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentId.java
@@ -1,5 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
+import org.onlab.onos.net.flow.BatchOperationTarget;
+
/**
* Intent identifier suitable as an external key.
* <p/>
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java
index c403846..22669e5 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentInstaller.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import java.util.List;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentListener.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentListener.java
index c00c1f6..aa1a4a0 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentListener.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentListener.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import org.onlab.onos.event.EventListener;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperation.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperation.java
new file mode 100644
index 0000000..4135ed8
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperation.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
+
+/**
+ * Abstraction of an intent-related operation, e.g. add, remove, replace.
+ */
+public class IntentOperation {
+
+ private final Type type;
+ private final IntentId intentId;
+ private final Intent intent;
+
+ /**
+ * Operation type.
+ */
+ enum Type {
+ /**
+ * Indicates that an intent should be added.
+ */
+ SUBMIT,
+
+ /**
+ * Indicates that an intent should be removed.
+ */
+ WITHDRAW,
+
+ /**
+ * Indicates that an intent should be replaced with another.
+ */
+ REPLACE
+ }
+
+ /**
+ * Creates an intent operation.
+ *
+ * @param type operation type
+ * @param intentId identifier of the intent subject to the operation
+ * @param intent intent subject
+ */
+ IntentOperation(Type type, IntentId intentId, Intent intent) {
+ this.type = type;
+ this.intentId = intentId;
+ this.intent = intent;
+ }
+
+ /**
+ * Returns the type of the operation.
+ *
+ * @return operation type
+ */
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the identifier of the intent to which this operation applies.
+ *
+ * @return intent identifier
+ */
+ public IntentId intentId() {
+ return intentId;
+ }
+
+ /**
+ * Returns the intent to which this operation applied. For remove,
+ * this can be null.
+ *
+ * @return intent that is the subject of the operation; null for remove
+ */
+ public Intent intent() {
+ return intent;
+ }
+
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java
index 470d98b..32f0414 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentOperations.java
@@ -1,10 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.onos.net.intent.IntentOperation.Type.REPLACE;
+import static org.onlab.onos.net.intent.IntentOperation.Type.SUBMIT;
+import static org.onlab.onos.net.intent.IntentOperation.Type.WITHDRAW;
+
/**
- * Abstraction of a batch of intent submit/withdraw operations.
+ * Batch of intent submit/withdraw/replace operations.
*/
-public interface IntentOperations {
+public final class IntentOperations {
- // TODO: elaborate once the revised BatchOperation scheme is in place
+ private final List<IntentOperation> operations;
+ /**
+ * Creates a batch of intent operations using the supplied list.
+ *
+ * @param operations list of intent operations
+ */
+ private IntentOperations(List<IntentOperation> operations) {
+ this.operations = operations;
+ }
+
+ /**
+ * List of operations that need to be executed as a unit.
+ *
+ * @return list of intent operations
+ */
+ public List<IntentOperation> operations() {
+ return operations;
+ }
+
+ /**
+ * Returns a builder for intent operation batches.
+ *
+ * @return intent operations builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for batches of intent operations.
+ */
+ public static final class Builder {
+
+ private final ImmutableList.Builder<IntentOperation> builder = ImmutableList.builder();
+
+ // Public construction is forbidden.
+ private Builder() {
+ }
+
+ /**
+ * Adds an intent submit operation.
+ *
+ * @param intent intent to be submitted
+ * @return self
+ */
+ public Builder addSubmitOperation(Intent intent) {
+ checkNotNull(intent, "Intent cannot be null");
+ builder.add(new IntentOperation(SUBMIT, intent.id(), intent));
+ return this;
+ }
+
+ /**
+ * Adds an intent submit operation.
+ *
+ * @param oldIntentId intent to be replaced
+ * @param newIntent replacement intent
+ * @return self
+ */
+ public Builder addReplaceOperation(IntentId oldIntentId, Intent newIntent) {
+ checkNotNull(oldIntentId, "Intent ID cannot be null");
+ checkNotNull(newIntent, "Intent cannot be null");
+ builder.add(new IntentOperation(REPLACE, oldIntentId, newIntent));
+ return this;
+ }
+
+ /**
+ * Adds an intent submit operation.
+ *
+ * @param intentId identifier of the intent to be withdrawn
+ * @return self
+ */
+ public Builder addWithdrawOperation(IntentId intentId) {
+ checkNotNull(intentId, "Intent ID cannot be null");
+ builder.add(new IntentOperation(WITHDRAW, intentId, null));
+ return this;
+ }
+
+ /**
+ * Builds a batch of intent operations.
+ *
+ * @return immutable batch of intent operations
+ */
+ public IntentOperations build() {
+ return new IntentOperations(builder.build());
+ }
+
+ }
}
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
index 700066d..6b89a68 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentService.java
@@ -1,7 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import java.util.List;
+import java.util.concurrent.Future;
/**
* Service for application submitting or withdrawing their intents.
@@ -28,6 +47,14 @@
void withdraw(Intent intent);
/**
+ * Replaces the specified intent with a new one.
+ *
+ * @param oldIntentId identifier of the old intent being replaced
+ * @param newIntent new intent replacing the old one
+ */
+ void replace(IntentId oldIntentId, Intent newIntent);
+
+ /**
* Submits a batch of submit & withdraw operations. Such a batch is
* assumed to be processed together.
* <p/>
@@ -36,7 +63,7 @@
*
* @param operations batch of intent operations
*/
- void execute(IntentOperations operations);
+ Future<IntentOperations> execute(IntentOperations operations);
/**
* Returns an iterable of intents currently in the system.
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java
index bd140af..38662d2 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentState.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
/**
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentStore.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentStore.java
index c234b0f..7a62d61 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentStore.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentStore.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import org.onlab.onos.store.Store;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/IntentStoreDelegate.java b/core/api/src/main/java/org/onlab/onos/net/intent/IntentStoreDelegate.java
index 6c37db8..ab117d2 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/IntentStoreDelegate.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/IntentStoreDelegate.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import org.onlab.onos.store.StoreDelegate;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/LinkCollectionIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/LinkCollectionIntent.java
index 1b465aa..65d1ba8 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/LinkCollectionIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/LinkCollectionIntent.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import com.google.common.base.MoreObjects;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
index 7c638aa..13324d0 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/MultiPointToSinglePointIntent.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import com.google.common.base.MoreObjects;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
index 1e41120..d7996c9 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PathIntent.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import com.google.common.base.MoreObjects;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java
index d170c27..7b91cdf 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/PointToPointIntent.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import com.google.common.base.MoreObjects;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
index 62e36e7..e5790d0 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/SinglePointToMultiPointIntent.java
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.intent;
import com.google.common.base.MoreObjects;
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java b/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java
index 3e5e46f..d7efa52 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/package-info.java
@@ -1,3 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
/**
* Set of abstractions for conveying high-level intents for treatment of
* selected network traffic by allowing applications to express the
diff --git a/core/api/src/main/java/org/onlab/onos/net/statistic/DefaultLoad.java b/core/api/src/main/java/org/onlab/onos/net/statistic/DefaultLoad.java
new file mode 100644
index 0000000..f0f890a
--- /dev/null
+++ b/core/api/src/main/java/org/onlab/onos/net/statistic/DefaultLoad.java
@@ -0,0 +1,64 @@
+package org.onlab.onos.net.statistic;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.onos.net.flow.FlowRuleProvider;
+
+/**
+ * Implementation of a load.
+ */
+public class DefaultLoad implements Load {
+
+ private final boolean isValid;
+ private final long current;
+ private final long previous;
+ private final long time;
+
+ /**
+ * Creates an invalid load.
+ */
+ public DefaultLoad() {
+ this.isValid = false;
+ this.time = System.currentTimeMillis();
+ this.current = -1;
+ this.previous = -1;
+ }
+
+ /**
+ * Creates a load value from the parameters.
+ * @param current the current value
+ * @param previous the previous value
+ */
+ public DefaultLoad(long current, long previous) {
+ this.current = current;
+ this.previous = previous;
+ this.time = System.currentTimeMillis();
+ this.isValid = true;
+ }
+
+ @Override
+ public long rate() {
+ return (current - previous) / FlowRuleProvider.POLL_INTERVAL;
+ }
+
+ @Override
+ public long latest() {
+ return current;
+ }
+
+ @Override
+ public boolean isValid() {
+ return isValid;
+ }
+
+ @Override
+ public long time() {
+ return time;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper("Load").add("rate", rate())
+ .add("latest", latest()).toString();
+
+ }
+}
diff --git a/core/api/src/main/java/org/onlab/onos/net/statistic/Load.java b/core/api/src/main/java/org/onlab/onos/net/statistic/Load.java
index 534b10c..b609f2b 100644
--- a/core/api/src/main/java/org/onlab/onos/net/statistic/Load.java
+++ b/core/api/src/main/java/org/onlab/onos/net/statistic/Load.java
@@ -6,15 +6,27 @@
public interface Load {
/**
- * Obtain the current observed rate on a link.
+ * Obtain the current observed rate (in bytes/s) on a link.
* @return long value
*/
long rate();
/**
- * Obtain the latest counter viewed on that link.
+ * Obtain the latest bytes counter viewed on that link.
* @return long value
*/
long latest();
+ /**
+ * Indicates whether this load was built on valid values.
+ * @return boolean
+ */
+ boolean isValid();
+
+ /**
+ * Returns when this value was seen.
+ * @return epoch time
+ */
+ long time();
+
}
diff --git a/core/api/src/main/java/org/onlab/onos/net/topology/TopologyProvider.java b/core/api/src/main/java/org/onlab/onos/net/topology/TopologyProvider.java
index 7ad8cc0..312e154 100644
--- a/core/api/src/main/java/org/onlab/onos/net/topology/TopologyProvider.java
+++ b/core/api/src/main/java/org/onlab/onos/net/topology/TopologyProvider.java
@@ -7,4 +7,9 @@
*/
public interface TopologyProvider extends Provider {
+ /**
+ * Triggers topology recomputation.
+ */
+ void triggerRecompute();
+
}
diff --git a/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageResponse.java b/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageResponse.java
index ae2089d..d2a0039 100644
--- a/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageResponse.java
+++ b/core/api/src/main/java/org/onlab/onos/store/cluster/messaging/ClusterMessageResponse.java
@@ -1,12 +1,18 @@
package org.onlab.onos.store.cluster.messaging;
+import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.onlab.onos.cluster.NodeId;
-public interface ClusterMessageResponse {
+public interface ClusterMessageResponse extends Future<byte[]> {
+
public NodeId sender();
- public byte[] get(long timeout, TimeUnit timeunit) throws TimeoutException;
- public byte[] get(long timeout) throws InterruptedException;
+
+ // TODO InterruptedException, ExecutionException removed from original
+ // Future declaration. Revisit if we ever need those.
+ @Override
+ public byte[] get(long timeout, TimeUnit unit) throws TimeoutException;
+
}
diff --git a/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
index 5020459..cb982ba 100644
--- a/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
+++ b/core/api/src/test/java/org/onlab/onos/net/intent/FakeIntentManager.java
@@ -9,6 +9,7 @@
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
/**
* Fake implementation of the intent service to assist in developing tests of
@@ -171,11 +172,17 @@
}
@Override
- public void execute(IntentOperations operations) {
+ public void replace(IntentId oldIntentId, Intent newIntent) {
// TODO: implement later
}
@Override
+ public Future<IntentOperations> execute(IntentOperations operations) {
+ // TODO: implement later
+ return null;
+ }
+
+ @Override
public Set<Intent> getIntents() {
return Collections.unmodifiableSet(new HashSet<>(intents.values()));
}
diff --git a/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java b/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
index b2c172a..1a671f6 100644
--- a/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
+++ b/core/net/src/main/java/org/onlab/onos/cluster/impl/MastershipManager.java
@@ -227,10 +227,14 @@
if (clusterService.getNodes().size() > (clusterSize.intValue() / 2)) {
return true;
}
- //else {
+// else {
//FIXME: break tie for equal-sized clusters, by number of
// connected switches, then masters, then nodeId hash
- // }
+ // problem is, how do we get at channel info cleanly here?
+ // Also, what's the time hit for a distributed store look-up
+ // versus channel re-negotiation? bet on the latter being worse.
+
+// }
return false;
}
diff --git a/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java b/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
index fd47dc7..a157e50 100644
--- a/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/device/impl/DeviceManager.java
@@ -161,6 +161,17 @@
}
}
+ // Queries a device for port information.
+ private void queryPortInfo(DeviceId deviceId) {
+ Device device = store.getDevice(deviceId);
+ // FIXME: Device might not be there yet. (eventual consistent)
+ if (device == null) {
+ return;
+ }
+ DeviceProvider provider = getProvider(device.providerId());
+ provider.triggerProbe(device);
+ }
+
@Override
public void removeDevice(DeviceId deviceId) {
checkNotNull(deviceId, DEVICE_ID_NULL);
@@ -210,8 +221,6 @@
log.info("Device {} connected", deviceId);
// check my Role
MastershipRole role = mastershipService.requestRoleFor(deviceId);
- log.info("## - our role for {} is {} [master is {}]", deviceId, role,
- mastershipService.getMasterFor(deviceId));
if (role != MastershipRole.MASTER) {
// TODO: Do we need to explicitly tell the Provider that
// this instance is no longer the MASTER? probably not
@@ -265,7 +274,6 @@
// but if I was the last STANDBY connection, etc. and no one else
// was there to mark the device offline, this instance may need to
// temporarily request for Master Role and mark offline.
- log.info("## for {} role is {}", deviceId, mastershipService.getLocalRole(deviceId));
if (!mastershipService.getLocalRole(deviceId).equals(MastershipRole.MASTER)) {
log.debug("Device {} disconnected, but I am not the master", deviceId);
//let go of ability to be backup
@@ -373,7 +381,6 @@
final DeviceId did = event.subject();
final NodeId myNodeId = clusterService.getLocalNode().id();
- log.info("## got Mastershipevent for dev {}", did);
if (myNodeId.equals(event.roleInfo().master())) {
MastershipTerm term = termService.getMastershipTerm(did);
@@ -384,7 +391,6 @@
return;
}
- log.info("## setting term for CPS as new master for {}", did);
// only set the new term if I am the master
deviceClockProviderService.setMastershipTerm(did, term);
@@ -404,6 +410,7 @@
device.serialNumber(), device.chassisId()));
}
//TODO re-collect device information to fix potential staleness
+ queryPortInfo(did);
applyRole(did, MastershipRole.MASTER);
} else if (event.roleInfo().backups().contains(myNodeId)) {
applyRole(did, MastershipRole.STANDBY);
diff --git a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
index ba37d22..3ef9fc8 100644
--- a/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/flow/impl/FlowRuleManager.java
@@ -286,7 +286,8 @@
private void extraneousFlow(FlowRule flowRule) {
checkNotNull(flowRule, FLOW_RULE_NULL);
checkValidity();
- removeFlowRules(flowRule);
+ FlowRuleProvider frp = getProvider(flowRule.deviceId());
+ frp.removeFlowRule(flowRule);
log.debug("Flow {} is on switch but not in store.", flowRule);
}
@@ -380,14 +381,14 @@
final FlowRuleBatchRequest request = event.subject();
switch (event.type()) {
case BATCH_OPERATION_REQUESTED:
-// for (FlowEntry entry : request.toAdd()) {
-// //eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADD_REQUESTED, entry));
-// }
-// for (FlowEntry entry : request.toRemove()) {
-// //eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_REMOVE_REQUESTED, entry));
-// }
-// // FIXME: what about op.equals(FlowRuleOperation.MODIFY) ?
-//
+ for (FlowEntry entry : request.toAdd()) {
+ eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_ADD_REQUESTED, entry));
+ }
+ for (FlowEntry entry : request.toRemove()) {
+ eventDispatcher.post(new FlowRuleEvent(FlowRuleEvent.Type.RULE_REMOVE_REQUESTED, entry));
+ }
+ // FIXME: what about op.equals(FlowRuleOperation.MODIFY) ?
+
FlowRuleBatchOperation batchOperation = request.asBatchOperation();
FlowRuleProvider flowRuleProvider =
diff --git a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
index bfdc57e..3a3d476 100644
--- a/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/intent/impl/IntentManager.java
@@ -126,7 +126,13 @@
// FIXME: implement this method
@Override
- public void execute(IntentOperations operations) {
+ public void replace(IntentId oldIntentId, Intent newIntent) {
+ throw new UnsupportedOperationException("execute() is not implemented yet");
+ }
+
+ // FIXME: implement this method
+ @Override
+ public Future<IntentOperations> execute(IntentOperations operations) {
throw new UnsupportedOperationException("execute() is not implemented yet");
}
diff --git a/core/net/src/main/java/org/onlab/onos/net/link/impl/LinkManager.java b/core/net/src/main/java/org/onlab/onos/net/link/impl/LinkManager.java
index 835c47e..e59eb9f 100644
--- a/core/net/src/main/java/org/onlab/onos/net/link/impl/LinkManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/link/impl/LinkManager.java
@@ -208,7 +208,7 @@
LinkEvent event = store.createOrUpdateLink(provider().id(),
linkDescription);
if (event != null) {
- log.debug("Link {} detected", linkDescription);
+ log.info("Link {} detected", linkDescription);
post(event);
}
}
diff --git a/core/net/src/main/java/org/onlab/onos/net/packet/impl/PacketManager.java b/core/net/src/main/java/org/onlab/onos/net/packet/impl/PacketManager.java
index e682c49..49d21e0 100644
--- a/core/net/src/main/java/org/onlab/onos/net/packet/impl/PacketManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/packet/impl/PacketManager.java
@@ -68,7 +68,9 @@
checkNotNull(packet, "Packet cannot be null");
final Device device = deviceService.getDevice(packet.sendThrough());
final PacketProvider packetProvider = getProvider(device.providerId());
- packetProvider.emit(packet);
+ if (packetProvider != null) {
+ packetProvider.emit(packet);
+ }
}
@Override
diff --git a/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java b/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java
index 4bca215..6935148 100644
--- a/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java
+++ b/core/net/src/main/java/org/onlab/onos/net/statistic/impl/StatisticManager.java
@@ -9,14 +9,18 @@
import org.onlab.onos.net.ConnectPoint;
import org.onlab.onos.net.Link;
import org.onlab.onos.net.Path;
+
+import org.onlab.onos.net.flow.FlowEntry;
import org.onlab.onos.net.flow.FlowRule;
import org.onlab.onos.net.flow.FlowRuleEvent;
import org.onlab.onos.net.flow.FlowRuleListener;
import org.onlab.onos.net.flow.FlowRuleService;
+import org.onlab.onos.net.statistic.DefaultLoad;
import org.onlab.onos.net.statistic.Load;
import org.onlab.onos.net.statistic.StatisticService;
import org.onlab.onos.net.statistic.StatisticStore;
import org.slf4j.Logger;
+import java.util.Set;
import static org.slf4j.LoggerFactory.getLogger;
@@ -35,12 +39,14 @@
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected StatisticStore statisticStore;
+
private final InternalFlowRuleListener listener = new InternalFlowRuleListener();
@Activate
public void activate() {
flowRuleService.addListener(listener);
log.info("Started");
+
}
@Deactivate
@@ -51,27 +57,91 @@
@Override
public Load load(Link link) {
- return null;
+ return load(link.src());
}
@Override
public Load load(ConnectPoint connectPoint) {
- return null;
+ return loadInternal(connectPoint);
}
@Override
public Link max(Path path) {
- return null;
+ if (path.links().isEmpty()) {
+ return null;
+ }
+ Load maxLoad = new DefaultLoad();
+ Link maxLink = null;
+ for (Link link : path.links()) {
+ Load load = loadInternal(link.src());
+ if (load.rate() > maxLoad.rate()) {
+ maxLoad = load;
+ maxLink = link;
+ }
+ }
+ return maxLink;
}
@Override
public Link min(Path path) {
- return null;
+ if (path.links().isEmpty()) {
+ return null;
+ }
+ Load minLoad = new DefaultLoad();
+ Link minLink = null;
+ for (Link link : path.links()) {
+ Load load = loadInternal(link.src());
+ if (load.rate() < minLoad.rate()) {
+ minLoad = load;
+ minLink = link;
+ }
+ }
+ return minLink;
}
@Override
public FlowRule highestHitter(ConnectPoint connectPoint) {
- return null;
+ Set<FlowEntry> hitters = statisticStore.getCurrentStatistic(connectPoint);
+ if (hitters.isEmpty()) {
+ return null;
+ }
+
+ FlowEntry max = hitters.iterator().next();
+ for (FlowEntry entry : hitters) {
+ if (entry.bytes() > max.bytes()) {
+ max = entry;
+ }
+ }
+ return max;
+ }
+
+ private Load loadInternal(ConnectPoint connectPoint) {
+ Set<FlowEntry> current;
+ Set<FlowEntry> previous;
+ synchronized (statisticStore) {
+ current = statisticStore.getCurrentStatistic(connectPoint);
+ previous = statisticStore.getPreviousStatistic(connectPoint);
+ }
+ if (current == null || previous == null) {
+ return new DefaultLoad();
+ }
+ long currentAggregate = aggregate(current);
+ long previousAggregate = aggregate(previous);
+
+ return new DefaultLoad(currentAggregate, previousAggregate);
+ }
+
+ /**
+ * Aggregates a set of values.
+ * @param values the values to aggregate
+ * @return a long value
+ */
+ private long aggregate(Set<FlowEntry> values) {
+ long sum = 0;
+ for (FlowEntry f : values) {
+ sum += f.bytes();
+ }
+ return sum;
}
/**
@@ -81,7 +151,29 @@
@Override
public void event(FlowRuleEvent event) {
-
+ FlowRule rule = event.subject();
+ switch (event.type()) {
+ case RULE_ADDED:
+ case RULE_UPDATED:
+ if (rule instanceof FlowEntry) {
+ statisticStore.addOrUpdateStatistic((FlowEntry) rule);
+ } else {
+ log.warn("IT AIN'T A FLOWENTRY");
+ }
+ break;
+ case RULE_ADD_REQUESTED:
+ log.info("Preparing for stats");
+ statisticStore.prepareForStatistics(rule);
+ break;
+ case RULE_REMOVE_REQUESTED:
+ log.info("Removing stats");
+ statisticStore.removeFromStatistics(rule);
+ break;
+ case RULE_REMOVED:
+ break;
+ default:
+ log.warn("Unknown flow rule event {}", event);
+ }
}
}
diff --git a/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java b/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java
index 9631c66..0efd08b 100644
--- a/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java
+++ b/core/net/src/main/java/org/onlab/onos/net/topology/impl/DefaultTopologyProvider.java
@@ -5,6 +5,7 @@
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.event.AbstractEventAccumulator;
import org.onlab.onos.event.Event;
import org.onlab.onos.event.EventAccumulator;
@@ -39,6 +40,7 @@
* new topology snapshots.
*/
@Component(immediate = true)
+@Service
public class DefaultTopologyProvider extends AbstractProvider
implements TopologyProvider {
@@ -89,7 +91,7 @@
linkService.addListener(linkListener);
isStarted = true;
- triggerTopologyBuild(Collections.<Event>emptyList());
+ triggerRecompute();
log.info("Started");
}
@@ -108,6 +110,11 @@
log.info("Stopped");
}
+ @Override
+ public void triggerRecompute() {
+ triggerTopologyBuild(Collections.<Event>emptyList());
+ }
+
/**
* Triggers assembly of topology data citing the specified events as the
* reason.
@@ -177,7 +184,11 @@
@Override
public void run() {
- buildTopology(reasons);
+ try {
+ buildTopology(reasons);
+ } catch (Exception e) {
+ log.warn("Unable to compute topology due to: {}", e.getMessage());
+ }
}
}
diff --git a/core/net/src/test/java/org/onlab/onos/event/impl/TestEventDispatcher.java b/core/net/src/test/java/org/onlab/onos/event/impl/TestEventDispatcher.java
index 9eb3980..d2cbc24 100644
--- a/core/net/src/test/java/org/onlab/onos/event/impl/TestEventDispatcher.java
+++ b/core/net/src/test/java/org/onlab/onos/event/impl/TestEventDispatcher.java
@@ -15,6 +15,7 @@
implements EventDeliveryService {
@Override
+ @SuppressWarnings("unchecked")
public void post(Event event) {
EventSink sink = getSink(event.getClass());
checkState(sink != null, "No sink for event %s", event);
diff --git a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
index 59b2963..1677af6 100644
--- a/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/flow/impl/FlowRuleManagerTest.java
@@ -9,6 +9,9 @@
import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_REMOVED;
import static org.onlab.onos.net.flow.FlowRuleEvent.Type.RULE_UPDATED;
+
+import static org.onlab.onos.net.flow.FlowRuleEvent.Type.*;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -55,7 +58,7 @@
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.flow.criteria.Criterion;
import org.onlab.onos.net.flow.instructions.Instruction;
-import org.onlab.onos.net.intent.BatchOperation;
+import org.onlab.onos.net.flow.BatchOperation;
import org.onlab.onos.net.provider.AbstractProvider;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.store.trivial.impl.SimpleFlowRuleStore;
@@ -165,7 +168,8 @@
assertEquals("2 rules should exist", 2, flowCount());
providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2));
- validateEvents(RULE_ADDED, RULE_ADDED);
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+ RULE_ADDED, RULE_ADDED);
addFlowRule(1);
assertEquals("should still be 2 rules", 2, flowCount());
@@ -218,11 +222,12 @@
FlowEntry fe2 = new DefaultFlowEntry(f2);
FlowEntry fe3 = new DefaultFlowEntry(f3);
providerService.pushFlowMetrics(DID, ImmutableList.of(fe1, fe2, fe3));
- validateEvents(RULE_ADDED, RULE_ADDED, RULE_ADDED);
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+ RULE_ADDED, RULE_ADDED, RULE_ADDED);
mgr.removeFlowRules(f1, f2);
//removing from north, so no events generated
- validateEvents();
+ validateEvents(RULE_REMOVE_REQUESTED, RULE_REMOVE_REQUESTED);
assertEquals("3 rule should exist", 3, flowCount());
assertTrue("Entries should be pending remove.",
validateState(ImmutableMap.of(
@@ -244,7 +249,8 @@
service.removeFlowRules(f1);
fe1.setState(FlowEntryState.REMOVED);
providerService.flowRemoved(fe1);
- validateEvents(RULE_ADDED, RULE_ADDED, RULE_REMOVED);
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADDED,
+ RULE_ADDED, RULE_REMOVE_REQUESTED, RULE_REMOVED);
providerService.flowRemoved(fe1);
validateEvents();
@@ -253,7 +259,7 @@
FlowEntry fe3 = new DefaultFlowEntry(f3);
service.applyFlowRules(f3);
providerService.pushFlowMetrics(DID, Collections.singletonList(fe3));
- validateEvents(RULE_ADDED);
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADDED);
providerService.flowRemoved(fe3);
validateEvents();
@@ -282,7 +288,8 @@
f2, FlowEntryState.ADDED,
f3, FlowEntryState.PENDING_ADD)));
- validateEvents(RULE_ADDED, RULE_ADDED);
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+ RULE_ADDED, RULE_ADDED);
}
@Test
@@ -302,7 +309,7 @@
providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2, fe3));
- validateEvents(RULE_ADDED, RULE_ADDED);
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADDED, RULE_ADDED);
}
@@ -327,7 +334,8 @@
providerService.pushFlowMetrics(DID, Lists.newArrayList(fe1, fe2));
- validateEvents(RULE_ADDED, RULE_ADDED, RULE_REMOVED);
+ validateEvents(RULE_ADD_REQUESTED, RULE_ADD_REQUESTED, RULE_ADD_REQUESTED,
+ RULE_REMOVE_REQUESTED, RULE_ADDED, RULE_ADDED, RULE_REMOVED);
}
diff --git a/core/net/src/test/java/org/onlab/onos/net/topology/impl/TopologyManagerTest.java b/core/net/src/test/java/org/onlab/onos/net/topology/impl/TopologyManagerTest.java
index d369073..ada7e78 100644
--- a/core/net/src/test/java/org/onlab/onos/net/topology/impl/TopologyManagerTest.java
+++ b/core/net/src/test/java/org/onlab/onos/net/topology/impl/TopologyManagerTest.java
@@ -195,6 +195,10 @@
public TestProvider() {
super(PID);
}
+
+ @Override
+ public void triggerRecompute() {
+ }
}
private static class TestListener implements TopologyListener {
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
index 42d89de..4db23ce 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/cluster/messaging/impl/ClusterCommunicationManager.java
@@ -4,9 +4,9 @@
import java.io.IOException;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@@ -181,10 +181,13 @@
}
}
- private static final class InternalClusterMessageResponse implements ClusterMessageResponse {
+ private static final class InternalClusterMessageResponse
+ implements ClusterMessageResponse {
private final NodeId sender;
private final Response responseFuture;
+ private volatile boolean isCancelled = false;
+ private volatile boolean isDone = false;
public InternalClusterMessageResponse(NodeId sender, Response responseFuture) {
this.sender = sender;
@@ -198,12 +201,39 @@
@Override
public byte[] get(long timeout, TimeUnit timeunit)
throws TimeoutException {
- return responseFuture.get(timeout, timeunit);
+ final byte[] result = responseFuture.get(timeout, timeunit);
+ isDone = true;
+ return result;
}
@Override
- public byte[] get(long timeout) throws InterruptedException {
- return responseFuture.get();
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ if (isDone()) {
+ return false;
+ }
+ // doing nothing for now
+ // when onlab.netty Response support cancel, call them.
+ isCancelled = true;
+ return true;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return isCancelled;
+ }
+
+ @Override
+ public boolean isDone() {
+ return this.isDone || isCancelled();
+ }
+
+ @Override
+ public byte[] get() throws InterruptedException, ExecutionException {
+ // TODO: consider forbidding this call and force the use of timed get
+ // to enforce handling of remote peer failure scenario
+ final byte[] result = responseFuture.get();
+ isDone = true;
+ return result;
}
}
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
index ac9fc3e..21941b5 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/GossipDeviceStore.java
@@ -290,12 +290,17 @@
private DeviceEvent updateDevice(ProviderId providerId,
Device oldDevice,
Device newDevice, Timestamp newTimestamp) {
-
// We allow only certain attributes to trigger update
- if (!Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
- !Objects.equals(oldDevice.swVersion(), newDevice.swVersion()) ||
- !AnnotationsUtil.isEqual(oldDevice.annotations(), newDevice.annotations())) {
+ boolean propertiesChanged =
+ !Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
+ !Objects.equals(oldDevice.swVersion(), newDevice.swVersion());
+ boolean annotationsChanged =
+ !AnnotationsUtil.isEqual(oldDevice.annotations(), newDevice.annotations());
+ // Primary providers can respond to all changes, but ancillary ones
+ // should respond only to annotation changes.
+ if ((providerId.isAncillary() && annotationsChanged) ||
+ (!providerId.isAncillary() && (propertiesChanged || annotationsChanged))) {
boolean replaced = devices.replace(newDevice.id(), oldDevice, newDevice);
if (!replaced) {
verify(replaced,
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java
index 5f97afd..c789c96 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalDeviceEventSerializer.java
@@ -35,6 +35,8 @@
Class<InternalDeviceEvent> type) {
ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+
+ @SuppressWarnings("unchecked")
Timestamped<DeviceDescription> deviceDescription
= (Timestamped<DeviceDescription>) kryo.readClassAndObject(input);
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java
index 4f4a9e6..2facb7e 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/device/impl/InternalPortEventSerializer.java
@@ -37,6 +37,8 @@
Class<InternalPortEvent> type) {
ProviderId providerId = (ProviderId) kryo.readClassAndObject(input);
DeviceId deviceId = (DeviceId) kryo.readClassAndObject(input);
+
+ @SuppressWarnings("unchecked")
Timestamped<List<PortDescription>> portDescriptions
= (Timestamped<List<PortDescription>>) kryo.readClassAndObject(input);
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java
index 5e1457a..f03600d 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/flow/impl/ReplicaInfoManager.java
@@ -86,7 +86,7 @@
final List<NodeId> standbyList = Collections.<NodeId>emptyList();
eventDispatcher.post(new ReplicaInfoEvent(MASTER_CHANGED,
event.subject(),
- new ReplicaInfo(event.node(), standbyList)));
+ new ReplicaInfo(event.roleInfo().master(), standbyList)));
}
}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/intent/impl/DistributedIntentStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/intent/impl/DistributedIntentStore.java
index 0deccfb..f940f7d 100644
--- a/core/store/dist/src/main/java/org/onlab/onos/store/intent/impl/DistributedIntentStore.java
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/intent/impl/DistributedIntentStore.java
@@ -82,12 +82,28 @@
public IntentEvent setState(Intent intent, IntentState state) {
IntentId id = intent.id();
states.put(id, state);
- IntentEvent.Type type = (state == SUBMITTED ? IntentEvent.Type.SUBMITTED :
- (state == INSTALLED ? IntentEvent.Type.INSTALLED :
- (state == FAILED ? IntentEvent.Type.FAILED :
- state == WITHDRAWN ? IntentEvent.Type.WITHDRAWN :
- null)));
- return type == null ? null : new IntentEvent(type, intent);
+ IntentEvent.Type type = null;
+
+ switch (state) {
+ case SUBMITTED:
+ type = IntentEvent.Type.SUBMITTED;
+ break;
+ case INSTALLED:
+ type = IntentEvent.Type.INSTALLED;
+ break;
+ case FAILED:
+ type = IntentEvent.Type.FAILED;
+ break;
+ case WITHDRAWN:
+ type = IntentEvent.Type.WITHDRAWN;
+ break;
+ default:
+ break;
+ }
+ if (type == null) {
+ return null;
+ }
+ return new IntentEvent(type, intent);
}
@Override
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/DistributedStatisticStore.java b/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/DistributedStatisticStore.java
new file mode 100644
index 0000000..273e3cc
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/DistributedStatisticStore.java
@@ -0,0 +1,300 @@
+package org.onlab.onos.store.statistic.impl;
+
+import static org.onlab.onos.store.statistic.impl.StatisticStoreMessageSubjects.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import com.google.common.collect.Sets;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.cluster.ClusterService;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.flow.FlowEntry;
+import org.onlab.onos.net.flow.FlowRule;
+import org.onlab.onos.net.flow.instructions.Instruction;
+import org.onlab.onos.net.flow.instructions.Instructions;
+import org.onlab.onos.net.statistic.StatisticStore;
+import org.onlab.onos.store.cluster.messaging.ClusterCommunicationService;
+import org.onlab.onos.store.cluster.messaging.ClusterMessage;
+import org.onlab.onos.store.cluster.messaging.ClusterMessageHandler;
+import org.onlab.onos.store.cluster.messaging.ClusterMessageResponse;
+import org.onlab.onos.store.flow.ReplicaInfo;
+import org.onlab.onos.store.flow.ReplicaInfoService;
+import org.onlab.onos.store.serializers.KryoNamespaces;
+import org.onlab.onos.store.serializers.KryoSerializer;
+import org.onlab.util.KryoNamespace;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * Maintains statistics using RPC calls to collect stats from remote instances
+ * on demand.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedStatisticStore implements StatisticStore {
+
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private ReplicaInfoService replicaInfoManager;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private ClusterCommunicationService clusterCommunicator;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private ClusterService clusterService;
+
+ private Map<ConnectPoint, InternalStatisticRepresentation> representations =
+ new ConcurrentHashMap<>();
+
+ private Map<ConnectPoint, Set<FlowEntry>> previous =
+ new ConcurrentHashMap<>();
+
+ private Map<ConnectPoint, Set<FlowEntry>> current =
+ new ConcurrentHashMap<>();
+
+ protected static final KryoSerializer SERIALIZER = new KryoSerializer() {
+ @Override
+ protected void setupKryoPool() {
+ serializerPool = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ // register this store specific classes here
+ .build()
+ .populate(1);
+ }
+ };;
+
+ private static final long STATISTIC_STORE_TIMEOUT_MILLIS = 3000;
+
+ @Activate
+ public void activate() {
+ clusterCommunicator.addSubscriber(GET_CURRENT, new ClusterMessageHandler() {
+
+ @Override
+ public void handle(ClusterMessage message) {
+ ConnectPoint cp = SERIALIZER.decode(message.payload());
+ try {
+ message.respond(SERIALIZER.encode(getCurrentStatisticInternal(cp)));
+ } catch (IOException e) {
+ log.error("Failed to respond back", e);
+ }
+ }
+ });
+
+ clusterCommunicator.addSubscriber(GET_PREVIOUS, new ClusterMessageHandler() {
+
+ @Override
+ public void handle(ClusterMessage message) {
+ ConnectPoint cp = SERIALIZER.decode(message.payload());
+ try {
+ message.respond(SERIALIZER.encode(getPreviousStatisticInternal(cp)));
+ } catch (IOException e) {
+ log.error("Failed to respond back", e);
+ }
+ }
+ });
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public void prepareForStatistics(FlowRule rule) {
+ ConnectPoint cp = buildConnectPoint(rule);
+ if (cp == null) {
+ return;
+ }
+ InternalStatisticRepresentation rep;
+ synchronized (representations) {
+ rep = getOrCreateRepresentation(cp);
+ }
+ rep.prepare();
+ }
+
+ @Override
+ public synchronized void removeFromStatistics(FlowRule rule) {
+ ConnectPoint cp = buildConnectPoint(rule);
+ if (cp == null) {
+ return;
+ }
+ InternalStatisticRepresentation rep = representations.get(cp);
+ if (rep != null) {
+ rep.remove(rule);
+ }
+ Set<FlowEntry> values = current.get(cp);
+ if (values != null) {
+ values.remove(rule);
+ }
+ values = previous.get(cp);
+ if (values != null) {
+ values.remove(rule);
+ }
+
+ }
+
+ @Override
+ public void addOrUpdateStatistic(FlowEntry rule) {
+ ConnectPoint cp = buildConnectPoint(rule);
+ if (cp == null) {
+ return;
+ }
+ InternalStatisticRepresentation rep = representations.get(cp);
+ if (rep != null && rep.submit(rule)) {
+ updatePublishedStats(cp, rep.get());
+ }
+ }
+
+ private synchronized void updatePublishedStats(ConnectPoint cp,
+ Set<FlowEntry> flowEntries) {
+ Set<FlowEntry> curr = current.get(cp);
+ if (curr == null) {
+ curr = new HashSet<>();
+ }
+ previous.put(cp, curr);
+ current.put(cp, flowEntries);
+
+ }
+
+ @Override
+ public Set<FlowEntry> getCurrentStatistic(ConnectPoint connectPoint) {
+ ReplicaInfo replicaInfo = replicaInfoManager.getReplicaInfoFor(connectPoint.deviceId());
+ if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
+ return getCurrentStatisticInternal(connectPoint);
+ } else {
+ ClusterMessage message = new ClusterMessage(
+ clusterService.getLocalNode().id(),
+ GET_CURRENT,
+ SERIALIZER.encode(connectPoint));
+
+ try {
+ ClusterMessageResponse response =
+ clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
+ return SERIALIZER.decode(response.get(STATISTIC_STORE_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS));
+ } catch (IOException | TimeoutException e) {
+ // FIXME: throw a StatsStoreException
+ throw new RuntimeException(e);
+ }
+ }
+
+ }
+
+ private synchronized Set<FlowEntry> getCurrentStatisticInternal(ConnectPoint connectPoint) {
+ return current.get(connectPoint);
+ }
+
+ @Override
+ public Set<FlowEntry> getPreviousStatistic(ConnectPoint connectPoint) {
+ ReplicaInfo replicaInfo = replicaInfoManager.getReplicaInfoFor(connectPoint.deviceId());
+ if (replicaInfo.master().get().equals(clusterService.getLocalNode().id())) {
+ return getPreviousStatisticInternal(connectPoint);
+ } else {
+ ClusterMessage message = new ClusterMessage(
+ clusterService.getLocalNode().id(),
+ GET_PREVIOUS,
+ SERIALIZER.encode(connectPoint));
+
+ try {
+ ClusterMessageResponse response =
+ clusterCommunicator.sendAndReceive(message, replicaInfo.master().get());
+ return SERIALIZER.decode(response.get(STATISTIC_STORE_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS));
+ } catch (IOException | TimeoutException e) {
+ // FIXME: throw a StatsStoreException
+ throw new RuntimeException(e);
+ }
+ }
+
+ }
+
+ private synchronized Set<FlowEntry> getPreviousStatisticInternal(ConnectPoint connectPoint) {
+ return previous.get(connectPoint);
+ }
+
+ private InternalStatisticRepresentation getOrCreateRepresentation(ConnectPoint cp) {
+
+ if (representations.containsKey(cp)) {
+ return representations.get(cp);
+ } else {
+ InternalStatisticRepresentation rep = new InternalStatisticRepresentation();
+ representations.put(cp, rep);
+ return rep;
+ }
+
+ }
+
+ private ConnectPoint buildConnectPoint(FlowRule rule) {
+ PortNumber port = getOutput(rule);
+ if (port == null) {
+ log.warn("Rule {} has no output.", rule);
+ return null;
+ }
+ ConnectPoint cp = new ConnectPoint(rule.deviceId(), port);
+ return cp;
+ }
+
+ private PortNumber getOutput(FlowRule rule) {
+ for (Instruction i : rule.treatment().instructions()) {
+ if (i.type() == Instruction.Type.OUTPUT) {
+ Instructions.OutputInstruction out = (Instructions.OutputInstruction) i;
+ return out.port();
+ }
+ if (i.type() == Instruction.Type.DROP) {
+ return PortNumber.P0;
+ }
+ }
+ return null;
+ }
+
+ private class InternalStatisticRepresentation {
+
+ private final AtomicInteger counter = new AtomicInteger(0);
+ private final Set<FlowEntry> rules = new HashSet<>();
+
+ public void prepare() {
+ counter.incrementAndGet();
+ }
+
+ public synchronized void remove(FlowRule rule) {
+ rules.remove(rule);
+ counter.decrementAndGet();
+ }
+
+ public synchronized boolean submit(FlowEntry rule) {
+ if (rules.contains(rule)) {
+ rules.remove(rule);
+ }
+ rules.add(rule);
+ if (counter.get() == 0) {
+ return true;
+ } else {
+ return counter.decrementAndGet() == 0;
+ }
+ }
+
+ public synchronized Set<FlowEntry> get() {
+ counter.set(rules.size());
+ return Sets.newHashSet(rules);
+ }
+
+
+ }
+
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/StatisticStoreMessageSubjects.java b/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/StatisticStoreMessageSubjects.java
new file mode 100644
index 0000000..a096a3d
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/StatisticStoreMessageSubjects.java
@@ -0,0 +1,15 @@
+package org.onlab.onos.store.statistic.impl;
+
+import org.onlab.onos.store.cluster.messaging.MessageSubject;
+
+/**
+ * MessageSubjects used by DistributedStatisticStore peer-peer communication.
+ */
+public final class StatisticStoreMessageSubjects {
+ private StatisticStoreMessageSubjects() {}
+ public static final MessageSubject GET_CURRENT =
+ new MessageSubject("peer-return-current");
+ public static final MessageSubject GET_PREVIOUS =
+ new MessageSubject("peer-return-previous");
+
+}
diff --git a/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/package-info.java b/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/package-info.java
new file mode 100644
index 0000000..122d2be
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onlab/onos/store/statistic/impl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Implementation of the statistic store.
+ */
+package org.onlab.onos.store.statistic.impl;
\ No newline at end of file
diff --git a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
index 74ca8cd..316a3b4 100644
--- a/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
+++ b/core/store/hz/cluster/src/main/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStore.java
@@ -91,23 +91,14 @@
@Override
public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
- NodeId current = getNode(MASTER, deviceId);
- if (current == null) {
- if (isRole(STANDBY, nodeId, deviceId)) {
- //was previously standby, or set to standby from master
- return MastershipRole.STANDBY;
- } else {
- return MastershipRole.NONE;
- }
- } else {
- if (current.equals(nodeId)) {
- //*should* be in unusable, not always
- return MastershipRole.MASTER;
- } else {
- //may be in backups or unusable from earlier retirement
- return MastershipRole.STANDBY;
- }
+ final RoleValue roleInfo = getRoleValue(deviceId);
+ if (roleInfo.contains(MASTER, nodeId)) {
+ return MASTER;
}
+ if (roleInfo.contains(STANDBY, nodeId)) {
+ return STANDBY;
+ }
+ return NONE;
}
@Override
@@ -124,10 +115,11 @@
roleMap.put(deviceId, rv);
return null;
case STANDBY:
+ case NONE:
NodeId current = rv.get(MASTER);
if (current != null) {
//backup and replace current master
- rv.reassign(nodeId, NONE, STANDBY);
+ rv.reassign(current, NONE, STANDBY);
rv.replace(current, nodeId, MASTER);
} else {
//no master before so just add.
@@ -137,12 +129,6 @@
roleMap.put(deviceId, rv);
updateTerm(deviceId);
return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
- case NONE:
- rv.add(MASTER, nodeId);
- rv.reassign(nodeId, STANDBY, NONE);
- roleMap.put(deviceId, rv);
- updateTerm(deviceId);
- return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
default:
log.warn("unknown Mastership Role {}", role);
return null;
@@ -193,21 +179,28 @@
switch (role) {
case MASTER:
rv.reassign(local, STANDBY, NONE);
+ terms.putIfAbsent(deviceId, INIT);
roleMap.put(deviceId, rv);
break;
case STANDBY:
rv.reassign(local, NONE, STANDBY);
roleMap.put(deviceId, rv);
terms.putIfAbsent(deviceId, INIT);
-
break;
case NONE:
- //claim mastership
- rv.add(MASTER, local);
- rv.reassign(local, STANDBY, NONE);
+ //either we're the first standby, or first to device.
+ //for latter, claim mastership.
+ if (rv.get(MASTER) == null) {
+ rv.add(MASTER, local);
+ rv.reassign(local, STANDBY, NONE);
+ updateTerm(deviceId);
+ role = MastershipRole.MASTER;
+ } else {
+ rv.add(STANDBY, local);
+ rv.reassign(local, NONE, STANDBY);
+ role = MastershipRole.STANDBY;
+ }
roleMap.put(deviceId, rv);
- updateTerm(deviceId);
- role = MastershipRole.MASTER;
break;
default:
log.warn("unknown Mastership Role {}", role);
@@ -315,7 +308,10 @@
RoleValue value = roleMap.get(deviceId);
if (value == null) {
value = new RoleValue();
- roleMap.put(deviceId, value);
+ RoleValue concurrentlyAdded = roleMap.putIfAbsent(deviceId, value);
+ if (concurrentlyAdded != null) {
+ return concurrentlyAdded;
+ }
}
return value;
}
@@ -329,16 +325,6 @@
return null;
}
- //check if node is a certain role given a device
- private boolean isRole(
- MastershipRole role, NodeId nodeId, DeviceId deviceId) {
- RoleValue value = roleMap.get(deviceId);
- if (value != null) {
- return value.contains(role, nodeId);
- }
- return false;
- }
-
//adds or updates term information.
private void updateTerm(DeviceId deviceId) {
terms.lock(deviceId);
diff --git a/core/store/hz/cluster/src/test/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStoreTest.java b/core/store/hz/cluster/src/test/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStoreTest.java
index 5c867f4..c5daf6c 100644
--- a/core/store/hz/cluster/src/test/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStoreTest.java
+++ b/core/store/hz/cluster/src/test/java/org/onlab/onos/store/mastership/impl/DistributedMastershipStoreTest.java
@@ -97,6 +97,7 @@
assertEquals("wrong role:", NONE, dms.getRole(N1, DID1));
testStore.put(DID1, N1, true, false, true);
assertEquals("wrong role:", MASTER, dms.getRole(N1, DID1));
+ testStore.put(DID1, N2, false, true, false);
assertEquals("wrong role:", STANDBY, dms.getRole(N2, DID1));
}
@@ -155,6 +156,7 @@
//switch over to N2
assertEquals("wrong event:", Type.MASTER_CHANGED, dms.setMaster(N2, DID1).type());
+ System.out.println(dms.getTermFor(DID1).master() + ":" + dms.getTermFor(DID1).termNumber());
assertEquals("wrong term", MastershipTerm.of(N2, 1), dms.getTermFor(DID1));
//orphan switch - should be rare case
@@ -182,14 +184,9 @@
assertEquals("wrong event:", Type.MASTER_CHANGED, dms.relinquishRole(N1, DID1).type());
assertEquals("wrong master", N2, dms.getMaster(DID1));
- //STANDBY - nothing here, either
- assertNull("wrong event:", dms.relinquishRole(N1, DID1));
- assertEquals("wrong role for node:", STANDBY, dms.getRole(N1, DID1));
-
//all nodes "give up" on device, which goes back to NONE.
assertNull("wrong event:", dms.relinquishRole(N2, DID1));
assertEquals("wrong role for node:", NONE, dms.getRole(N2, DID1));
- assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID1));
assertEquals("wrong number of retired nodes", 2,
dms.roleMap.get(DID1).nodesOfRole(NONE).size());
@@ -201,6 +198,10 @@
assertEquals("wrong number of backup nodes", 1,
dms.roleMap.get(DID1).nodesOfRole(STANDBY).size());
+ //If STANDBY, should drop to NONE
+ assertNull("wrong event:", dms.relinquishRole(N1, DID1));
+ assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID1));
+
//NONE - nothing happens
assertNull("wrong event:", dms.relinquishRole(N1, DID2));
assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID2));
@@ -218,7 +219,7 @@
public void notify(MastershipEvent event) {
assertEquals("wrong event:", Type.MASTER_CHANGED, event.type());
assertEquals("wrong subject", DID1, event.subject());
- assertEquals("wrong subject", N1, event.node());
+ assertEquals("wrong subject", N1, event.roleInfo().master());
addLatch.countDown();
}
};
diff --git a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
index e6259d8..7fddb01 100644
--- a/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onlab/onos/store/serializers/KryoNamespaces.java
@@ -4,6 +4,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import org.onlab.onos.cluster.ControllerNode;
import org.onlab.onos.cluster.DefaultControllerNode;
@@ -30,6 +31,7 @@
import org.onlab.onos.net.flow.DefaultFlowRule;
import org.onlab.onos.net.flow.DefaultTrafficSelector;
import org.onlab.onos.net.flow.DefaultTrafficTreatment;
+import org.onlab.onos.net.flow.FlowEntry;
import org.onlab.onos.net.flow.FlowId;
import org.onlab.onos.net.flow.StoredFlowEntry;
import org.onlab.onos.net.flow.criteria.Criteria;
@@ -77,6 +79,7 @@
ArrayList.class,
Arrays.asList().getClass(),
HashMap.class,
+ HashSet.class,
//
//
ControllerNode.State.class,
@@ -98,6 +101,8 @@
DefaultFlowEntry.class,
StoredFlowEntry.class,
DefaultFlowRule.class,
+ DefaultFlowEntry.class,
+ FlowEntry.FlowEntryState.class,
FlowId.class,
DefaultTrafficSelector.class,
Criteria.PortCriterion.class,
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
index fbfaf9d..c1be1f5 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleDeviceStore.java
@@ -70,15 +70,16 @@
public static final String DEVICE_NOT_FOUND = "Device with ID %s not found";
- // collection of Description given from various providers
+ // Collection of Description given from various providers
private final ConcurrentMap<DeviceId, Map<ProviderId, DeviceDescriptions>>
- deviceDescs = Maps.newConcurrentMap();
+ deviceDescs = Maps.newConcurrentMap();
- // cache of Device and Ports generated by compositing descriptions from providers
+ // Cache of Device and Ports generated by compositing descriptions from providers
private final ConcurrentMap<DeviceId, Device> devices = Maps.newConcurrentMap();
- private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, Port>> devicePorts = Maps.newConcurrentMap();
+ private final ConcurrentMap<DeviceId, ConcurrentMap<PortNumber, Port>>
+ devicePorts = Maps.newConcurrentMap();
- // available(=UP) devices
+ // Available (=UP) devices
private final Set<DeviceId> availableDevices = Sets.newConcurrentHashSet();
@@ -113,19 +114,17 @@
@Override
public DeviceEvent createOrUpdateDevice(ProviderId providerId,
- DeviceId deviceId,
- DeviceDescription deviceDescription) {
-
+ DeviceId deviceId,
+ DeviceDescription deviceDescription) {
Map<ProviderId, DeviceDescriptions> providerDescs
- = getOrCreateDeviceDescriptions(deviceId);
+ = getOrCreateDeviceDescriptions(deviceId);
synchronized (providerDescs) {
// locking per device
-
DeviceDescriptions descs
- = getOrCreateProviderDeviceDescriptions(providerDescs,
- providerId,
- deviceDescription);
+ = getOrCreateProviderDeviceDescriptions(providerDescs,
+ providerId,
+ deviceDescription);
Device oldDevice = devices.get(deviceId);
// update description
@@ -145,12 +144,11 @@
// Creates the device and returns the appropriate event if necessary.
// Guarded by deviceDescs value (=Device lock)
private DeviceEvent createDevice(ProviderId providerId, Device newDevice) {
-
// update composed device cache
Device oldDevice = devices.putIfAbsent(newDevice.id(), newDevice);
verify(oldDevice == null,
- "Unexpected Device in cache. PID:%s [old=%s, new=%s]",
- providerId, oldDevice, newDevice);
+ "Unexpected Device in cache. PID:%s [old=%s, new=%s]",
+ providerId, oldDevice, newDevice);
if (!providerId.isAncillary()) {
availableDevices.add(newDevice.id());
@@ -162,17 +160,24 @@
// Updates the device and returns the appropriate event if necessary.
// Guarded by deviceDescs value (=Device lock)
private DeviceEvent updateDevice(ProviderId providerId, Device oldDevice, Device newDevice) {
-
// We allow only certain attributes to trigger update
- if (!Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
- !Objects.equals(oldDevice.swVersion(), newDevice.swVersion()) ||
- !AnnotationsUtil.isEqual(oldDevice.annotations(), newDevice.annotations())) {
+ boolean propertiesChanged =
+ !Objects.equals(oldDevice.hwVersion(), newDevice.hwVersion()) ||
+ !Objects.equals(oldDevice.swVersion(), newDevice.swVersion());
+ boolean annotationsChanged =
+ !AnnotationsUtil.isEqual(oldDevice.annotations(), newDevice.annotations());
+
+ // Primary providers can respond to all changes, but ancillary ones
+ // should respond only to annotation changes.
+ if ((providerId.isAncillary() && annotationsChanged) ||
+ (!providerId.isAncillary() && (propertiesChanged || annotationsChanged))) {
boolean replaced = devices.replace(newDevice.id(), oldDevice, newDevice);
if (!replaced) {
+ // FIXME: Is the enclosing if required here?
verify(replaced,
- "Replacing devices cache failed. PID:%s [expected:%s, found:%s, new=%s]",
- providerId, oldDevice, devices.get(newDevice.id())
+ "Replacing devices cache failed. PID:%s [expected:%s, found:%s, new=%s]",
+ providerId, oldDevice, devices.get(newDevice.id())
, newDevice);
}
if (!providerId.isAncillary()) {
@@ -193,7 +198,7 @@
@Override
public DeviceEvent markOffline(DeviceId deviceId) {
Map<ProviderId, DeviceDescriptions> providerDescs
- = getOrCreateDeviceDescriptions(deviceId);
+ = getOrCreateDeviceDescriptions(deviceId);
// locking device
synchronized (providerDescs) {
@@ -212,9 +217,8 @@
@Override
public List<DeviceEvent> updatePorts(ProviderId providerId,
- DeviceId deviceId,
- List<PortDescription> portDescriptions) {
-
+ DeviceId deviceId,
+ List<PortDescription> portDescriptions) {
Device device = devices.get(deviceId);
checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
@@ -226,8 +230,8 @@
DeviceDescriptions descs = descsMap.get(providerId);
// every provider must provide DeviceDescription.
checkArgument(descs != null,
- "Device description for Device ID %s from Provider %s was not found",
- deviceId, providerId);
+ "Device description for Device ID %s from Provider %s was not found",
+ deviceId, providerId);
Map<PortNumber, Port> ports = getPortMap(deviceId);
@@ -247,8 +251,8 @@
newPort = composePort(device, number, descsMap);
events.add(oldPort == null ?
- createPort(device, newPort, ports) :
- updatePort(device, oldPort, newPort, ports));
+ createPort(device, newPort, ports) :
+ updatePort(device, oldPort, newPort, ports));
}
events.addAll(pruneOldPorts(device, ports, processed));
@@ -272,7 +276,7 @@
Port newPort,
Map<PortNumber, Port> ports) {
if (oldPort.isEnabled() != newPort.isEnabled() ||
- !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) {
+ !AnnotationsUtil.isEqual(oldPort.annotations(), newPort.annotations())) {
ports.put(oldPort.number(), newPort);
return new DeviceEvent(PORT_UPDATED, device, newPort);
@@ -303,7 +307,7 @@
// exist, it creates and registers a new one.
private ConcurrentMap<PortNumber, Port> getPortMap(DeviceId deviceId) {
return createIfAbsentUnchecked(devicePorts, deviceId,
- NewConcurrentHashMap.<PortNumber, Port>ifNeeded());
+ NewConcurrentHashMap.<PortNumber, Port>ifNeeded());
}
private Map<ProviderId, DeviceDescriptions> getOrCreateDeviceDescriptions(
@@ -325,9 +329,8 @@
// Guarded by deviceDescs value (=Device lock)
private DeviceDescriptions getOrCreateProviderDeviceDescriptions(
- Map<ProviderId, DeviceDescriptions> device,
- ProviderId providerId, DeviceDescription deltaDesc) {
-
+ Map<ProviderId, DeviceDescriptions> device,
+ ProviderId providerId, DeviceDescription deltaDesc) {
synchronized (device) {
DeviceDescriptions r = device.get(providerId);
if (r == null) {
@@ -340,7 +343,7 @@
@Override
public DeviceEvent updatePortStatus(ProviderId providerId, DeviceId deviceId,
- PortDescription portDescription) {
+ PortDescription portDescription) {
Device device = devices.get(deviceId);
checkArgument(device != null, DEVICE_NOT_FOUND, deviceId);
@@ -351,8 +354,8 @@
DeviceDescriptions descs = descsMap.get(providerId);
// assuming all providers must give DeviceDescription first
checkArgument(descs != null,
- "Device description for Device ID %s from Provider %s was not found",
- deviceId, providerId);
+ "Device description for Device ID %s from Provider %s was not found",
+ deviceId, providerId);
ConcurrentMap<PortNumber, Port> ports = getPortMap(deviceId);
final PortNumber number = portDescription.portNumber();
@@ -404,19 +407,19 @@
availableDevices.remove(deviceId);
descs.clear();
return device == null ? null :
- new DeviceEvent(DEVICE_REMOVED, device, null);
+ new DeviceEvent(DEVICE_REMOVED, device, null);
}
}
/**
* Returns a Device, merging description given from multiple Providers.
*
- * @param deviceId device identifier
+ * @param deviceId device identifier
* @param providerDescs Collection of Descriptions from multiple providers
* @return Device instance
*/
private Device composeDevice(DeviceId deviceId,
- Map<ProviderId, DeviceDescriptions> providerDescs) {
+ Map<ProviderId, DeviceDescriptions> providerDescs) {
checkArgument(!providerDescs.isEmpty(), "No Device descriptions supplied");
@@ -447,21 +450,21 @@
annotations = merge(annotations, e.getValue().getDeviceDesc().annotations());
}
- return new DefaultDevice(primary, deviceId , type, manufacturer,
- hwVersion, swVersion, serialNumber,
- chassisId, annotations);
+ return new DefaultDevice(primary, deviceId, type, manufacturer,
+ hwVersion, swVersion, serialNumber,
+ chassisId, annotations);
}
/**
* Returns a Port, merging description given from multiple Providers.
*
- * @param device device the port is on
- * @param number port number
+ * @param device device the port is on
+ * @param number port number
* @param descsMap Collection of Descriptions from multiple providers
* @return Port instance
*/
private Port composePort(Device device, PortNumber number,
- Map<ProviderId, DeviceDescriptions> descsMap) {
+ Map<ProviderId, DeviceDescriptions> descsMap) {
ProviderId primary = pickPrimaryPID(descsMap);
DeviceDescriptions primDescs = descsMap.get(primary);
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
index 3d10d3d..bbfc263 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleFlowRuleStore.java
@@ -187,17 +187,23 @@
public void deleteFlowRule(FlowRule rule) {
List<StoredFlowEntry> entries = getFlowEntries(rule.deviceId(), rule.id());
+
synchronized (entries) {
for (StoredFlowEntry entry : entries) {
if (entry.equals(rule)) {
synchronized (entry) {
entry.setState(FlowEntryState.PENDING_REMOVE);
// TODO: Should we notify only if it's "remote" event?
- //notifyDelegate(new FlowRuleEvent(Type.RULE_REMOVE_REQUESTED, rule));
+ notifyDelegate(FlowRuleBatchEvent.create(
+ new FlowRuleBatchRequest(
+ Collections.<FlowEntry>emptyList(),
+ Arrays.<FlowEntry>asList(entry))));
}
}
}
}
+
+
//log.warn("Cannot find rule {}", rule);
}
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
index 0ec8612..12e62e2 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleIntentStore.java
@@ -82,12 +82,28 @@
public IntentEvent setState(Intent intent, IntentState state) {
IntentId id = intent.id();
states.put(id, state);
- IntentEvent.Type type = (state == SUBMITTED ? IntentEvent.Type.SUBMITTED :
- (state == INSTALLED ? IntentEvent.Type.INSTALLED :
- (state == FAILED ? IntentEvent.Type.FAILED :
- state == WITHDRAWN ? IntentEvent.Type.WITHDRAWN :
- null)));
- return type == null ? null : new IntentEvent(type, intent);
+ IntentEvent.Type type = null;
+
+ switch (state) {
+ case SUBMITTED:
+ type = IntentEvent.Type.SUBMITTED;
+ break;
+ case INSTALLED:
+ type = IntentEvent.Type.INSTALLED;
+ break;
+ case FAILED:
+ type = IntentEvent.Type.FAILED;
+ break;
+ case WITHDRAWN:
+ type = IntentEvent.Type.WITHDRAWN;
+ break;
+ default:
+ break;
+ }
+ if (type == null) {
+ return null;
+ }
+ return new IntentEvent(type, intent);
}
@Override
diff --git a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
index 5c87921..09d6a62 100644
--- a/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
+++ b/core/store/trivial/src/main/java/org/onlab/onos/store/trivial/impl/SimpleLinkStore.java
@@ -192,14 +192,6 @@
// Creates and stores the link and returns the appropriate event.
// Guarded by linkDescs value (=locking each Link)
private LinkEvent createLink(LinkKey key, Link newLink) {
-
- if (newLink.providerId().isAncillary()) {
- // TODO: revisit ancillary only Link handling
-
- // currently treating ancillary only as down (not visible outside)
- return null;
- }
-
links.put(key, newLink);
srcLinks.put(newLink.src().deviceId(), key);
dstLinks.put(newLink.dst().deviceId(), key);
@@ -209,10 +201,8 @@
// Updates, if necessary the specified link and returns the appropriate event.
// Guarded by linkDescs value (=locking each Link)
private LinkEvent updateLink(LinkKey key, Link oldLink, Link newLink) {
-
if (newLink.providerId().isAncillary()) {
// TODO: revisit ancillary only Link handling
-
// currently treating ancillary only as down (not visible outside)
return null;
}
diff --git a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
index 02cb411..a7f40ac 100644
--- a/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onlab/onos/store/trivial/impl/SimpleLinkStoreTest.java
@@ -1,18 +1,6 @@
package org.onlab.onos.store.trivial.impl;
-import static org.junit.Assert.*;
-import static org.onlab.onos.net.DeviceId.deviceId;
-import static org.onlab.onos.net.Link.Type.*;
-import static org.onlab.onos.net.link.LinkEvent.Type.*;
-import static org.onlab.onos.store.trivial.impl.SimpleDeviceStoreTest.assertAnnotationsEquals;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
+import com.google.common.collect.Iterables;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
@@ -23,17 +11,27 @@
import org.onlab.onos.net.DefaultAnnotations;
import org.onlab.onos.net.DeviceId;
import org.onlab.onos.net.Link;
+import org.onlab.onos.net.Link.Type;
import org.onlab.onos.net.LinkKey;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.SparseAnnotations;
-import org.onlab.onos.net.Link.Type;
import org.onlab.onos.net.link.DefaultLinkDescription;
import org.onlab.onos.net.link.LinkEvent;
import org.onlab.onos.net.link.LinkStore;
import org.onlab.onos.net.link.LinkStoreDelegate;
import org.onlab.onos.net.provider.ProviderId;
-import com.google.common.collect.Iterables;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.Link.Type.*;
+import static org.onlab.onos.net.link.LinkEvent.Type.*;
+import static org.onlab.onos.store.trivial.impl.SimpleDeviceStoreTest.assertAnnotationsEquals;
/**
* Test of the simple LinkStore implementation.
@@ -301,7 +299,7 @@
LinkEvent event = linkStore.createOrUpdateLink(PIDA,
new DefaultLinkDescription(src, dst, INDIRECT, A1));
- assertNull("Ancillary only link is ignored", event);
+ assertNotNull("Ancillary only link is ignored", event);
// add Primary link
LinkEvent event2 = linkStore.createOrUpdateLink(PID,
@@ -309,7 +307,7 @@
assertLink(DID1, P1, DID2, P2, INDIRECT, event2.subject());
assertAnnotationsEquals(event2.subject().annotations(), A2, A1);
- assertEquals(LINK_ADDED, event2.type());
+ assertEquals(LINK_UPDATED, event2.type());
// update link type
LinkEvent event3 = linkStore.createOrUpdateLink(PID,
@@ -375,7 +373,7 @@
}
@Test
- public final void testAncillaryOnlyNotVisible() {
+ public final void testAncillaryVisible() {
ConnectPoint src = new ConnectPoint(DID1, P1);
ConnectPoint dst = new ConnectPoint(DID2, P2);
@@ -384,18 +382,8 @@
new DefaultLinkDescription(src, dst, INDIRECT, A1));
// Ancillary only link should not be visible
- assertEquals(0, linkStore.getLinkCount());
-
- assertTrue(Iterables.isEmpty(linkStore.getLinks()));
-
- assertNull(linkStore.getLink(src, dst));
-
- assertEquals(Collections.emptySet(), linkStore.getIngressLinks(dst));
-
- assertEquals(Collections.emptySet(), linkStore.getEgressLinks(src));
-
- assertEquals(Collections.emptySet(), linkStore.getDeviceEgressLinks(DID1));
- assertEquals(Collections.emptySet(), linkStore.getDeviceIngressLinks(DID2));
+ assertEquals(1, linkStore.getLinkCount());
+ assertNotNull(linkStore.getLink(src, dst));
}
// If Delegates should be called only on remote events,
diff --git a/features/features.xml b/features/features.xml
index e6c4beb..e363094 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -199,9 +199,16 @@
<feature name="onos-app-metrics" version="1.0.0"
description="ONOS metrics applications">
+ <feature>onos-app-metrics-intent</feature>
<feature>onos-app-metrics-topology</feature>
</feature>
+ <feature name="onos-app-metrics-intent" version="1.0.0"
+ description="ONOS intent metrics application">
+ <feature>onos-api</feature>
+ <bundle>mvn:org.onlab.onos/onos-app-metrics-intent/1.0.0-SNAPSHOT</bundle>
+ </feature>
+
<feature name="onos-app-metrics-topology" version="1.0.0"
description="ONOS topology metrics application">
<feature>onos-api</feature>
diff --git a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java
index 6fd02bc..1375a20 100644
--- a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java
+++ b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitch.java
@@ -110,8 +110,7 @@
*
* @param role the failed role
*/
- void returnRoleAssertFailure(RoleState role);
-
+ public void returnRoleAssertFailure(RoleState role);
/**
* Indicates if this switch is optical.
@@ -120,5 +119,4 @@
*/
public boolean isOptical();
-
}
diff --git a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java
index 53217da..33e5bdf 100644
--- a/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java
+++ b/openflow/api/src/main/java/org/onlab/onos/openflow/controller/OpenFlowSwitchListener.java
@@ -20,6 +20,12 @@
public void switchRemoved(Dpid dpid);
/**
+ * Notify that the switch has changed in some way.
+ * @param dpid the switch that changed
+ */
+ public void switchChanged(Dpid dpid);
+
+ /**
* Notify that a port has changed.
* @param dpid the switch on which the change happened.
* @param status the new state of the port.
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java
index 009cd3f..4ea2f71 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OFChannelHandler.java
@@ -41,7 +41,6 @@
import org.projectfloodlight.openflow.protocol.OFHelloElem;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
-import org.projectfloodlight.openflow.protocol.OFPacketOut;
import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFPortDescStatsRequest;
import org.projectfloodlight.openflow.protocol.OFPortStatus;
@@ -565,6 +564,9 @@
@Override
void processOFStatisticsReply(OFChannelHandler h,
OFStatsReply m) {
+ if (m.getStatsType().equals(OFStatsType.PORT_DESC)) {
+ h.sw.setPortDescReply((OFPortDescStatsReply) m);
+ }
h.dispatchMessage(m);
}
@@ -608,6 +610,12 @@
h.dispatchMessage(m);
}
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) {
+ h.sw.setFeaturesReply(m);
+ h.dispatchMessage(m);
+ }
+
};
private final boolean handshakeComplete;
@@ -652,10 +660,9 @@
* However, we could be more forgiving
* @param h the channel handler that received the message
* @param m the message
- * @throws SwitchStateException
- * @throws SwitchStateExeption we always through the execption
+ * @throws SwitchStateException we always throw the exception
*/
- // needs to be protected because enum members are acutally subclasses
+ // needs to be protected because enum members are actually subclasses
protected void illegalMessageReceived(OFChannelHandler h, OFMessage m)
throws SwitchStateException {
String msg = getSwitchStateMessage(h, m,
@@ -1016,7 +1023,9 @@
// all state for the original switch (with the same dpid),
// which we obviously don't want.
log.info("{}:removal called", getSwitchInfoString());
- sw.removeConnectedSwitch();
+ if (sw != null) {
+ sw.removeConnectedSwitch();
+ }
} else {
// A duplicate was disconnected on this ChannelHandler,
// this is the same switch reconnecting, but the original state was
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
index 565ccb9..9ef3077 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/controller/impl/OpenFlowControllerImpl.java
@@ -27,6 +27,8 @@
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPortStatus;
+import org.projectfloodlight.openflow.protocol.OFStatsReply;
+import org.projectfloodlight.openflow.protocol.OFStatsType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -146,6 +148,11 @@
l.portChanged(dpid, (OFPortStatus) msg);
}
break;
+ case FEATURES_REPLY:
+ for (OpenFlowSwitchListener l : ofSwitchListener) {
+ l.switchChanged(dpid);
+ }
+ break;
case PACKET_IN:
OpenFlowPacketContext pktCtx = DefaultOpenFlowPacketContext
.packetContextFromPacketIn(this.getSwitch(dpid),
@@ -154,9 +161,15 @@
p.handlePacket(pktCtx);
}
break;
+ case STATS_REPLY:
+ OFStatsReply reply = (OFStatsReply) msg;
+ if (reply.getStatsType().equals(OFStatsType.PORT_DESC)) {
+ for (OpenFlowSwitchListener l : ofSwitchListener) {
+ l.switchChanged(dpid);
+ }
+ }
case FLOW_REMOVED:
case ERROR:
- case STATS_REPLY:
case BARRIER_REPLY:
executor.submit(new OFMessageHandler(dpid, msg));
break;
@@ -194,7 +207,7 @@
+ "value for dpid: {}", dpid);
return false;
} else {
- log.error("Added switch {}", dpid);
+ log.info("Added switch {}", dpid);
connectedSwitches.put(dpid, sw);
for (OpenFlowSwitchListener l : ofSwitchListener) {
l.switchAdded(dpid);
diff --git a/openflow/ctl/src/main/java/org/onlab/onos/openflow/drivers/impl/OFSwitchImplCPqD13.java b/openflow/ctl/src/main/java/org/onlab/onos/openflow/drivers/impl/OFSwitchImplCPqD13.java
index c4c2e19..3d60dfa 100644
--- a/openflow/ctl/src/main/java/org/onlab/onos/openflow/drivers/impl/OFSwitchImplCPqD13.java
+++ b/openflow/ctl/src/main/java/org/onlab/onos/openflow/drivers/impl/OFSwitchImplCPqD13.java
@@ -1188,7 +1188,8 @@
.setHardTimeout(0)
.setXid(getNextTransactionId())
.build();
- sendMsg(tableMissEntry);
+
+ write(tableMissEntry);
}
private void sendBarrier(boolean finalBarrier) {
@@ -1200,7 +1201,8 @@
.buildBarrierRequest()
.setXid(xid)
.build();
- sendMsg(br);
+
+ write(br);
}
@Override
@@ -1210,7 +1212,7 @@
@Override
public void write(OFMessage msg) {
- this.channel.write(msg);
+ this.channel.write(Collections.singletonList(msg));
}
diff --git a/pom.xml b/pom.xml
index a9c83fd..8aa5ce4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -88,7 +88,6 @@
<version>18.0</version>
</dependency>
-
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
@@ -610,7 +609,8 @@
</plugin>
</plugins>
-
</reporting>
-
+ <prerequisites>
+ <maven>3.0.0</maven>
+ </prerequisites>
</project>
diff --git a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
index 75ce1da..bf4fee0 100644
--- a/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
+++ b/providers/lldp/src/main/java/org/onlab/onos/provider/lldp/impl/LinkDiscovery.java
@@ -16,6 +16,7 @@
package org.onlab.onos.provider.lldp.impl;
+import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
import java.nio.ByteBuffer;
@@ -95,11 +96,13 @@
*/
public LinkDiscovery(Device device, PacketService pktService,
MastershipService masterService, LinkProviderService providerService, Boolean... useBDDP) {
+
this.device = device;
this.probeRate = 3000;
this.linkProvider = providerService;
this.pktService = pktService;
- this.mastershipService = masterService;
+
+ this.mastershipService = checkNotNull(masterService, "WTF!");
this.slowPorts = Collections.synchronizedSet(new HashSet<Long>());
this.fastPorts = Collections.synchronizedSet(new HashSet<Long>());
this.portProbeCount = new HashMap<>();
@@ -344,7 +347,14 @@
}
private void sendProbes(Long portNumber) {
- if (mastershipService.getLocalRole(this.device.id()) ==
+ if (device == null) {
+ log.warn("CRAZY SHIT");
+ }
+ if (mastershipService == null) {
+ log.warn("INSANE");
+ }
+ if (device.type() != Device.Type.ROADM &&
+ mastershipService.getLocalRole(this.device.id()) ==
MastershipRole.MASTER) {
OutboundPacket pkt = this.createOutBoundLLDP(portNumber);
pktService.emit(pkt);
diff --git a/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java b/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
index fcc7810..984e8ae 100644
--- a/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
+++ b/providers/openflow/device/src/main/java/org/onlab/onos/provider/of/device/impl/OpenFlowDeviceProvider.java
@@ -23,7 +23,9 @@
import org.onlab.onos.openflow.controller.OpenFlowSwitch;
import org.onlab.onos.openflow.controller.OpenFlowSwitchListener;
import org.onlab.onos.openflow.controller.RoleState;
+import org.onlab.onos.openflow.controller.driver.OpenFlowSwitchDriver;
import org.onlab.packet.ChassisId;
+import org.projectfloodlight.openflow.protocol.OFFactory;
import org.projectfloodlight.openflow.protocol.OFPortConfig;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.OFPortState;
@@ -89,6 +91,28 @@
@Override
public void triggerProbe(Device device) {
LOG.info("Triggering probe on device {}", device.id());
+
+ // 1. check device liveness
+ // FIXME if possible, we might want this to be part of
+ // OpenFlowSwitch interface so the driver interface isn't misused.
+ OpenFlowSwitch sw = controller.getSwitch(dpid(device.id().uri()));
+ if (!((OpenFlowSwitchDriver) sw).isConnected()) {
+ providerService.deviceDisconnected(device.id());
+ return;
+ }
+
+ // 2. Prompt an update of port information. Do we have an XID for this?
+ OFFactory fact = sw.factory();
+ switch (fact.getVersion()) {
+ case OF_10:
+ sw.sendMsg(fact.buildFeaturesRequest().setXid(0).build());
+ break;
+ case OF_13:
+ sw.sendMsg(fact.buildPortDescStatsRequest().setXid(0).build());
+ break;
+ default:
+ LOG.warn("Unhandled protocol version");
+ }
}
@Override
@@ -141,6 +165,17 @@
providerService.deviceDisconnected(deviceId(uri(dpid)));
}
+
+ @Override
+ public void switchChanged(Dpid dpid) {
+ if (providerService == null) {
+ return;
+ }
+ DeviceId did = deviceId(uri(dpid));
+ OpenFlowSwitch sw = controller.getSwitch(dpid);
+ providerService.updatePorts(did, buildPortDescriptions(sw.getPorts()));
+ }
+
@Override
public void portChanged(Dpid dpid, OFPortStatus status) {
PortDescription portDescription = buildPortDescription(status.getDesc());
diff --git a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
index b7d84f0..8d3c018 100644
--- a/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
+++ b/providers/openflow/flow/src/main/java/org/onlab/onos/provider/of/flow/impl/OpenFlowRuleProvider.java
@@ -31,7 +31,7 @@
import org.onlab.onos.net.flow.FlowRuleProvider;
import org.onlab.onos.net.flow.FlowRuleProviderRegistry;
import org.onlab.onos.net.flow.FlowRuleProviderService;
-import org.onlab.onos.net.intent.BatchOperation;
+import org.onlab.onos.net.flow.BatchOperation;
import org.onlab.onos.net.provider.AbstractProvider;
import org.onlab.onos.net.provider.ProviderId;
import org.onlab.onos.net.topology.TopologyService;
@@ -107,6 +107,8 @@
private final Map<Long, InstallationFuture> pendingFMs =
new ConcurrentHashMap<Long, InstallationFuture>();
+ private final Map<Dpid, FlowStatsCollector> collectors = Maps.newHashMap();
+
/**
* Creates an OpenFlow host provider.
*/
@@ -119,6 +121,14 @@
providerService = providerRegistry.register(this);
controller.addListener(listener);
controller.addEventListener(listener);
+
+ for (OpenFlowSwitch sw : controller.getSwitches()) {
+ FlowStatsCollector fsc = new FlowStatsCollector(sw, POLL_INTERVAL);
+ fsc.start();
+ collectors.put(new Dpid(sw.getId()), fsc);
+ }
+
+
log.info("Started");
}
@@ -217,7 +227,7 @@
private class InternalFlowProvider
implements OpenFlowSwitchListener, OpenFlowEventListener {
- private final Map<Dpid, FlowStatsCollector> collectors = Maps.newHashMap();
+
private final Multimap<DeviceId, FlowEntry> completeEntries =
ArrayListMultimap.create();
@@ -237,6 +247,10 @@
}
@Override
+ public void switchChanged(Dpid dpid) {
+ }
+
+ @Override
public void portChanged(Dpid dpid, OFPortStatus status) {
//TODO: Decide whether to evict flows internal store.
}
@@ -317,6 +331,7 @@
}
return false;
}
+
}
private class InstallationFuture implements ListenableFuture<CompletedBatchOperation> {
diff --git a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
index 7f16eaa..c3f5a68 100644
--- a/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
+++ b/providers/openflow/link/src/main/java/org/onlab/onos/provider/of/link/impl/OpenFlowLinkProvider.java
@@ -118,6 +118,12 @@
DeviceId.deviceId("of:" + Long.toHexString(dpid.value())));
}
+
+ @Override
+ public void switchChanged(Dpid dpid) {
+ //might not need to do anything since DeviceManager is notified
+ }
+
@Override
public void portChanged(Dpid dpid, OFPortStatus status) {
LinkDiscovery ld = discoverers.get(dpid);
diff --git a/tools/test/cells/single b/tools/test/cells/single
index 6b13756..125477a 100644
--- a/tools/test/cells/single
+++ b/tools/test/cells/single
@@ -7,4 +7,4 @@
export OCN="192.168.56.103"
export OCI="${OC1}"
-export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core-trivial,onos-cli,onos-openflow,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}"
+export ONOS_FEATURES="${ONOS_FEATURES:-webconsole,onos-api,onos-core-trivial,onos-cli,onos-rest,onos-openflow,onos-app-fwd,onos-app-proxyarp,onos-app-tvue}"
diff --git a/tools/test/topos/oe-linear-3.json b/tools/test/topos/oe-linear-3.json
new file mode 100644
index 0000000..9214bd9
--- /dev/null
+++ b/tools/test/topos/oe-linear-3.json
@@ -0,0 +1,45 @@
+{
+ "devices" : [
+ {
+ "uri": "of:0000ffffffffff01", "mac": "ffffffffffff01", "type": "ROADM",
+ "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?",
+ "annotations": { "latitude": 37.6, "longitude": 122.3, "optical.regens": 0 }
+ },
+ {
+ "uri": "of:0000ffffffffff02", "mac": "ffffffffffff02", "type": "ROADM",
+ "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?",
+ "annotations": { "latitude": 37.3, "longitude": 121.9, "optical.regens": 0 }
+ },
+ {
+ "uri": "of:0000ffffffffff03", "mac": "ffffffffffff03", "type": "ROADM",
+ "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?",
+ "annotations": { "latitude": 33.9, "longitude": 118.4, "optical.regens": 2 }
+ },
+
+ {
+ "uri": "of:0000ffffffff0001", "mac": "ffffffffff0003", "type": "SWITCH",
+ "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?",
+ "annotations": { "latitude": 37.6, "longitude": 122.3 }
+ },
+ {
+ "uri": "of:0000ffffffff0002", "mac": "ffffffffff0002", "type": "SWITCH",
+ "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?",
+ "annotations": { "latitude": 37.3, "longitude": 121.9 }
+ }
+ ],
+
+ "links" : [
+ { "src": "of:0000ffffffffff01/10", "dst": "of:0000ffffffffff03/30", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM" } },
+ { "src": "of:0000ffffffffff02/20", "dst": "of:0000ffffffffff03/31", "type": "OPTICAL", "annotations": { "optical.waves": 80, "optical.type": "WDM" } },
+
+ { "src": "of:0000ffffffff0001/10", "dst": "of:0000ffffffffff01/11", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } },
+ { "src": "of:0000ffffffff0002/10", "dst": "of:0000ffffffffff02/21", "type": "OPTICAL", "annotations": { "bandwidth": 100000, "optical.type": "cross-connect" } }
+ ],
+
+ "hosts" : [
+ { "mac": "a0:00:00:00:00:11", "vlan": -1, "location": "of:0000ffffffff0001/11", "ip": "1.2.3.4" },
+ { "mac": "a0:00:00:00:00:12", "vlan": -1, "location": "of:0000ffffffff0001/12", "ip": "1.2.3.5" },
+ { "mac": "a0:00:00:00:00:21", "vlan": -1, "location": "of:0000ffffffff0002/11", "ip": "2.2.3.4" },
+ { "mac": "a0:00:00:00:00:22", "vlan": -1, "location": "of:0000ffffffff0002/12", "ip": "2.2.3.5" }
+ ]
+}
\ No newline at end of file
diff --git a/tools/test/topos/onos.py b/tools/test/topos/onos.py
index ba09f67..9566d00 100755
--- a/tools/test/topos/onos.py
+++ b/tools/test/topos/onos.py
@@ -16,58 +16,95 @@
import time
from sys import argv
from time import sleep
+from sets import Set
class ONOS( Controller ):
- #def __init__( self, name, command='/opt/onos/bin/onos-service', **kwargs ):
- # Controller.__init__( self, name, command=command, inNamespace=True, **kwargs )
- #def __init__( self, name, inNamespace=False, command='controller',
- # cargs='-v ptcp:%d', cdir=None, ip="127.0.0.1",
- # port=6633, protocol='tcp', **params ):
- #self.command = command
- #self.cargs = cargs
- #self.cdir = cdir
- #self.ip = ip
- #self.port = port
- #self.protocol = protocol
- #Node.__init__( self, name, inNamespace=inNamespace,
- # ip=ip, **params )
- #self.checkListening()
-
- ONOS_DIR = '/opt/onos/'
- KARAF_DIR = ONOS_DIR + 'apache-karaf-3.0.1/'
- reactive = True
+ "TODO"
+
+ onosDir = '/opt/onos/'
+
+ def __init__( self, name, onosDir=onosDir,
+ reactive=True, features=[ 'onos-app-tvue' ],
+ **kwargs ):
+ '''TODO'''
+
+ Controller.__init__( self, name, **kwargs )
+ # the following have been done for us:
+ #self.ip = ip ('127.0.0.1')
+ #self.port = port (6633)
+ #self.protocol = protocol ('tcp')
+ #self.checkListening()
+
+ self.onosDir = onosDir
+ self.karafDir = onosDir + 'apache-karaf-3.0.1/'
+ self.instanceDir = self.karafDir
+
+ # add default modules
+ # TODO: consider an ordered set
+ self.features = Set([ 'webconsole',
+ 'onos-api',
+ 'onos-cli',
+ 'onos-openflow' ])
+ self.features.update( features )
+ # add reactive forwarding modules
+ if reactive:
+ self.features.update( ['onos-app-fwd',
+ 'onos-app-proxyarp',
+ 'onos-app-mobility' ] )
+ # add the distributed core if we are in a namespace with no trivial core
+ if self.inNamespace and 'onos-core-trivial' not in self.features:
+ self.features.add( 'onos-core' )
+ # if there is no core, add the trivial one
+ if 'onos-core' not in self.features:
+ self.features.add( 'onos-core-trivial' )
+ print self.features
def start( self ):
- # switch to the non-root user because karaf gets upset otherwise
- # TODO we should look into why....
- self.sendCmd( 'sudo su - %s' % self.findUser() )
- self.waiting = False
-
if self.inNamespace:
- self.cmd( self.KARAF_DIR + 'bin/instance create %s' % self.name )
- src = self.KARAF_DIR + 'etc/org.apache.karaf.features.cfg'
- dst = self.KARAF_DIR + 'instances/%s/etc/org.apache.karaf.features.cfg' % self.name
- self.cmd( 'cp %s %s' % (src, dst) )
- self.updateProperties( dst )
- self.cmd( self.KARAF_DIR + 'bin/instance start %s' % self.name )
+ instanceOpts = ( '-furl mvn:org.onlab.onos/onos-features/1.0.0-SNAPSHOT/xml/features '
+ '-s 8101' )
+ self.userCmd( self.karafDir + 'bin/instance create %s %s' % ( instanceOpts, self.name ) )
+ self.instanceDir = self.karafDir + 'instances/%s/' % self.name
else:
# we are running in the root namespace, so let's use the root instance
- self.cmd( 'rm -rf '+ self.KARAF_DIR + 'data/' )
- filename = self.KARAF_DIR + 'etc/org.apache.karaf.features.cfg'
- self.updateProperties( filename )
- self.cmd( self.KARAF_DIR + 'bin/start' )
+ # clean up the data directory
+ #self.userCmd( 'rm -rf '+ self.karafDir + 'data/' )
+ pass
+ self.userCmd( 'rm -rf '+ self.instanceDir + 'data/' )
+
+ # Update etc/org.apache.karaf.features.cfg
+ self.updateFeatures()
+
+ # TODO 2. Update etc/hazelcast.xml : interface lines
+ #cp etc/hazelcast.xml instances/c1/etc/
+ self.updateHazelcast()
+
+ # TODO 3. Update etc/system.properties : onos.ip
+ # TODO 4. Update config/cluster.json : with all nodes
+
+ # start onos
+ self.userCmd( self.instanceDir + 'bin/start' )
#TODO we should wait for startup...
def stop( self ):
- if self.inNamespace:
- self.cmd( '/opt/onos/apache-karaf-3.0.1/bin/instance stop %s' % self.name )
- self.cmd( '/opt/onos/apache-karaf-3.0.1/bin/instance destroy %s' % self.name )
- else:
- self.cmd( self.ONOS_DIR + 'apache-karaf-3.0.1/bin/stop' )
+ self.userCmd( self.instanceDir + 'bin/stop' )
+ #if self.inNamespace:
+ # self.userCmd( self.karafDir + 'bin/instance destroy %s' % self.name )
self.terminate()
- def updateProperties( self, filename ):
+ def updateHazelcast( self ):
+ readfile = self.karafDir + 'etc/hazelcast.xml'
+ writefile = self.instanceDir + 'etc/hazelcast.xml'
+ with open( readfile, 'r' ) as r:
+ with open( writefile, 'w' ) as w:
+ for line in r.readlines():
+ if '<interface>' in line:
+ line = '<interface>' + '192.168.123.*' + '</interface>\n'
+ w.write( line )
+
+ def updateFeatures( self ):
+ filename = self.instanceDir + 'etc/org.apache.karaf.features.cfg'
with open( filename, 'r+' ) as f:
lines = f.readlines()
f.seek(0)
@@ -75,17 +112,25 @@
for line in lines:
#print '?', line,
if 'featuresBoot=' in line:
- line = line.rstrip()
- #print ord(line[-1]), ord(line[-2]), ord(line[-3])
- if self.reactive:
- line += ',onos-app-fwd'
- line += '\n'
+ # parse the features from the line
+ features = line.rstrip().split('=')[1].split(',')
+ # add the features to our features set
+ self.features.update( features )
+ # generate the new features line
+ line = 'featuresBoot=' + ','.join( self.features ) + '\n'
#print '!', line,
f.write( line )
+
@classmethod
def isAvailable( self ):
- return quietRun( 'ls /opt/onos' )
+ return quietRun( 'ls %s' % self.onosDir )
+
+ def userCmd( self, cmd ):
+ # switch to the non-root user because karaf gets upset otherwise
+ # because the .m2repo is not stored with root
+ cmd = 'sudo -u %s %s' % ( self.findUser(), cmd )
+ return self.cmd( cmd )
@staticmethod
def findUser():
@@ -111,7 +156,7 @@
# Connect everything to a single switch
cs0 = self.addSwitch( 'cs0' )
# Add hosts which will serve as data network controllers
- for i in range( 0, n ):
+ for i in range( 1, n+1 ):
c = self.addHost( 'c%s' % i, cls=dataController,
inNamespace=True )
self.addLink( c, cs0 )
@@ -122,7 +167,7 @@
class ONOSCluster( Controller ):
# TODO
- n = 4
+ n = 3
def start( self ):
ctopo = ControlNetwork( n=self.n, dataController=ONOS )
@@ -137,6 +182,9 @@
host.start()
def stop( self ):
+ for host in self.cnet.hosts:
+ if isinstance( host, Controller ):
+ host.stop()
self.cnet.stop()
def clist( self ):
@@ -158,10 +206,11 @@
if __name__ == '__main__':
# Simple test for ONOS() controller class
- setLogLevel( 'info' )
+ setLogLevel( 'info' ) #TODO info
size = 2 if len( argv ) != 2 else int( argv[ 1 ] )
net = Mininet( topo=LinearTopo( size ),
- controller=partial( ONOSCluster, n=4 ),
+ #controller=ONOS,
+ controller=partial( ONOSCluster, n=3 ), #TODO
switch=OVSSwitchONOS )
net.start()
#waitConnected( net.switches )
diff --git a/utils/misc/src/main/java/org/onlab/packet/ChassisId.java b/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
index 3029647..5b48e63 100644
--- a/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
+++ b/utils/misc/src/main/java/org/onlab/packet/ChassisId.java
@@ -32,7 +32,7 @@
* @param value the value to use.
*/
public ChassisId(String value) {
- this.value = Long.valueOf(value);
+ this.value = Long.valueOf(value, 16);
}
/**
diff --git a/utils/nio/src/test/java/org/onlab/nio/IOLoopIntegrationTest.java b/utils/nio/src/test/java/org/onlab/nio/IOLoopIntegrationTest.java
index e61f6e2..55a0130 100644
--- a/utils/nio/src/test/java/org/onlab/nio/IOLoopIntegrationTest.java
+++ b/utils/nio/src/test/java/org/onlab/nio/IOLoopIntegrationTest.java
@@ -1,6 +1,7 @@
package org.onlab.nio;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import java.net.InetAddress;
@@ -33,7 +34,8 @@
}
}
-
+ // TODO: this test can not pass in some environments, need to be improved
+ @Ignore
@Test
public void basic() throws Exception {
runTest(MILLION, MESSAGE_LENGTH, TIMEOUT);
diff --git a/utils/osgi/src/main/java/org/onlab/osgi/TestServiceDirectory.java b/utils/osgi/src/main/java/org/onlab/osgi/TestServiceDirectory.java
index 2915d4b..7fecbc3 100644
--- a/utils/osgi/src/main/java/org/onlab/osgi/TestServiceDirectory.java
+++ b/utils/osgi/src/main/java/org/onlab/osgi/TestServiceDirectory.java
@@ -6,6 +6,7 @@
/**
* Service directory implementation suitable for testing.
*/
+@SuppressWarnings("unchecked")
public class TestServiceDirectory implements ServiceDirectory {
private ClassToInstanceMap<Object> services = MutableClassToInstanceMap.create();
diff --git a/web/api/pom.xml b/web/api/pom.xml
index 11d90cc..da8fd1c 100644
--- a/web/api/pom.xml
+++ b/web/api/pom.xml
@@ -23,13 +23,6 @@
<version>1.0.0-SNAPSHOT</version>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- <version>17.0</version>
- <scope>test</scope>
- </dependency>
-
</dependencies>
<properties>
diff --git a/web/api/src/main/java/org/onlab/onos/rest/ConfigProvider.java b/web/api/src/main/java/org/onlab/onos/rest/ConfigProvider.java
new file mode 100644
index 0000000..3120511
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/ConfigProvider.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultAnnotations;
+import org.onlab.onos.net.Device;
+import org.onlab.onos.net.Host;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.HostLocation;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.MastershipRole;
+import org.onlab.onos.net.SparseAnnotations;
+import org.onlab.onos.net.device.DefaultDeviceDescription;
+import org.onlab.onos.net.device.DeviceDescription;
+import org.onlab.onos.net.device.DeviceProvider;
+import org.onlab.onos.net.device.DeviceProviderRegistry;
+import org.onlab.onos.net.device.DeviceProviderService;
+import org.onlab.onos.net.host.DefaultHostDescription;
+import org.onlab.onos.net.host.HostProvider;
+import org.onlab.onos.net.host.HostProviderRegistry;
+import org.onlab.onos.net.host.HostProviderService;
+import org.onlab.onos.net.link.DefaultLinkDescription;
+import org.onlab.onos.net.link.LinkProvider;
+import org.onlab.onos.net.link.LinkProviderRegistry;
+import org.onlab.onos.net.link.LinkProviderService;
+import org.onlab.onos.net.provider.ProviderId;
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import java.net.URI;
+import java.util.Iterator;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+
+/**
+ * Provider of devices and links parsed from a JSON configuration structure.
+ */
+class ConfigProvider implements DeviceProvider, LinkProvider, HostProvider {
+
+ private static final ProviderId PID =
+ new ProviderId("cfg", "org.onlab.onos.rest", true);
+
+ private final JsonNode cfg;
+ private final DeviceProviderRegistry deviceProviderRegistry;
+ private final LinkProviderRegistry linkProviderRegistry;
+ private final HostProviderRegistry hostProviderRegistry;
+
+ /**
+ * Creates a new configuration provider.
+ *
+ * @param cfg JSON configuration
+ * @param deviceProviderRegistry device provider registry
+ * @param linkProviderRegistry link provider registry
+ * @param hostProviderRegistry host provider registry
+ */
+ ConfigProvider(JsonNode cfg,
+ DeviceProviderRegistry deviceProviderRegistry,
+ LinkProviderRegistry linkProviderRegistry,
+ HostProviderRegistry hostProviderRegistry) {
+ this.cfg = checkNotNull(cfg, "Configuration cannot be null");
+ this.deviceProviderRegistry = checkNotNull(deviceProviderRegistry, "Device provider registry cannot be null");
+ this.linkProviderRegistry = checkNotNull(linkProviderRegistry, "Link provider registry cannot be null");
+ this.hostProviderRegistry = checkNotNull(hostProviderRegistry, "Host provider registry cannot be null");
+ }
+
+ /**
+ * Parses the given JSON and provides links as configured.
+ */
+ void parse() {
+ parseDevices();
+ parseLinks();
+ parseHosts();
+ }
+
+ // Parses the given JSON and provides devices.
+ private void parseDevices() {
+ try {
+ DeviceProviderService dps = deviceProviderRegistry.register(this);
+ JsonNode nodes = cfg.get("devices");
+ if (nodes != null) {
+ for (JsonNode node : nodes) {
+ parseDevice(dps, node);
+ }
+ }
+ } finally {
+ deviceProviderRegistry.unregister(this);
+ }
+ }
+
+ // Parses the given node with device data and supplies the device.
+ private void parseDevice(DeviceProviderService dps, JsonNode node) {
+ URI uri = URI.create(get(node, "uri"));
+ Device.Type type = Device.Type.valueOf(get(node, "type"));
+ String mfr = get(node, "mfr");
+ String hw = get(node, "hw");
+ String sw = get(node, "sw");
+ String serial = get(node, "serial");
+ ChassisId cid = new ChassisId(get(node, "mac"));
+ SparseAnnotations annotations = annotations(node.get("annotations"));
+
+ DeviceDescription desc =
+ new DefaultDeviceDescription(uri, type, mfr, hw, sw, serial,
+ cid, annotations);
+ dps.deviceConnected(deviceId(uri), desc);
+ }
+
+ // Parses the given JSON and provides links as configured.
+ private void parseLinks() {
+ try {
+ LinkProviderService lps = linkProviderRegistry.register(this);
+ JsonNode nodes = cfg.get("links");
+ if (nodes != null) {
+ for (JsonNode node : nodes) {
+ parseLink(lps, node, false);
+ if (!node.has("halfplex")) {
+ parseLink(lps, node, true);
+ }
+ }
+ }
+ } finally {
+ linkProviderRegistry.unregister(this);
+ }
+ }
+
+ // Parses the given node with link data and supplies the link.
+ private void parseLink(LinkProviderService lps, JsonNode node, boolean reverse) {
+ ConnectPoint src = connectPoint(get(node, "src"));
+ ConnectPoint dst = connectPoint(get(node, "dst"));
+ Link.Type type = Link.Type.valueOf(get(node, "type"));
+ SparseAnnotations annotations = annotations(node.get("annotations"));
+
+ DefaultLinkDescription desc = reverse ?
+ new DefaultLinkDescription(dst, src, type, annotations) :
+ new DefaultLinkDescription(src, dst, type, annotations);
+ lps.linkDetected(desc);
+ }
+
+ // Parses the given JSON and provides hosts as configured.
+ private void parseHosts() {
+ try {
+ HostProviderService hps = hostProviderRegistry.register(this);
+ JsonNode nodes = cfg.get("hosts");
+ if (nodes != null) {
+ for (JsonNode node : nodes) {
+ parseHost(hps, node);
+ }
+ }
+ } finally {
+ hostProviderRegistry.unregister(this);
+ }
+ }
+
+ // Parses the given node with host data and supplies the host.
+ private void parseHost(HostProviderService hps, JsonNode node) {
+ MacAddress mac = MacAddress.valueOf(get(node, "mac"));
+ VlanId vlanId = VlanId.vlanId(node.get("vlan").shortValue());
+ HostId hostId = HostId.hostId(mac, vlanId);
+ SparseAnnotations annotations = annotations(node.get("annotations"));
+ HostLocation location = new HostLocation(connectPoint(get(node, "location")), 0);
+ IpPrefix ip = IpPrefix.valueOf(get(node, "ip"));
+
+ DefaultHostDescription desc =
+ new DefaultHostDescription(mac, vlanId, location, ip, annotations);
+ hps.hostDetected(hostId, desc);
+ }
+
+ // Produces set of annotations from the given JSON node.
+ private SparseAnnotations annotations(JsonNode node) {
+ if (node == null) {
+ return null;
+ }
+
+ DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
+ Iterator<String> it = node.fieldNames();
+ while (it.hasNext()) {
+ String k = it.next();
+ builder.set(k, node.get(k).asText());
+ }
+ return builder.build();
+ }
+
+ // Produces a connection point from the specified uri/port text.
+ private ConnectPoint connectPoint(String text) {
+ int i = text.lastIndexOf("/");
+ return new ConnectPoint(deviceId(text.substring(0, i)),
+ portNumber(text.substring(i + 1)));
+ }
+
+ // Returns string form of the named property in the given JSON object.
+ private String get(JsonNode node, String name) {
+ return node.path(name).asText();
+ }
+
+ @Override
+ public void triggerProbe(Device device) {
+ }
+
+ @Override
+ public void roleChanged(Device device, MastershipRole newRole) {
+ }
+
+ @Override
+ public void triggerProbe(Host host) {
+ }
+
+ @Override
+ public ProviderId id() {
+ return PID;
+ }
+}
diff --git a/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java b/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java
new file mode 100644
index 0000000..219abbd
--- /dev/null
+++ b/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.rest;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.onlab.onos.net.device.DeviceProviderRegistry;
+import org.onlab.onos.net.host.HostProviderRegistry;
+import org.onlab.onos.net.link.LinkProviderRegistry;
+import org.onlab.rest.BaseResource;
+
+import javax.ws.rs.Consumes;
+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;
+
+/**
+ * Resource that acts as an ancillary provider for uploading pre-configured
+ * devices, ports and links.
+ */
+@Path("config")
+public class ConfigResource extends BaseResource {
+
+ @POST
+ @Path("topology")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response topology(InputStream input) throws IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode cfg = mapper.readTree(input);
+ new ConfigProvider(cfg, get(DeviceProviderRegistry.class),
+ get(LinkProviderRegistry.class),
+ get(HostProviderRegistry.class)).parse();
+ return Response.ok(mapper.createObjectNode().toString()).build();
+ }
+
+}
diff --git a/web/api/src/test/java/org/onlab/onos/rest/topo.json b/web/api/src/test/java/org/onlab/onos/rest/topo.json
new file mode 100644
index 0000000..cdef976
--- /dev/null
+++ b/web/api/src/test/java/org/onlab/onos/rest/topo.json
@@ -0,0 +1,19 @@
+{
+ "devices" : [
+ {
+ "uri": "of:00000000000001", "type": "ROADM", "mfr": "Foo, Inc.", "hw": "Alpha", "sw": "1.2.3",
+ "serial": "ab321", "mac": "00000000000001", "annotations": {"foo": "bar"},
+ "ports": []
+ },
+ {
+ "uri": "of:00000000000002", "type": "ROADM", "mfr": "Foo, Inc.", "hw": "Alpha", "sw": "1.2.3",
+ "serial": "ab456", "mac": "00000000000002", "annotations": {"foo": "bar"},
+ "ports": []
+ }
+ ],
+
+ "links" : [
+ { "src": "of:00000000000001/1", "dst": "of:00000000000002/1", "type": "OPTICAL" },
+ { "src": "of:00000000000002/1", "dst": "of:00000000000001/1", "type": "OPTICAL" }
+ ]
+}
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/geometry.js b/web/gui/src/main/webapp/geometry.js
new file mode 100644
index 0000000..71533cb
--- /dev/null
+++ b/web/gui/src/main/webapp/geometry.js
@@ -0,0 +1,107 @@
+/*
+ Geometry library - based on work by Mike Bostock.
+ */
+
+(function() {
+
+ if (typeof geo == 'undefined') {
+ geo = {};
+ }
+
+ var tolerance = 1e-10;
+
+ function eq(a, b) {
+ return (Math.abs(a - b) < tolerance);
+ }
+
+ function gt(a, b) {
+ return (a - b > -tolerance);
+ }
+
+ function lt(a, b) {
+ return gt(b, a);
+ }
+
+ geo.eq = eq;
+ geo.gt = gt;
+ geo.lt = lt;
+
+ geo.LineSegment = function(x1, y1, x2, y2) {
+ this.x1 = x1;
+ this.y1 = y1;
+ this.x2 = x2;
+ this.y2 = y2;
+
+ // Ax + By = C
+ this.a = y2 - y1;
+ this.b = x1 - x2;
+ this.c = x1 * this.a + y1 * this.b;
+
+ if (eq(this.a, 0) && eq(this.b, 0)) {
+ throw new Error(
+ 'Cannot construct a LineSegment with two equal endpoints.');
+ }
+ };
+
+ geo.LineSegment.prototype.intersect = function(that) {
+ var d = (this.x1 - this.x2) * (that.y1 - that.y2) -
+ (this.y1 - this.y2) * (that.x1 - that.x2);
+
+ if (eq(d, 0)) {
+ // The two lines are parallel or very close.
+ return {
+ x : NaN,
+ y : NaN
+ };
+ }
+
+ var t1 = this.x1 * this.y2 - this.y1 * this.x2,
+ t2 = that.x1 * that.y2 - that.y1 * that.x2,
+ x = (t1 * (that.x1 - that.x2) - t2 * (this.x1 - this.x2)) / d,
+ y = (t1 * (that.y1 - that.y2) - t2 * (this.y1 - this.y2)) / d,
+ in1 = (gt(x, Math.min(this.x1, this.x2)) && lt(x, Math.max(this.x1, this.x2)) &&
+ gt(y, Math.min(this.y1, this.y2)) && lt(y, Math.max(this.y1, this.y2))),
+ in2 = (gt(x, Math.min(that.x1, that.x2)) && lt(x, Math.max(that.x1, that.x2)) &&
+ gt(y, Math.min(that.y1, that.y2)) && lt(y, Math.max(that.y1, that.y2)));
+
+ return {
+ x : x,
+ y : y,
+ in1 : in1,
+ in2 : in2
+ };
+ };
+
+ geo.LineSegment.prototype.x = function(y) {
+ // x = (C - By) / a;
+ if (this.a) {
+ return (this.c - this.b * y) / this.a;
+ } else {
+ // a == 0 -> horizontal line
+ return NaN;
+ }
+ };
+
+ geo.LineSegment.prototype.y = function(x) {
+ // y = (C - Ax) / b;
+ if (this.b) {
+ return (this.c - this.a * x) / this.b;
+ } else {
+ // b == 0 -> vertical line
+ return NaN;
+ }
+ };
+
+ geo.LineSegment.prototype.length = function() {
+ return Math.sqrt(
+ (this.y2 - this.y1) * (this.y2 - this.y1) +
+ (this.x2 - this.x1) * (this.x2 - this.x1));
+ };
+
+ geo.LineSegment.prototype.offset = function(x, y) {
+ return new geo.LineSegment(
+ this.x1 + x, this.y1 + y,
+ this.x2 + x, this.y2 + y);
+ };
+
+})();
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 3b0d290..ebf25c5 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -14,6 +14,7 @@
<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="onos.css">
+ <script src="geometry.js"></script>
<script src="onosui.js"></script>
</head>
@@ -32,20 +33,20 @@
<div id="view"></div>
</div>
- // Initialize the UI...
+ <!-- Initialize the UI...-->
<script type="text/javascript">
var ONOS = $.onos({note: "config, if needed"});
</script>
- // include module files
- // + mast.js
- // + nav.js
- // + .... application views
+ <!-- include module files-->
+ <!-- + mast.js-->
+ <!-- + nav.js-->
+ <!-- + .... application views-->
- // for now, we are just bootstrapping the network visualization
+ <!-- for now, we are just bootstrapping the network visualization-->
<script src="network.js" type="text/javascript"></script>
- // finally, build the UI
+ <!-- finally, build the UI-->
<script type="text/javascript">
$(ONOS.buildUi);
</script>
diff --git a/web/gui/src/main/webapp/network.js b/web/gui/src/main/webapp/network.js
index 81105dc..80d11b7 100644
--- a/web/gui/src/main/webapp/network.js
+++ b/web/gui/src/main/webapp/network.js
@@ -10,11 +10,16 @@
var api = onos.api;
var config = {
+ layering: false,
jsonUrl: 'network.json',
+ iconUrl: {
+ pkt: 'pkt.png',
+ opt: 'opt.png'
+ },
mastHeight: 32,
force: {
- linkDistance: 150,
- linkStrength: 0.9,
+ linkDistance: 240,
+ linkStrength: 0.8,
charge: -400,
ticksWithoutCollisions: 50,
marginLR: 20,
@@ -26,8 +31,9 @@
}
},
labels: {
- padLR: 3,
- padTB: 2,
+ imgPad: 22,
+ padLR: 8,
+ padTB: 6,
marginLR: 3,
marginTB: 2
},
@@ -53,7 +59,7 @@
d3.json(config.jsonUrl, function (err, data) {
if (err) {
alert('Oops! Error reading JSON...\n\n' +
- 'URL: ' + jsonUrl + '\n\n' +
+ 'URL: ' + config.jsonUrl + '\n\n' +
'Error: ' + err.message);
return;
}
@@ -100,7 +106,7 @@
network.data.nodes.forEach(function(n) {
var ypc = yPosConstraintForNode(n),
- ix = Math.random() * 0.8 * nw + 0.1 * nw,
+ ix = Math.random() * 0.6 * nw + 0.2 * nw,
iy = ypc * nh,
node = {
id: n.id,
@@ -153,10 +159,48 @@
.attr('height', view.height)
.append('g')
.attr('transform', config.force.translate());
+// .attr('id', 'zoomable')
+// .call(d3.behavior.zoom().on("zoom", zoomRedraw));
- // TODO: svg.append('defs')
- // TODO: glow/blur stuff
+// function zoomRedraw() {
+// d3.select("#zoomable").attr("transform",
+// "translate(" + d3.event.translate + ")"
+// + " scale(" + d3.event.scale + ")");
+// }
+
+ // TODO: svg.append('defs') for markers?
+
+ // TODO: move glow/blur stuff to util script
+ var glow = network.svg.append('filter')
+ .attr('x', '-50%')
+ .attr('y', '-50%')
+ .attr('width', '200%')
+ .attr('height', '200%')
+ .attr('id', 'blue-glow');
+
+ glow.append('feColorMatrix')
+ .attr('type', 'matrix')
+ .attr('values', '0 0 0 0 0 ' +
+ '0 0 0 0 0 ' +
+ '0 0 0 0 .7 ' +
+ '0 0 0 1 0 ');
+
+ glow.append('feGaussianBlur')
+ .attr('stdDeviation', 3)
+ .attr('result', 'coloredBlur');
+
+ glow.append('feMerge').selectAll('feMergeNode')
+ .data(['coloredBlur', 'SourceGraphic'])
+ .enter().append('feMergeNode')
+ .attr('in', String);
+
// TODO: legend (and auto adjust on scroll)
+// $('#view').on('scroll', function() {
+//
+// });
+
+
+
network.link = network.svg.append('g').selectAll('.link')
.data(network.force.links(), function(d) {return d.id})
@@ -164,37 +208,103 @@
.attr('class', 'link');
// TODO: drag behavior
- // TODO: closest node deselect
+ network.draggedThreshold = d3.scale.linear()
+ .domain([0, 0.1])
+ .range([5, 20])
+ .clamp(true);
- // TODO: add drag, mouseover, mouseout behaviors
+ function dragged(d) {
+ var threshold = network.draggedThreshold(network.force.alpha()),
+ dx = d.oldX - d.px,
+ dy = d.oldY - d.py;
+ if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
+ d.dragged = true;
+ }
+ return d.dragged;
+ }
+
+ network.drag = d3.behavior.drag()
+ .origin(function(d) { return d; })
+ .on('dragstart', function(d) {
+ d.oldX = d.x;
+ d.oldY = d.y;
+ d.dragged = false;
+ d.fixed |= 2;
+ })
+ .on('drag', function(d) {
+ d.px = d3.event.x;
+ d.py = d3.event.y;
+ if (dragged(d)) {
+ if (!network.force.alpha()) {
+ network.force.alpha(.025);
+ }
+ }
+ })
+ .on('dragend', function(d) {
+ if (!dragged(d)) {
+ selectObject(d, this);
+ }
+ d.fixed &= ~6;
+ });
+
+ $('#view').on('click', function(e) {
+ if (!$(e.target).closest('.node').length) {
+ deselectObject();
+ }
+ });
+
+
network.node = network.svg.selectAll('.node')
.data(network.force.nodes(), function(d) {return d.id})
.enter().append('g')
- .attr('class', 'node')
+ .attr('class', function(d) {
+ return 'node ' + d.type;
+ })
.attr('transform', function(d) {
return translate(d.x, d.y);
})
- // .call(network.drag)
- .on('mouseover', function(d) {})
- .on('mouseout', function(d) {});
+ .call(network.drag)
+ .on('mouseover', function(d) {
+ if (!selected.obj) {
+ if (network.mouseoutTimeout) {
+ clearTimeout(network.mouseoutTimeout);
+ network.mouseoutTimeout = null;
+ }
+ highlightObject(d);
+ }
+ })
+ .on('mouseout', function(d) {
+ if (!selected.obj) {
+ if (network.mouseoutTimeout) {
+ clearTimeout(network.mouseoutTimeout);
+ network.mouseoutTimeout = null;
+ }
+ network.mouseoutTimeout = setTimeout(function() {
+ highlightObject(null);
+ }, 160);
+ }
+ });
- // TODO: augment stroke and fill functions
network.nodeRect = network.node.append('rect')
- // TODO: css for node rects
.attr('rx', 5)
.attr('ry', 5)
- .attr('stroke', function(d) { return '#000'})
- .attr('fill', function(d) { return '#ddf'})
- .attr('width', 60)
- .attr('height', 24);
+ .attr('width', 126)
+ .attr('height', 40);
network.node.each(function(d) {
var node = d3.select(this),
- rect = node.select('rect');
- var text = node.append('text')
- .text(d.id)
- .attr('dx', '1em')
- .attr('dy', '2.1em');
+ rect = node.select('rect'),
+ img = node.append('svg:image')
+ .attr('x', -16)
+ .attr('y', -16)
+ .attr('width', 32)
+ .attr('height', 32)
+ .attr('xlink:href', iconUrl(d)),
+ text = node.append('text')
+ .text(d.id)
+ .attr('dy', '1.1em'),
+ dummy;
+
});
// this function is scheduled to happen soon after the given thread ends
@@ -207,12 +317,64 @@
first = true;
// NOTE: probably unnecessary code if we only have one line.
+ text.each(function() {
+ var box = this.getBBox();
+ if (first || box.x < bounds.x1) {
+ bounds.x1 = box.x;
+ }
+ if (first || box.y < bounds.y1) {
+ bounds.y1 = box.y;
+ }
+ if (first || box.x + box.width < bounds.x2) {
+ bounds.x2 = box.x + box.width;
+ }
+ if (first || box.y + box.height < bounds.y2) {
+ bounds.y2 = box.y + box.height;
+ }
+ first = false;
+ }).attr('text-anchor', 'middle');
+
+ var lab = config.labels,
+ oldWidth = bounds.x2 - bounds.x1;
+
+ bounds.x1 -= oldWidth / 2;
+ bounds.x2 -= oldWidth / 2;
+
+ bounds.x1 -= (lab.padLR + lab.imgPad);
+ bounds.y1 -= lab.padTB;
+ bounds.x2 += lab.padLR;
+ bounds.y2 += lab.padTB;
+
+ node.select('rect')
+ .attr('x', bounds.x1)
+ .attr('y', bounds.y1)
+ .attr('width', bounds.x2 - bounds.x1)
+ .attr('height', bounds.y2 - bounds.y1);
+
+ node.select('image')
+ .attr('x', bounds.x1);
+
+ d.extent = {
+ left: bounds.x1 - lab.marginLR,
+ right: bounds.x2 + lab.marginLR,
+ top: bounds.y1 - lab.marginTB,
+ bottom: bounds.y2 + lab.marginTB
+ };
+
+ d.edge = {
+ left : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2),
+ right : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2),
+ top : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1),
+ bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2)
+ };
+
+ // ====
});
network.numTicks = 0;
network.preventCollisions = false;
network.force.start();
- for (var i = 0; i < config.ticksWithoutCollisions; i++) {
+ for (var i = 0; i < config.force.ticksWithoutCollisions; i++) {
network.force.tick();
}
network.preventCollisions = true;
@@ -221,22 +383,78 @@
}
+ function iconUrl(d) {
+ return config.iconUrl[d.type];
+ }
+
function translate(x, y) {
return 'translate(' + x + ',' + y + ')';
}
+ function preventCollisions() {
+ var quadtree = d3.geom.quadtree(network.nodes);
+
+ network.nodes.forEach(function(n) {
+ var nx1 = n.x + n.extent.left,
+ nx2 = n.x + n.extent.right,
+ ny1 = n.y + n.extent.top,
+ ny2 = n.y + n.extent.bottom;
+
+ quadtree.visit(function(quad, x1, y1, x2, y2) {
+ if (quad.point && quad.point !== n) {
+ // check if the rectangles intersect
+ var p = quad.point,
+ px1 = p.x + p.extent.left,
+ px2 = p.x + p.extent.right,
+ py1 = p.y + p.extent.top,
+ py2 = p.y + p.extent.bottom,
+ ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2);
+ if (ix) {
+ var xa1 = nx2 - px1, // shift n left , p right
+ xa2 = px2 - nx1, // shift n right, p left
+ ya1 = ny2 - py1, // shift n up , p down
+ ya2 = py2 - ny1, // shift n down , p up
+ adj = Math.min(xa1, xa2, ya1, ya2);
+
+ if (adj == xa1) {
+ n.x -= adj / 2;
+ p.x += adj / 2;
+ } else if (adj == xa2) {
+ n.x += adj / 2;
+ p.x -= adj / 2;
+ } else if (adj == ya1) {
+ n.y -= adj / 2;
+ p.y += adj / 2;
+ } else if (adj == ya2) {
+ n.y += adj / 2;
+ p.y -= adj / 2;
+ }
+ }
+ return ix;
+ }
+ });
+
+ });
+ }
function tick(e) {
network.numTicks++;
- // adjust the y-coord of each node, based on y-pos constraints
-// network.nodes.forEach(function (n) {
-// var z = e.alpha * n.constraint.weight;
-// if (!isNaN(n.constraint.y)) {
-// n.y = (n.constraint.y * z + n.y * (1 - z));
-// }
-// });
+ if (config.layering) {
+ // adjust the y-coord of each node, based on y-pos constraints
+ network.nodes.forEach(function (n) {
+ var z = e.alpha * n.constraint.weight;
+ if (!isNaN(n.constraint.y)) {
+ n.y = (n.constraint.y * z + n.y * (1 - z));
+ }
+ });
+ }
+ if (network.preventCollisions) {
+ preventCollisions();
+ }
+
+ // TODO: use intersection technique for source end of link also
network.link
.attr('x1', function(d) {
return d.source.x;
@@ -244,11 +462,24 @@
.attr('y1', function(d) {
return d.source.y;
})
- .attr('x2', function(d) {
- return d.target.x;
- })
- .attr('y2', function(d) {
- return d.target.y;
+ .each(function(d) {
+ var x = d.target.x,
+ y = d.target.y,
+ line = new geo.LineSegment(d.source.x, d.source.y, x, y);
+
+ for (var e in d.target.edge) {
+ var ix = line.intersect(d.target.edge[e].offset(x,y));
+ if (ix.in1 && ix.in2) {
+ x = ix.x;
+ y = ix.y;
+ break;
+ }
+ }
+
+ d3.select(this)
+ .attr('x2', x)
+ .attr('y2', y);
+
});
network.node
diff --git a/web/gui/src/main/webapp/network.json b/web/gui/src/main/webapp/network.json
index 74a276a..b4f1b6e 100644
--- a/web/gui/src/main/webapp/network.json
+++ b/web/gui/src/main/webapp/network.json
@@ -7,50 +7,50 @@
},
"nodes": [
{
- "id": "switch-1",
+ "id": "sample1",
"type": "opt",
"status": "good"
},
{
- "id": "switch-2",
+ "id": "00:00:00:00:00:00:00:02",
"type": "opt",
"status": "good"
},
{
- "id": "switch-3",
+ "id": "00:00:00:00:00:00:00:03",
"type": "opt",
"status": "good"
},
{
- "id": "switch-4",
+ "id": "00:00:00:00:00:00:00:04",
"type": "opt",
"status": "good"
},
{
- "id": "switch-11",
+ "id": "00:00:00:00:00:00:00:11",
"type": "pkt",
"status": "good"
},
{
- "id": "switch-12",
+ "id": "00:00:00:00:00:00:00:12",
"type": "pkt",
"status": "good"
},
{
- "id": "switch-13",
+ "id": "00:00:00:00:00:00:00:13",
"type": "pkt",
"status": "good"
}
],
"links": [
- { "src": "switch-1", "dst": "switch-2" },
- { "src": "switch-1", "dst": "switch-3" },
- { "src": "switch-1", "dst": "switch-4" },
- { "src": "switch-2", "dst": "switch-3" },
- { "src": "switch-2", "dst": "switch-4" },
- { "src": "switch-3", "dst": "switch-4" },
- { "src": "switch-13", "dst": "switch-3" },
- { "src": "switch-12", "dst": "switch-2" },
- { "src": "switch-11", "dst": "switch-1" }
+ { "src": "sample1", "dst": "00:00:00:00:00:00:00:02" },
+ { "src": "sample1", "dst": "00:00:00:00:00:00:00:03" },
+ { "src": "sample1", "dst": "00:00:00:00:00:00:00:04" },
+ { "src": "00:00:00:00:00:00:00:02", "dst": "00:00:00:00:00:00:00:03" },
+ { "src": "00:00:00:00:00:00:00:02", "dst": "00:00:00:00:00:00:00:04" },
+ { "src": "00:00:00:00:00:00:00:03", "dst": "00:00:00:00:00:00:00:04" },
+ { "src": "00:00:00:00:00:00:00:13", "dst": "00:00:00:00:00:00:00:03" },
+ { "src": "00:00:00:00:00:00:00:12", "dst": "00:00:00:00:00:00:00:02" },
+ { "src": "00:00:00:00:00:00:00:11", "dst": "sample1" }
]
}
diff --git a/web/gui/src/main/webapp/onos.css b/web/gui/src/main/webapp/onos.css
index 328e109..340ae79 100644
--- a/web/gui/src/main/webapp/onos.css
+++ b/web/gui/src/main/webapp/onos.css
@@ -13,7 +13,7 @@
*/
span.title {
- color: red;
+ color: darkblue;
font-size: 16pt;
font-style: italic;
}
@@ -30,7 +30,7 @@
* === DEBUGGING ======
*/
svg {
- border: 1px dashed red;
+ /*border: 1px dashed red;*/
}
@@ -64,36 +64,47 @@
-moz-transition: opacity 250ms;
}
-.node text {
- fill: #000;
+/*differentiate between packet and optical nodes*/
+svg .node.pkt rect {
+ fill: #77a;
+}
+
+svg .node.opt rect {
+ fill: #7a7;
+}
+
+svg .node text {
+ fill: white;
font: 10px sans-serif;
pointer-events: none;
}
-.node.selected rect {
+svg .node.selected rect {
filter: url(#blue-glow);
}
-.link.inactive,
-.node.inactive rect,
-.node.inactive text {
+svg .link.inactive,
+svg .node.inactive rect,
+svg .node.inactive text,
+svg .node.inactive image {
opacity: .2;
}
-.node.inactive.selected rect,
-.node.inactive.selected text {
+svg .node.inactive.selected rect,
+svg .node.inactive.selected text,
+svg .node.inactive.selected image {
opacity: .6;
}
-.legend {
+svg .legend {
position: fixed;
}
-.legend .category rect {
+svg .legend .category rect {
stroke-width: 1px;
}
-.legend .category text {
+svg .legend .category text {
fill: #000;
font: 10px sans-serif;
pointer-events: none;
@@ -110,15 +121,15 @@
#frame {
width: 100%;
height: 100%;
- background-color: #ffd;
+ background-color: #cdf;
}
#mast {
height: 32px;
- background-color: #dda;
+ background-color: #abe;
vertical-align: baseline;
}
#main {
- background-color: #99b;
+ background-color: #99c;
}
diff --git a/web/gui/src/main/webapp/opt.png b/web/gui/src/main/webapp/opt.png
new file mode 100644
index 0000000..2f2c88e
--- /dev/null
+++ b/web/gui/src/main/webapp/opt.png
Binary files differ
diff --git a/web/gui/src/main/webapp/pkt.png b/web/gui/src/main/webapp/pkt.png
new file mode 100644
index 0000000..1b1d5a3
--- /dev/null
+++ b/web/gui/src/main/webapp/pkt.png
Binary files differ
diff --git a/web/pom.xml b/web/pom.xml
index 3e9f2a0..ebe4d89 100644
--- a/web/pom.xml
+++ b/web/pom.xml
@@ -44,6 +44,11 @@
</dependency>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-servlet</artifactId>
</dependency>
@@ -93,6 +98,7 @@
${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,
@@ -100,6 +106,8 @@
com.sun.jersey.server.impl.container.servlet,
com.fasterxml.jackson.databind,
com.fasterxml.jackson.databind.node,
+ com.google.common.base.*,
+ org.onlab.packet.*,
org.onlab.rest.*,
org.onlab.onos.*
</Import-Package>