Merge remote-tracking branch 'origin/master' into dev/murrelet
diff --git a/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java b/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java
index a2e513e..1e4296a 100644
--- a/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java
+++ b/apps/config/src/main/java/org/onosproject/config/DynamicConfigEvent.java
@@ -67,4 +67,17 @@
public DynamicConfigEvent(Type type, ResourceId path) {
super(type, path);
}
-}
\ No newline at end of file
+
+ /**
+ * Returns ResourceId relative to root node.
+ * (=root will not be part of ResourceId returned)
+ *
+ * @return resource Id
+ */
+ @Override
+ public ResourceId subject() {
+ return super.subject();
+ }
+
+ // TODO add DataNode as event payload
+}
diff --git a/apps/config/src/main/java/org/onosproject/config/DynamicConfigService.java b/apps/config/src/main/java/org/onosproject/config/DynamicConfigService.java
index a5e881a..bb562e8 100644
--- a/apps/config/src/main/java/org/onosproject/config/DynamicConfigService.java
+++ b/apps/config/src/main/java/org/onosproject/config/DynamicConfigService.java
@@ -32,6 +32,12 @@
public interface DynamicConfigService
extends ListenerService<DynamicConfigEvent, DynamicConfigListener> {
+ // FIXME revisit and verify ResourceId documentation.
+ // it is likely that it actually is not expecting absolute ResourceId
+
+ // TODO revisit which path ResourceId these API should accepting.
+ // there is inconsistency, some expect parent, some expect node itself
+
/**
* Creates a new node in the dynamic config store.
* This method would throw an exception if there is a node with the same
@@ -39,11 +45,11 @@
* nodes were not present in the path leading up to the requested node.
* Failure reason will be the error message in the exception.
*
- * @param path data structure with absolute path to the parent
+ * @param parent data structure with absolute path to the parent
* @param node recursive data structure, holding a leaf node or a subtree
* @throws FailedException if the new node could not be created
*/
- void createNode(ResourceId path, DataNode node);
+ void createNode(ResourceId parent, DataNode node);
/**
* Reads the requested node form the dynamic config store.
@@ -76,11 +82,11 @@
* parent nodes in the path were not present.
* Failure reason will be the error message in the exception.
*
- * @param path data structure with absolute path to the parent
+ * @param parent data structure with absolute path to the parent
* @param node recursive data structure, holding a leaf node or a subtree
* @throws FailedException if the update request failed for any reason
*/
- void updateNode(ResourceId path, DataNode node);
+ void updateNode(ResourceId parent, DataNode node);
/**
* Replaces nodes in the dynamic config store.
@@ -89,11 +95,11 @@
* the requested node or any of the parent nodes in the path were not
* present. Failure reason will be the error message in the exception.
*
- * @param path data structure with absolute path to the parent
+ * @param parent data structure with absolute path to the parent
* @param node recursive data structure, holding a leaf node or a subtree
* @throws FailedException if the replace request failed
*/
- void replaceNode(ResourceId path, DataNode node);
+ void replaceNode(ResourceId parent, DataNode node);
/**
* Removes a node from the dynamic config store.
diff --git a/apps/config/src/main/java/org/onosproject/config/FailedException.java b/apps/config/src/main/java/org/onosproject/config/FailedException.java
index 0115c33..80559ed 100644
--- a/apps/config/src/main/java/org/onosproject/config/FailedException.java
+++ b/apps/config/src/main/java/org/onosproject/config/FailedException.java
@@ -23,6 +23,8 @@
@Beta
public class FailedException extends RuntimeException {
+ private static final long serialVersionUID = 266049754055616595L;
+
/**
* Constructs a new runtime exception with no error message.
*/
diff --git a/apps/config/src/main/java/org/onosproject/config/InvalidFilterException.java b/apps/config/src/main/java/org/onosproject/config/InvalidFilterException.java
index fcb73b0..1406b20 100644
--- a/apps/config/src/main/java/org/onosproject/config/InvalidFilterException.java
+++ b/apps/config/src/main/java/org/onosproject/config/InvalidFilterException.java
@@ -23,6 +23,8 @@
@Beta
public class InvalidFilterException extends RuntimeException {
+ private static final long serialVersionUID = 7270417891847061766L;
+
/**
* Constructs a new runtime exception with no error message.
*/
@@ -38,4 +40,6 @@
public InvalidFilterException(String message) {
super(message);
}
+
+ // TODO add constructor with cause
}
\ No newline at end of file
diff --git a/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java b/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
index 0236657..dc93e58 100644
--- a/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
+++ b/apps/config/src/main/java/org/onosproject/config/ResourceIdParser.java
@@ -26,6 +26,8 @@
import org.onosproject.yang.model.NodeKey;
import org.onosproject.yang.model.ResourceId;
+import com.google.common.annotations.Beta;
+
// FIXME add non-trivial examples
/**
* Utilities to work on the ResourceId.
@@ -38,6 +40,7 @@
*
*/
//FIXME add javadocs
+@Beta
public final class ResourceIdParser {
/**
@@ -129,6 +132,7 @@
}
public static String appendNodeKey(String path, NodeKey key) {
+ // FIXME this is not handling root path exception
return (path + EL_SEP + key.schemaId().name() + NM_SEP + key.schemaId().namespace());
}
@@ -199,7 +203,8 @@
while (itr.hasNext()) {
nodeKeyList.add(itr.next());
}
- if (nodeKeyList.get(0).schemaId().name().compareTo("/") == 0) {
+ // exception for dealing with root
+ if (nodeKeyList.get(0).schemaId().name().equals("/")) {
nodeKeyList.remove(0);
}
for (NodeKey key : nodeKeyList) {
diff --git a/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java b/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
index 7c73922..97731a6 100644
--- a/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
+++ b/apps/config/src/main/java/org/onosproject/config/impl/DistributedDynamicConfigStore.java
@@ -29,6 +29,7 @@
import org.onosproject.config.FailedException;
import org.onosproject.config.Filter;
import org.onosproject.config.ResourceIdParser;
+import org.onosproject.d.config.ResourceIds;
import org.onosproject.store.AbstractStore;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.AsyncDocumentTree;
@@ -57,7 +58,9 @@
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
+import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -75,11 +78,19 @@
public class DistributedDynamicConfigStore
extends AbstractStore<DynamicConfigEvent, DynamicConfigStoreDelegate>
implements DynamicConfigStore {
+
private final Logger log = LoggerFactory.getLogger(getClass());
+
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected StorageService storageService;
+
+ // FIXME transactionally mutate the 2 or consolidate into 1 AsyncDocTree
+ // effectively tree structure only
private AsyncDocumentTree<DataNode.Type> keystore;
+ // TODO Can we pass DocumentPath directly to ConsistentMap?
+ // Map<DocumentPath as String, leaf value>
private ConsistentMap<String, LeafNode> objectStore;
+
private final DocumentTreeListener<DataNode.Type> klistener = new InternalDocTreeListener();
private final MapEventListener<String, LeafNode> olistener = new InternalMapListener();
@@ -87,7 +98,7 @@
public void activateStore() {
KryoNamespace.Builder kryoBuilder = new KryoNamespace.Builder()
.register(KryoNamespaces.BASIC)
- .register(java.lang.Class.class)
+ .register(Class.class)
.register(DataNode.Type.class)
.register(LeafNode.class)
.register(InnerNode.class)
@@ -98,7 +109,7 @@
.register(ListKey.class)
.register(KeyLeaf.class)
.register(BigInteger.class)
- .register(java.util.LinkedHashMap.class);
+ .register(LinkedHashMap.class);
keystore = storageService.<DataNode.Type>documentTreeBuilder()
.withSerializer(Serializer.using(kryoBuilder.build()))
.withName("config-key-store")
@@ -124,8 +135,10 @@
@Override
public CompletableFuture<Boolean>
- addNode(ResourceId complete, DataNode node) {
- String spath = ResourceIdParser.parseResId(complete);
+ addNode(ResourceId parent, DataNode node) {
+ String spath = ResourceIdParser.parseResId(parent);
+ log.trace(" addNode({}, {})", parent, node);
+ log.trace(" spath={}", spath);
if (spath == null) {
throw new FailedException("Invalid RsourceId, cannot create Node");
}
@@ -134,12 +147,19 @@
throw new FailedException("Node or parent does not exist for " + spath);
}
}
- spath = ResourceIdParser.appendNodeKey(spath, node.key());
- parseNode(spath, node);
+ ResourceId abs = ResourceIds.resourceId(parent, node);
+ //spath = ResourceIdParser.appendNodeKey(spath, node.key());
+ parseNode(ResourceIdParser.parseResId(abs), node);
return CompletableFuture.completedFuture(true);
}
+ // FIXME this is more like addNode
+ /**
+ * @param path pointing to {@code node}
+ * @param node node
+ */
private void parseNode(String path, DataNode node) {
+ log.trace("parseNode({}, {})", path, node);
if (completeVersioned(keystore.get(DocumentPath.from(path))) != null) {
throw new FailedException("Requested node already present in the" +
" store, please use an update method");
@@ -167,12 +187,19 @@
}
}
+ // FIXME this is more like addInnteNode
+ /**
+ * @param path pointing to {@code node}
+ * @param node node
+ */
private void traverseInner(String path, InnerNode node) {
+ log.trace("traverseInner({}, {})", path, node);
addKey(path, node.type());
Map<NodeKey, DataNode> entries = node.childNodes();
if (entries.size() == 0) {
return;
}
+ // FIXME ignoring results
entries.forEach((k, v) -> {
String tempPath;
tempPath = ResourceIdParser.appendNodeKey(path, v.key());
@@ -198,11 +225,19 @@
}
private Boolean addKey(String path, DataNode.Type type) {
- if (completeVersioned(keystore.get(DocumentPath.from(path))) != null) {
- completeVersioned(keystore.set(DocumentPath.from(path), type));
+ log.trace("addKey({}, {})", path, type);
+ DocumentPath dpath = DocumentPath.from(path);
+ log.trace("dpath={}", dpath);
+ // FIXME Not atomic, should probably use create or replace
+ if (completeVersioned(keystore.get(dpath)) != null) {
+ completeVersioned(keystore.set(dpath, type));
+ log.trace("true");
return true;
}
- return complete(keystore.create(DocumentPath.from(path), type));
+ log.trace(" keystore.create({}, {})", dpath, type);
+ Boolean result = complete(keystore.create(dpath, type));
+ log.trace("{}", result);
+ return result;
}
@Override
@@ -489,10 +524,10 @@
throw new FailedException(e.getCause().getMessage());
} catch (ExecutionException e) {
if (e.getCause() instanceof IllegalDocumentModificationException) {
- throw new FailedException("Node or parent doesnot exist or is root or is not a Leaf Node",
+ throw new FailedException("Node or parent does not exist or is root or is not a Leaf Node",
e.getCause());
} else if (e.getCause() instanceof NoSuchDocumentPathException) {
- throw new FailedException("Resource id does not exist", e.getCause());
+ throw new FailedException("ResourceId does not exist", e.getCause());
} else {
throw new FailedException("Datastore operation failed", e.getCause());
}
@@ -500,25 +535,8 @@
}
private <T> T completeVersioned(CompletableFuture<Versioned<T>> future) {
- try {
- if (future.get() != null) {
- return future.get().value();
- } else {
- return null;
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new FailedException(e.getCause().getMessage());
- } catch (ExecutionException e) {
- if (e == null) {
- throw new FailedException("Unknown Exception");
- } else if (e.getCause() instanceof IllegalDocumentModificationException) {
- throw new FailedException("Node or parent does not exist or is root or is not a Leaf Node");
- } else if (e.getCause() instanceof NoSuchDocumentPathException) {
- throw new FailedException("Resource id does not exist");
- } else {
- throw new FailedException("Datastore operation failed");
- }
- }
- }
-}
\ No newline at end of file
+ return Optional.ofNullable(complete(future))
+ .map(Versioned::value)
+ .orElse(null);
+ }
+}
diff --git a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
index 39e53ff..2ef43b7 100644
--- a/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
+++ b/apps/dhcprelay/src/main/java/org/onosproject/dhcprelay/Dhcp4HandlerImpl.java
@@ -722,7 +722,10 @@
udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
} else {
// forward to another dhcp relay
- udpPacket.setDestinationPort(UDP.DHCP_SERVER_PORT);
+ // FIXME: Currently we assume the DHCP comes from a L2 relay with
+ // Option 82, this might not work if DHCP message comes from
+ // L3 relay.
+ udpPacket.setDestinationPort(UDP.DHCP_CLIENT_PORT);
}
udpPacket.setPayload(dhcpPayload);
diff --git a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpensatckRouterWebResource.java b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java
similarity index 98%
rename from apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpensatckRouterWebResource.java
rename to apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java
index 5b769224..eff151d 100644
--- a/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpensatckRouterWebResource.java
+++ b/apps/openstacknetworking/src/main/java/org/onosproject/openstacknetworking/web/OpenstackRouterWebResource.java
@@ -49,7 +49,7 @@
*/
@Path("routers")
-public class OpensatckRouterWebResource extends AbstractWebResource {
+public class OpenstackRouterWebResource extends AbstractWebResource {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String MESSAGE_ROUTER = "Received router %s request";
diff --git a/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml b/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml
index 99a0027..89f5be2 100644
--- a/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml
+++ b/apps/openstacknetworking/src/main/webapp/WEB-INF/web.xml
@@ -29,7 +29,7 @@
org.onosproject.openstacknetworking.web.OpenstackPortWebResource,
org.onosproject.openstacknetworking.web.OpenstackNetworkWebResource,
org.onosproject.openstacknetworking.web.OpenstackSubnetWebResource,
- org.onosproject.openstacknetworking.web.OpensatckRouterWebResource,
+ org.onosproject.openstacknetworking.web.OpenstackRouterWebResource,
org.onosproject.openstacknetworking.web.OpenstackFloatingIpWebResource,
org.onosproject.openstacknetworking.web.OpenstackSecurityGroupWebResource,
org.onosproject.openstacknetworking.web.OpenstackSecurityGroupRuleWebResource
diff --git a/apps/p4-tutorial/icmpdropper/BUCK b/apps/p4-tutorial/icmpdropper/BUCK
new file mode 100644
index 0000000..026827d
--- /dev/null
+++ b/apps/p4-tutorial/icmpdropper/BUCK
@@ -0,0 +1,25 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//apps/p4-tutorial/pipeconf:onos-apps-p4-tutorial-pipeconf',
+]
+
+osgi_jar (
+ deps = COMPILE_DEPS,
+)
+
+BUNDLES = [
+ '//apps/p4-tutorial/icmpdropper:onos-apps-p4-tutorial-icmpdropper',
+]
+
+onos_app (
+ app_name = 'org.onosproject.p4tutorial.icmpdropper',
+ title = 'ICMP Dropper (P4 Tutorial)',
+ category = 'Security',
+ url = 'http://onosproject.org',
+ description = 'Inhibits forwarding of ICMP packets for PI-enabled devices using the ' +
+ 'p4-tutorial-pipeconf.',
+ included_bundles = BUNDLES,
+ required_apps = [
+ 'org.onosproject.p4tutorial.pipeconf',
+ ]
+)
diff --git a/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java
new file mode 100644
index 0000000..8914ee7
--- /dev/null
+++ b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4tutorial.icmpdropper;
+
+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.onosproject.app.ApplicationAdminService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.pi.model.PiPipelineProgrammable;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.p4tutorial.pipeconf.PipeconfFactory;
+import org.slf4j.Logger;
+
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Simple application that drops all ICMP packets.
+ */
+@Component(immediate = true)
+public class IcmpDropper {
+
+ private static final Logger log = getLogger(IcmpDropper.class);
+
+ private static final String APP_NAME = "org.onosproject.p4tutorial.icmpdropper";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private FlowRuleService flowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private ApplicationAdminService appService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private PiPipeconfService piPipeconfService;
+
+ private final DeviceListener deviceListener = new InternalDeviceListener();
+
+ private ApplicationId appId;
+
+ @Activate
+ public void activate() {
+ log.info("Starting...");
+
+ appId = coreService.registerApplication(APP_NAME);
+ // Register listener for handling new devices.
+ deviceService.addListener(deviceListener);
+ // Install rules to existing devices.
+ deviceService.getDevices()
+ .forEach(device -> installDropRule(device.id()));
+
+ log.info("STARTED", appId.id());
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopping...");
+
+ deviceService.removeListener(deviceListener);
+ flowRuleService.removeFlowRulesById(appId);
+
+ log.info("STOPPED");
+ }
+
+ private boolean checkPipeconf(Device device) {
+ if (!device.is(PiPipelineProgrammable.class)) {
+ // Device is not PI-pipeline programmable. Ignore.
+ return false;
+ }
+ if (!piPipeconfService.ofDevice(device.id()).isPresent() ||
+ !piPipeconfService.ofDevice(device.id()).get().equals(PipeconfFactory.PIPECONF_ID)) {
+ log.warn("Device {} has pipeconf {} instead of {}, can't install flow rule for this device",
+ device.id(), piPipeconfService.ofDevice(device.id()).get(), PipeconfFactory.PIPECONF_ID);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void installDropRule(DeviceId deviceId) {
+ PiHeaderFieldId ipv4ProtoFieldId = PiHeaderFieldId.of("ipv4", "protocol");
+ PiActionId dropActionId = PiActionId.of("_drop");
+
+ PiCriterion piCriterion = PiCriterion.builder()
+ .matchExact(ipv4ProtoFieldId, (byte) 0x01)
+ .build();
+ PiAction dropAction = PiAction.builder()
+ .withId(dropActionId)
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .forTable(PiTableId.of("ip_proto_filter_table"))
+ .fromApp(appId)
+ .makePermanent()
+ .withPriority(1000)
+ .withSelector(DefaultTrafficSelector.builder()
+ .matchPi(piCriterion)
+ .build())
+ .withTreatment(
+ DefaultTrafficTreatment.builder()
+ .piTableAction(dropAction)
+ .build())
+ .build();
+
+ log.warn("Installing ICMP drop rule to {}", deviceId);
+
+ flowRuleService.applyFlowRules(flowRule);
+ }
+
+ /**
+ * A listener of device events that installs a rule to drop packet for each new device.
+ */
+ private class InternalDeviceListener implements DeviceListener {
+ @Override
+ public void event(DeviceEvent event) {
+ Device device = event.subject();
+ if (checkPipeconf(device)) {
+ installDropRule(device.id());
+ }
+ }
+
+ @Override
+ public boolean isRelevant(DeviceEvent event) {
+ // Reacts only to new devices.
+ return event.type() == DEVICE_ADDED;
+ }
+ }
+}
diff --git a/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/package-info.java b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/package-info.java
new file mode 100644
index 0000000..5ea1180
--- /dev/null
+++ b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * P4 tutorial application classes.
+ */
+package org.onosproject.p4tutorial.icmpdropper;
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/BUCK b/apps/p4-tutorial/pipeconf/BUCK
new file mode 100644
index 0000000..038736f
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/BUCK
@@ -0,0 +1,29 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:minimal-json',
+ '//incubator/bmv2/model:onos-incubator-bmv2-model',
+ '//drivers/default:onos-drivers-default',
+ '//protocols/p4runtime/api:onos-protocols-p4runtime-api',
+]
+
+osgi_jar (
+ deps = COMPILE_DEPS,
+)
+
+BUNDLES = [
+ '//apps/p4-tutorial/pipeconf:onos-apps-p4-tutorial-pipeconf',
+ '//drivers/default:onos-drivers-default',
+ '//incubator/bmv2/model:onos-incubator-bmv2-model',
+]
+
+onos_app (
+ app_name = 'org.onosproject.p4tutorial.pipeconf',
+ title = 'P4 Tutorial Pipeconf',
+ category = 'Pipeconf',
+ url = 'http://onosproject.org',
+ description = 'Provides pipeconf for the ONOS-P4 Tutorial',
+ included_bundles = BUNDLES,
+ required_apps = [
+ 'org.onosproject.drivers.p4runtime',
+ ]
+)
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java
new file mode 100644
index 0000000..b284eaa
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4tutorial.pipeconf;
+
+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.onosproject.bmv2.model.Bmv2PipelineModelParser;
+import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
+import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.device.PortStatisticsDiscovery;
+import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+
+import java.net.URL;
+
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
+
+/**
+ * Component that produces and registers a pipeconf when loaded.
+ */
+@Component(immediate = true)
+public final class PipeconfFactory {
+
+ public static final PiPipeconfId PIPECONF_ID = new PiPipeconfId("p4-tutorial-pipeconf");
+ private static final URL P4INFO_URL = PipeconfFactory.class.getResource("/main.p4info");
+ private static final URL BMV2_JSON_URL = PipeconfFactory.class.getResource("/main.json");
+ private static final PiPipelineModel PIPELINE_MODEL = Bmv2PipelineModelParser.parse(BMV2_JSON_URL);
+
+ private static final PiPipeconf PIPECONF = DefaultPiPipeconf.builder()
+ .withId(PIPECONF_ID)
+ .withPipelineModel(PIPELINE_MODEL)
+ .addBehaviour(PiPipelineInterpreter.class, PipelineInterpreterImpl.class)
+ .addBehaviour(PortStatisticsDiscovery.class, PortStatisticsDiscoveryImpl.class)
+ // Since main.p4 defines only 1 table, we re-use the existing single-table pipeliner.
+ .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
+ .addExtension(P4_INFO_TEXT, P4INFO_URL)
+ .addExtension(BMV2_JSON, BMV2_JSON_URL)
+ .build();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private PiPipeconfService piPipeconfService;
+
+ @Activate
+ public void activate() {
+ // Registers the pipeconf at component activation.
+ piPipeconfService.register(PIPECONF);
+ }
+
+ @Deactivate
+ public void deactivate() {
+ piPipeconfService.remove(PIPECONF.id());
+ }
+}
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
new file mode 100644
index 0000000..c783236
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4tutorial.pipeconf;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import org.onlab.packet.Ethernet;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiPacketMetadata;
+import org.onosproject.net.pi.runtime.PiPacketMetadataId;
+import org.onosproject.net.pi.runtime.PiPacketOperation;
+import org.onosproject.net.pi.runtime.PiTableId;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onlab.util.ImmutableByteSequence.fit;
+import static org.onosproject.net.PortNumber.CONTROLLER;
+import static org.onosproject.net.PortNumber.FLOOD;
+import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
+import static org.onosproject.net.pi.runtime.PiPacketOperation.Type.PACKET_OUT;
+
+/**
+ * Implementation of a PI interpreter for the main.p4 program.
+ */
+public final class PipelineInterpreterImpl extends AbstractHandlerBehaviour implements PiPipelineInterpreter {
+
+ private static final String TABLE0 = "table0";
+ private static final String IP_PROTO_FILTER_TABLE = "ip_proto_filter_table";
+ private static final String SEND_TO_CPU = "send_to_cpu";
+ private static final String PORT = "port";
+ private static final String DROP = "_drop";
+ private static final String SET_EGRESS_PORT = "set_egress_port";
+ private static final String EGRESS_PORT = "egress_port";
+ private static final String INGRESS_PORT = "ingress_port";
+ private static final String ETHERNET = "ethernet";
+ private static final String STANDARD_METADATA = "standard_metadata";
+ private static final int PORT_FIELD_BITWIDTH = 9;
+
+ private static final PiHeaderFieldId INGRESS_PORT_ID = PiHeaderFieldId.of(STANDARD_METADATA, "ingress_port");
+ private static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of(ETHERNET, "dst_addr");
+ private static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of(ETHERNET, "src_addr");
+ private static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of(ETHERNET, "ether_type");
+ private static final PiTableId TABLE0_ID = PiTableId.of(TABLE0);
+ private static final PiTableId IP_PROTO_FILTER_TABLE_ID = PiTableId.of(IP_PROTO_FILTER_TABLE);
+
+ private static final BiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
+ 0, TABLE0_ID,
+ 1, IP_PROTO_FILTER_TABLE_ID);
+
+ private static final BiMap<Criterion.Type, PiHeaderFieldId> CRITERION_MAP =
+ new ImmutableBiMap.Builder<Criterion.Type, PiHeaderFieldId>()
+ .put(Criterion.Type.IN_PORT, INGRESS_PORT_ID)
+ .put(Criterion.Type.ETH_DST, ETH_DST_ID)
+ .put(Criterion.Type.ETH_SRC, ETH_SRC_ID)
+ .put(Criterion.Type.ETH_TYPE, ETH_TYPE_ID)
+ .build();
+
+ @Override
+ public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId)
+ throws PiInterpreterException {
+
+ if (treatment.allInstructions().size() == 0) {
+ // No instructions means drop for us.
+ return PiAction.builder()
+ .withId(PiActionId.of(DROP))
+ .build();
+ } else if (treatment.allInstructions().size() > 1) {
+ // We understand treatments with only 1 instruction.
+ throw new PiInterpreterException("Treatment has multiple instructions");
+ }
+
+ // Get the first and only instruction.
+ Instruction instruction = treatment.allInstructions().get(0);
+
+ switch (instruction.type()) {
+ case OUTPUT:
+ // We understand only instructions of type OUTPUT.
+ Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) instruction;
+ PortNumber port = outInstruction.port();
+ if (!port.isLogical()) {
+ return PiAction.builder()
+ .withId(PiActionId.of(SET_EGRESS_PORT))
+ .withParameter(new PiActionParam(PiActionParamId.of(PORT), copyFrom(port.toLong())))
+ .build();
+ } else if (port.equals(CONTROLLER)) {
+ return PiAction.builder()
+ .withId(PiActionId.of(SEND_TO_CPU))
+ .build();
+ } else {
+ throw new PiInterpreterException(format("Output on logical port '%s' not supported", port));
+ }
+ default:
+ throw new PiInterpreterException(format("Instruction of type '%s' not supported", instruction.type()));
+ }
+ }
+
+ @Override
+ public Optional<PiCounterId> mapTableCounter(PiTableId piTableId) {
+ return Optional.empty();
+ }
+
+ @Override
+ public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
+ throws PiInterpreterException {
+
+ TrafficTreatment treatment = packet.treatment();
+
+ // We support only packet-out with OUTPUT instructions.
+ List<Instructions.OutputInstruction> outInstructions = treatment.allInstructions().stream()
+ .filter(i -> i.type().equals(OUTPUT))
+ .map(i -> (Instructions.OutputInstruction) i)
+ .collect(toList());
+
+ if (treatment.allInstructions().size() != outInstructions.size()) {
+ // There are other instructions that are not of type OUTPUT.
+ throw new PiInterpreterException("Treatment not supported: " + treatment);
+ }
+
+ ImmutableList.Builder<PiPacketOperation> builder = ImmutableList.builder();
+
+ for (Instructions.OutputInstruction outInst : outInstructions) {
+ if (outInst.port().isLogical() && !outInst.port().equals(FLOOD)) {
+ throw new PiInterpreterException(format("Output on logical port '%s' not supported", outInst.port()));
+ } else if (outInst.port().equals(FLOOD)) {
+ // Since main.p4 does not support flooding, we create a packet operation for each switch port.
+ DeviceService deviceService = handler().get(DeviceService.class);
+ for (Port port : deviceService.getPorts(packet.sendThrough())) {
+ builder.add(createPiPacketOperation(packet.data(), port.number().toLong()));
+ }
+ } else {
+ builder.add(createPiPacketOperation(packet.data(), outInst.port().toLong()));
+ }
+ }
+ return builder.build();
+ }
+
+ @Override
+ public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetIn)
+ throws PiInterpreterException {
+ // We assume that the packet is ethernet, which is fine since default.p4 can deparse only ethernet packets.
+ Ethernet ethPkt = new Ethernet();
+
+ ethPkt.deserialize(packetIn.data().asArray(), 0, packetIn.data().size());
+
+ // Returns the ingress port packet metadata.
+ Optional<PiPacketMetadata> packetMetadata = packetIn.metadatas().stream()
+ .filter(metadata -> metadata.id().name().equals(INGRESS_PORT))
+ .findFirst();
+
+ if (packetMetadata.isPresent()) {
+ ImmutableByteSequence portByteSequence = packetMetadata.get().value();
+ short s = portByteSequence.asReadOnlyBuffer().getShort();
+ ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
+ return new DefaultInboundPacket(receivedFrom, ethPkt, packetIn.data().asReadOnlyBuffer());
+ } else {
+ throw new PiInterpreterException(format(
+ "Missing metadata '%s' in packet-in received from '%s': %s", INGRESS_PORT, deviceId, packetIn));
+ }
+ }
+
+ private PiPacketOperation createPiPacketOperation(ByteBuffer data, long portNumber) throws PiInterpreterException {
+ PiPacketMetadata metadata = createPacketMetadata(portNumber);
+ return PiPacketOperation.builder()
+ .withType(PACKET_OUT)
+ .withData(copyFrom(data))
+ .withMetadatas(ImmutableList.of(metadata))
+ .build();
+ }
+
+ private PiPacketMetadata createPacketMetadata(long portNumber) throws PiInterpreterException {
+ try {
+ return PiPacketMetadata.builder()
+ .withId(PiPacketMetadataId.of(EGRESS_PORT))
+ .withValue(fit(copyFrom(portNumber), PORT_FIELD_BITWIDTH))
+ .build();
+ } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
+ throw new PiInterpreterException(format("Port number %d too big, %s", portNumber, e.getMessage()));
+ }
+ }
+
+ @Override
+ public Optional<PiHeaderFieldId> mapCriterionType(Criterion.Type type) {
+ return Optional.ofNullable(CRITERION_MAP.get(type));
+ }
+
+ @Override
+ public Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId) {
+ return Optional.ofNullable(CRITERION_MAP.inverse().get(headerFieldId));
+ }
+
+ @Override
+ public Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId) {
+ return Optional.ofNullable(TABLE_MAP.get(flowRuleTableId));
+ }
+
+ @Override
+ public Optional<Integer> mapPiTableId(PiTableId piTableId) {
+ return Optional.ofNullable(TABLE_MAP.inverse().get(piTableId));
+ }
+}
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
new file mode 100644
index 0000000..7d715ee1
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.p4tutorial.pipeconf;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DefaultPortStatistics;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortStatistics;
+import org.onosproject.net.device.PortStatisticsDiscovery;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCellId;
+import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.runtime.PiIndirectCounterCellId;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.p4runtime.api.P4RuntimeClient;
+import org.onosproject.p4runtime.api.P4RuntimeController;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import static org.onosproject.net.pi.runtime.PiCounterType.INDIRECT;
+
+/**
+ * Implementation of the PortStatisticsDiscovery behaviour for the main.p4 program. This behaviour works by using a
+ * P4Runtime client to read the values of the ingress/egress port counters defined in the P4 program.
+ */
+public final class PortStatisticsDiscoveryImpl extends AbstractHandlerBehaviour implements PortStatisticsDiscovery {
+
+ private static final Logger log = LoggerFactory.getLogger(PortStatisticsDiscoveryImpl.class);
+
+ private static final PiCounterId INGRESS_COUNTER_ID = PiCounterId.of("igr_port_counter", INDIRECT);
+ private static final PiCounterId EGRESS_COUNTER_ID = PiCounterId.of("egr_port_counter", INDIRECT);
+
+ @Override
+ public Collection<PortStatistics> discoverPortStatistics() {
+
+ DeviceService deviceService = this.handler().get(DeviceService.class);
+ DeviceId deviceId = this.data().deviceId();
+
+ // Get a client for this device.
+ P4RuntimeController controller = handler().get(P4RuntimeController.class);
+ if (!controller.hasClient(deviceId)) {
+ log.warn("Unable to find client for {}, aborting operation", deviceId);
+ return Collections.emptyList();
+ }
+ P4RuntimeClient client = controller.getClient(deviceId);
+
+ // Get the pipeconf of this device.
+ PiPipeconfService piPipeconfService = handler().get(PiPipeconfService.class);
+ if (!piPipeconfService.ofDevice(deviceId).isPresent() ||
+ !piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).isPresent()) {
+ log.warn("Unable to get the pipeconf of {}, aborting operation", deviceId);
+ return Collections.emptyList();
+ }
+ PiPipeconf pipeconf = piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).get();
+
+ // Prepare PortStatistics objects to return, one per port of this device.
+ Map<Long, DefaultPortStatistics.Builder> portStatBuilders = Maps.newHashMap();
+ deviceService
+ .getPorts(deviceId)
+ .forEach(p -> portStatBuilders.put(p.number().toLong(),
+ DefaultPortStatistics.builder()
+ .setPort(p.number())
+ .setDeviceId(deviceId)));
+
+ // Generate the counter cell IDs.
+ Set<PiCounterCellId> counterCellIds = Sets.newHashSet();
+ portStatBuilders.keySet().forEach(p -> {
+ // Counter cell/index = port number.
+ counterCellIds.add(PiIndirectCounterCellId.of(INGRESS_COUNTER_ID, p));
+ counterCellIds.add(PiIndirectCounterCellId.of(EGRESS_COUNTER_ID, p));
+ });
+
+ // Query the device.
+ Collection<PiCounterCellData> counterEntryResponse;
+ try {
+ counterEntryResponse = client.readCounterCells(counterCellIds, pipeconf).get();
+ } catch (InterruptedException | ExecutionException e) {
+ log.warn("Exception while reading port counters from {}: {}", deviceId, e.toString());
+ log.debug("", e);
+ return Collections.emptyList();
+ }
+
+ // Process response.
+ counterEntryResponse.forEach(counterData -> {
+ if (counterData.cellId().type() != INDIRECT) {
+ log.warn("Invalid counter data type {}, skipping", counterData.cellId().type());
+ return;
+ }
+ PiIndirectCounterCellId indCellId = (PiIndirectCounterCellId) counterData.cellId();
+ if (!portStatBuilders.containsKey(indCellId.index())) {
+ log.warn("Unrecognized counter index {}, skipping", counterData);
+ return;
+ }
+ DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(indCellId.index());
+ if (counterData.cellId().counterId().equals(INGRESS_COUNTER_ID)) {
+ statsBuilder.setPacketsReceived(counterData.packets());
+ statsBuilder.setBytesReceived(counterData.bytes());
+ } else if (counterData.cellId().counterId().equals(EGRESS_COUNTER_ID)) {
+ statsBuilder.setPacketsSent(counterData.packets());
+ statsBuilder.setBytesSent(counterData.bytes());
+ } else {
+ log.warn("Unrecognized counter ID {}, skipping", counterData);
+ }
+ });
+
+ return portStatBuilders
+ .values()
+ .stream()
+ .map(DefaultPortStatistics.Builder::build)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/package-info.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/package-info.java
new file mode 100644
index 0000000..3adbcda
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * P4 tutorial pipeconf classes.
+ */
+package org.onosproject.p4tutorial.pipeconf;
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/Makefile b/apps/p4-tutorial/pipeconf/src/main/resources/Makefile
new file mode 100644
index 0000000..9a6a8d0
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/Makefile
@@ -0,0 +1,6 @@
+all: main
+
+main: main.p4
+ p4c-bm2-ss -o main.json --p4runtime-file main.p4info --p4runtime-format text main.p4
+ # Fix for BMv2/p4c bug...
+ sed -i -e 's/"value" : "0xff"/"value" : "0x00ff"/g' main.json
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/main.json b/apps/p4-tutorial/pipeconf/src/main/resources/main.json
new file mode 100644
index 0000000..3d059bb
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/main.json
@@ -0,0 +1,931 @@
+{
+ "program" : "main.p4",
+ "__meta__" : {
+ "version" : [2, 7],
+ "compiler" : "https://github.com/p4lang/p4c"
+ },
+ "header_types" : [
+ {
+ "name" : "scalars_0",
+ "id" : 0,
+ "fields" : [
+ ["tmp", 32, false],
+ ["tmp_0", 32, false]
+ ]
+ },
+ {
+ "name" : "ethernet_t",
+ "id" : 1,
+ "fields" : [
+ ["dst_addr", 48, false],
+ ["src_addr", 48, false],
+ ["ether_type", 16, false]
+ ]
+ },
+ {
+ "name" : "ipv4_t",
+ "id" : 2,
+ "fields" : [
+ ["version", 4, false],
+ ["ihl", 4, false],
+ ["diffserv", 8, false],
+ ["len", 16, false],
+ ["identification", 16, false],
+ ["flags", 3, false],
+ ["frag_offset", 13, false],
+ ["ttl", 8, false],
+ ["protocol", 8, false],
+ ["hdr_checksum", 16, false],
+ ["src_addr", 32, false],
+ ["dst_addr", 32, false]
+ ]
+ },
+ {
+ "name" : "packet_out_header_t",
+ "id" : 3,
+ "fields" : [
+ ["egress_port", 9, false],
+ ["_padding", 7, false]
+ ]
+ },
+ {
+ "name" : "packet_in_header_t",
+ "id" : 4,
+ "fields" : [
+ ["ingress_port", 9, false],
+ ["_padding_0", 7, false]
+ ]
+ },
+ {
+ "name" : "standard_metadata",
+ "id" : 5,
+ "fields" : [
+ ["ingress_port", 9, false],
+ ["egress_spec", 9, false],
+ ["egress_port", 9, false],
+ ["clone_spec", 32, false],
+ ["instance_type", 32, false],
+ ["drop", 1, false],
+ ["recirculate_port", 16, false],
+ ["packet_length", 32, false],
+ ["enq_timestamp", 32, false],
+ ["enq_qdepth", 19, false],
+ ["deq_timedelta", 32, false],
+ ["deq_qdepth", 19, false],
+ ["ingress_global_timestamp", 48, false],
+ ["lf_field_list", 32, false],
+ ["mcast_grp", 16, false],
+ ["resubmit_flag", 1, false],
+ ["egress_rid", 16, false],
+ ["_padding_1", 5, false]
+ ]
+ }
+ ],
+ "headers" : [
+ {
+ "name" : "scalars",
+ "id" : 0,
+ "header_type" : "scalars_0",
+ "metadata" : true,
+ "pi_omit" : true
+ },
+ {
+ "name" : "standard_metadata",
+ "id" : 1,
+ "header_type" : "standard_metadata",
+ "metadata" : true,
+ "pi_omit" : true
+ },
+ {
+ "name" : "ethernet",
+ "id" : 2,
+ "header_type" : "ethernet_t",
+ "metadata" : false,
+ "pi_omit" : true
+ },
+ {
+ "name" : "ipv4",
+ "id" : 3,
+ "header_type" : "ipv4_t",
+ "metadata" : false,
+ "pi_omit" : true
+ },
+ {
+ "name" : "packet_out",
+ "id" : 4,
+ "header_type" : "packet_out_header_t",
+ "metadata" : false,
+ "pi_omit" : true
+ },
+ {
+ "name" : "packet_in",
+ "id" : 5,
+ "header_type" : "packet_in_header_t",
+ "metadata" : false,
+ "pi_omit" : true
+ }
+ ],
+ "header_stacks" : [],
+ "header_union_types" : [],
+ "header_unions" : [],
+ "header_union_stacks" : [],
+ "field_lists" : [],
+ "errors" : [
+ ["NoError", 1],
+ ["PacketTooShort", 2],
+ ["NoMatch", 3],
+ ["StackOutOfBounds", 4],
+ ["HeaderTooShort", 5],
+ ["ParserTimeout", 6]
+ ],
+ "enums" : [],
+ "parsers" : [
+ {
+ "name" : "parser",
+ "id" : 0,
+ "init_state" : "start",
+ "parse_states" : [
+ {
+ "name" : "start",
+ "id" : 0,
+ "parser_ops" : [],
+ "transitions" : [
+ {
+ "value" : "0x00ff",
+ "mask" : null,
+ "next_state" : "parse_packet_out"
+ },
+ {
+ "value" : "default",
+ "mask" : null,
+ "next_state" : "parse_ethernet"
+ }
+ ],
+ "transition_key" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ }
+ ]
+ },
+ {
+ "name" : "parse_packet_out",
+ "id" : 1,
+ "parser_ops" : [
+ {
+ "parameters" : [
+ {
+ "type" : "regular",
+ "value" : "packet_out"
+ }
+ ],
+ "op" : "extract"
+ }
+ ],
+ "transitions" : [
+ {
+ "value" : "default",
+ "mask" : null,
+ "next_state" : "parse_ethernet"
+ }
+ ],
+ "transition_key" : []
+ },
+ {
+ "name" : "parse_ethernet",
+ "id" : 2,
+ "parser_ops" : [
+ {
+ "parameters" : [
+ {
+ "type" : "regular",
+ "value" : "ethernet"
+ }
+ ],
+ "op" : "extract"
+ }
+ ],
+ "transitions" : [
+ {
+ "value" : "0x0800",
+ "mask" : null,
+ "next_state" : "parse_ipv4"
+ },
+ {
+ "value" : "default",
+ "mask" : null,
+ "next_state" : null
+ }
+ ],
+ "transition_key" : [
+ {
+ "type" : "field",
+ "value" : ["ethernet", "ether_type"]
+ }
+ ]
+ },
+ {
+ "name" : "parse_ipv4",
+ "id" : 3,
+ "parser_ops" : [
+ {
+ "parameters" : [
+ {
+ "type" : "regular",
+ "value" : "ipv4"
+ }
+ ],
+ "op" : "extract"
+ }
+ ],
+ "transitions" : [
+ {
+ "value" : "default",
+ "mask" : null,
+ "next_state" : null
+ }
+ ],
+ "transition_key" : []
+ }
+ ]
+ }
+ ],
+ "deparsers" : [
+ {
+ "name" : "deparser",
+ "id" : 0,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 264,
+ "column" : 8,
+ "source_fragment" : "DeparserImpl"
+ },
+ "order" : ["packet_in", "ethernet", "ipv4"]
+ }
+ ],
+ "meter_arrays" : [],
+ "counter_arrays" : [
+ {
+ "name" : "egr_port_counter",
+ "id" : 0,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 181,
+ "column" : 38,
+ "source_fragment" : "egr_port_counter"
+ },
+ "size" : 511,
+ "is_direct" : false
+ },
+ {
+ "name" : "igr_port_counter",
+ "id" : 1,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 182,
+ "column" : 38,
+ "source_fragment" : "igr_port_counter"
+ },
+ "size" : 511,
+ "is_direct" : false
+ }
+ ],
+ "register_arrays" : [],
+ "calculations" : [],
+ "learn_lists" : [],
+ "actions" : [
+ {
+ "name" : "NoAction",
+ "id" : 0,
+ "runtime_data" : [],
+ "primitives" : []
+ },
+ {
+ "name" : "send_to_cpu",
+ "id" : 1,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ {
+ "type" : "hexstr",
+ "value" : "0x00ff"
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 24,
+ "column" : 24,
+ "source_fragment" : "255; ..."
+ }
+ },
+ {
+ "op" : "add_header",
+ "parameters" : [
+ {
+ "type" : "header",
+ "value" : "packet_in"
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 137,
+ "column" : 8,
+ "source_fragment" : "hdr.packet_in.setValid()"
+ }
+ },
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["packet_in", "ingress_port"]
+ },
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 138,
+ "column" : 8,
+ "source_fragment" : "hdr.packet_in.ingress_port = standard_metadata.ingress_port"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "set_egress_port",
+ "id" : 2,
+ "runtime_data" : [
+ {
+ "name" : "port",
+ "bitwidth" : 9
+ }
+ ],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ {
+ "type" : "runtime_data",
+ "value" : 0
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 142,
+ "column" : 8,
+ "source_fragment" : "standard_metadata.egress_spec = port"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "_drop",
+ "id" : 3,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ {
+ "type" : "hexstr",
+ "value" : "0x01ff"
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 25,
+ "column" : 25,
+ "source_fragment" : "511; ..."
+ }
+ }
+ ]
+ },
+ {
+ "name" : "_drop",
+ "id" : 4,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ {
+ "type" : "hexstr",
+ "value" : "0x01ff"
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 25,
+ "column" : 25,
+ "source_fragment" : "511; ..."
+ }
+ }
+ ]
+ },
+ {
+ "name" : "act",
+ "id" : 5,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ {
+ "type" : "field",
+ "value" : ["packet_out", "egress_port"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 195,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.egress_spec = hdr.packet_out.egress_port"
+ }
+ },
+ {
+ "op" : "remove_header",
+ "parameters" : [
+ {
+ "type" : "header",
+ "value" : "packet_out"
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 196,
+ "column" : 12,
+ "source_fragment" : "hdr.packet_out.setInvalid()"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "act_0",
+ "id" : 6,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp"]
+ },
+ {
+ "type" : "expression",
+ "value" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "&",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0xffffffff"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "op" : "count",
+ "parameters" : [
+ {
+ "type" : "counter_array",
+ "value" : "egr_port_counter"
+ },
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 218,
+ "column" : 12,
+ "source_fragment" : "egr_port_counter.count((bit<32>) standard_metadata.egress_spec)"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "act_1",
+ "id" : 7,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp_0"]
+ },
+ {
+ "type" : "expression",
+ "value" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "&",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0xffffffff"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "op" : "count",
+ "parameters" : [
+ {
+ "type" : "counter_array",
+ "value" : "igr_port_counter"
+ },
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp_0"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 221,
+ "column" : 12,
+ "source_fragment" : "igr_port_counter.count((bit<32>) standard_metadata.ingress_port)"
+ }
+ }
+ ]
+ }
+ ],
+ "pipelines" : [
+ {
+ "name" : "ingress",
+ "id" : 0,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 126,
+ "column" : 8,
+ "source_fragment" : "IngressImpl"
+ },
+ "init_table" : "node_2",
+ "tables" : [
+ {
+ "name" : "tbl_act",
+ "id" : 0,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [5],
+ "actions" : ["act"],
+ "base_default_next" : "node_7",
+ "next_tables" : {
+ "act" : "node_7"
+ },
+ "default_entry" : {
+ "action_id" : 5,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
+ },
+ {
+ "name" : "table0",
+ "id" : 1,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 149,
+ "column" : 10,
+ "source_fragment" : "table0"
+ },
+ "key" : [
+ {
+ "match_type" : "ternary",
+ "target" : ["standard_metadata", "ingress_port"],
+ "mask" : null
+ },
+ {
+ "match_type" : "ternary",
+ "target" : ["ethernet", "dst_addr"],
+ "mask" : null
+ },
+ {
+ "match_type" : "ternary",
+ "target" : ["ethernet", "src_addr"],
+ "mask" : null
+ },
+ {
+ "match_type" : "ternary",
+ "target" : ["ethernet", "ether_type"],
+ "mask" : null
+ }
+ ],
+ "match_type" : "ternary",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [2, 1, 3],
+ "actions" : ["set_egress_port", "send_to_cpu", "_drop"],
+ "base_default_next" : "node_7",
+ "next_tables" : {
+ "set_egress_port" : "node_5",
+ "send_to_cpu" : "node_7",
+ "_drop" : "node_7"
+ },
+ "default_entry" : {
+ "action_id" : 3,
+ "action_const" : false,
+ "action_data" : [],
+ "action_entry_const" : false
+ }
+ },
+ {
+ "name" : "ip_proto_filter_table",
+ "id" : 2,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 164,
+ "column" : 10,
+ "source_fragment" : "ip_proto_filter_table"
+ },
+ "key" : [
+ {
+ "match_type" : "ternary",
+ "target" : ["ipv4", "src_addr"],
+ "mask" : null
+ },
+ {
+ "match_type" : "exact",
+ "target" : ["ipv4", "protocol"],
+ "mask" : null
+ }
+ ],
+ "match_type" : "ternary",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [4, 0],
+ "actions" : ["_drop", "NoAction"],
+ "base_default_next" : "node_7",
+ "next_tables" : {
+ "_drop" : "node_7",
+ "NoAction" : "node_7"
+ },
+ "default_entry" : {
+ "action_id" : 0,
+ "action_const" : false,
+ "action_data" : [],
+ "action_entry_const" : false
+ }
+ },
+ {
+ "name" : "tbl_act_0",
+ "id" : 3,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [6],
+ "actions" : ["act_0"],
+ "base_default_next" : "node_9",
+ "next_tables" : {
+ "act_0" : "node_9"
+ },
+ "default_entry" : {
+ "action_id" : 6,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
+ },
+ {
+ "name" : "tbl_act_1",
+ "id" : 4,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [7],
+ "actions" : ["act_1"],
+ "base_default_next" : null,
+ "next_tables" : {
+ "act_1" : null
+ },
+ "default_entry" : {
+ "action_id" : 7,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
+ }
+ ],
+ "action_profiles" : [],
+ "conditionals" : [
+ {
+ "name" : "node_2",
+ "id" : 0,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 188,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.ingress_port == CPU_PORT"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "==",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x00ff"
+ }
+ }
+ },
+ "true_next" : "tbl_act",
+ "false_next" : "table0"
+ },
+ {
+ "name" : "node_5",
+ "id" : 1,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 205,
+ "column" : 24,
+ "source_fragment" : "hdr.ipv4.isValid()"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "==",
+ "left" : {
+ "type" : "field",
+ "value" : ["ipv4", "$valid$"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x01"
+ }
+ }
+ },
+ "true_next" : "ip_proto_filter_table",
+ "false_next" : "node_7"
+ },
+ {
+ "name" : "node_7",
+ "id" : 2,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 217,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.egress_spec < 511"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "<",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x01ff"
+ }
+ }
+ },
+ "true_next" : "tbl_act_0",
+ "false_next" : "node_9"
+ },
+ {
+ "name" : "node_9",
+ "id" : 3,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 220,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.ingress_port < 511"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "<",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x01ff"
+ }
+ }
+ },
+ "false_next" : null,
+ "true_next" : "tbl_act_1"
+ }
+ ]
+ },
+ {
+ "name" : "egress",
+ "id" : 1,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 230,
+ "column" : 8,
+ "source_fragment" : "EgressImpl"
+ },
+ "init_table" : null,
+ "tables" : [],
+ "action_profiles" : [],
+ "conditionals" : []
+ }
+ ],
+ "checksums" : [],
+ "force_arith" : [],
+ "extern_instances" : [],
+ "field_aliases" : [
+ [
+ "queueing_metadata.enq_timestamp",
+ ["standard_metadata", "enq_timestamp"]
+ ],
+ [
+ "queueing_metadata.enq_qdepth",
+ ["standard_metadata", "enq_qdepth"]
+ ],
+ [
+ "queueing_metadata.deq_timedelta",
+ ["standard_metadata", "deq_timedelta"]
+ ],
+ [
+ "queueing_metadata.deq_qdepth",
+ ["standard_metadata", "deq_qdepth"]
+ ],
+ [
+ "intrinsic_metadata.ingress_global_timestamp",
+ ["standard_metadata", "ingress_global_timestamp"]
+ ],
+ [
+ "intrinsic_metadata.lf_field_list",
+ ["standard_metadata", "lf_field_list"]
+ ],
+ [
+ "intrinsic_metadata.mcast_grp",
+ ["standard_metadata", "mcast_grp"]
+ ],
+ [
+ "intrinsic_metadata.resubmit_flag",
+ ["standard_metadata", "resubmit_flag"]
+ ],
+ [
+ "intrinsic_metadata.egress_rid",
+ ["standard_metadata", "egress_rid"]
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/main.p4 b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4
new file mode 100644
index 0000000..9f0b809
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <core.p4>
+#include <v1model.p4>
+
+#define ETH_TYPE_IPV4 0x0800
+#define MAX_PORTS 511
+
+typedef bit<9> port_t;
+const port_t CPU_PORT = 255;
+const port_t DROP_PORT = 511;
+
+//------------------------------------------------------------------------------
+// HEADERS
+//------------------------------------------------------------------------------
+
+header ethernet_t {
+ bit<48> dst_addr;
+ bit<48> src_addr;
+ bit<16> ether_type;
+}
+
+header ipv4_t {
+ bit<4> version;
+ bit<4> ihl;
+ bit<8> diffserv;
+ bit<16> len;
+ bit<16> identification;
+ bit<3> flags;
+ bit<13> frag_offset;
+ bit<8> ttl;
+ bit<8> protocol;
+ bit<16> hdr_checksum;
+ bit<32> src_addr;
+ bit<32> dst_addr;
+}
+
+/*
+Packet-in header. Prepended to packets sent to the controller and used to carry
+the original ingress port where the packet was received.
+ */
+@controller_header("packet_in")
+header packet_in_header_t {
+ bit<9> ingress_port;
+}
+
+/*
+Packet-out header. Prepended to packets received by the controller and used to
+tell the switch on which physical port this packet should be forwarded.
+ */
+@controller_header("packet_out")
+header packet_out_header_t {
+ bit<9> egress_port;
+}
+
+/*
+For convenience we collect all headers under the same struct.
+ */
+struct headers_t {
+ ethernet_t ethernet;
+ ipv4_t ipv4;
+ packet_out_header_t packet_out;
+ packet_in_header_t packet_in;
+}
+
+/*
+Metadata can be used to carry information from one table to another.
+ */
+struct metadata_t {
+ /* Empty. We don't use it in this program. */
+}
+
+//------------------------------------------------------------------------------
+// PARSER
+//------------------------------------------------------------------------------
+
+parser ParserImpl(packet_in packet,
+ out headers_t hdr,
+ inout metadata_t meta,
+ inout standard_metadata_t standard_metadata) {
+
+ state start {
+ transition select(standard_metadata.ingress_port) {
+ CPU_PORT: parse_packet_out;
+ default: parse_ethernet;
+ }
+ }
+
+ state parse_packet_out {
+ packet.extract(hdr.packet_out);
+ transition parse_ethernet;
+ }
+
+ state parse_ethernet {
+ packet.extract(hdr.ethernet);
+ transition select(hdr.ethernet.ether_type) {
+ ETH_TYPE_IPV4: parse_ipv4;
+ default: accept;
+ }
+ }
+
+ state parse_ipv4 {
+ packet.extract(hdr.ipv4);
+ transition accept;
+ }
+}
+
+//------------------------------------------------------------------------------
+// INGRESS PIPELINE
+//------------------------------------------------------------------------------
+
+control IngressImpl(inout headers_t hdr,
+ inout metadata_t meta,
+ inout standard_metadata_t standard_metadata) {
+
+ action send_to_cpu() {
+ standard_metadata.egress_spec = CPU_PORT;
+ /*
+ Packets sent to the controller needs to be prepended with the packet-in
+ header. By setting it valid we make sure it will be deparsed before the
+ ethernet header (see DeparserImpl).
+ */
+ hdr.packet_in.setValid();
+ hdr.packet_in.ingress_port = standard_metadata.ingress_port;
+ }
+
+ action set_egress_port(port_t port) {
+ standard_metadata.egress_spec = port;
+ }
+
+ action _drop() {
+ standard_metadata.egress_spec = DROP_PORT;
+ }
+
+ table table0 {
+ key = {
+ standard_metadata.ingress_port : ternary;
+ hdr.ethernet.dst_addr : ternary;
+ hdr.ethernet.src_addr : ternary;
+ hdr.ethernet.ether_type : ternary;
+ }
+ actions = {
+ set_egress_port();
+ send_to_cpu();
+ _drop();
+ }
+ default_action = _drop();
+ }
+
+ table ip_proto_filter_table {
+ key = {
+ hdr.ipv4.src_addr : ternary;
+ hdr.ipv4.protocol : exact;
+ }
+ actions = {
+ _drop();
+ }
+ }
+
+ /*
+ Port counters.
+ We use these counter instances to count packets/bytes received/sent on each
+ port. BMv2 always counts both packets and bytes, even if the counter is
+ instantiated as "packets". For each counter we instantiate a number of cells
+ equal to MAX_PORTS.
+ */
+ counter(MAX_PORTS, CounterType.packets) egr_port_counter;
+ counter(MAX_PORTS, CounterType.packets) igr_port_counter;
+
+ /*
+ We define here the processing to be executed by this ingress pipeline.
+ */
+ apply {
+ if (standard_metadata.ingress_port == CPU_PORT) {
+ /*
+ Packet received from CPU_PORT, this is a packet-out sent by the
+ controller. Skip pipeline processing, set the egress port as
+ requested by the controller (packet_out header) and remove the
+ packet_out header.
+ */
+ standard_metadata.egress_spec = hdr.packet_out.egress_port;
+ hdr.packet_out.setInvalid();
+ } else {
+ /*
+ Packet received from switch port. Apply table0, if action is
+ set_egress_port and packet is IPv4, then apply
+ ip_proto_filter_table.
+ */
+ switch(table0.apply().action_run) {
+ set_egress_port: {
+ if (hdr.ipv4.isValid()) {
+ ip_proto_filter_table.apply();
+ }
+ }
+ }
+ }
+
+ /*
+ For each port counter, we update the cell at index = ingress/egress
+ port. We avoid counting packets sent/received on CPU_PORT or dropped
+ (DROP_PORT).
+ */
+ if (standard_metadata.egress_spec < MAX_PORTS) {
+ egr_port_counter.count((bit<32>) standard_metadata.egress_spec);
+ }
+ if (standard_metadata.ingress_port < MAX_PORTS) {
+ igr_port_counter.count((bit<32>) standard_metadata.ingress_port);
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// EGRESS PIPELINE
+//------------------------------------------------------------------------------
+
+control EgressImpl(inout headers_t hdr,
+ inout metadata_t meta,
+ inout standard_metadata_t standard_metadata) {
+ apply {
+ /*
+ Nothing to do on the egress pipeline.
+ */
+ }
+}
+
+//------------------------------------------------------------------------------
+// CHECKSUM HANDLING
+//------------------------------------------------------------------------------
+
+control VerifyChecksumImpl(in headers_t hdr, inout metadata_t meta) {
+ apply {
+ /*
+ Nothing to do here, we assume checksum is always correct.
+ */
+ }
+}
+
+control ComputeChecksumImpl(inout headers_t hdr, inout metadata_t meta) {
+ apply {
+ /*
+ Nothing to do here, as we do not modify packet headers.
+ */
+ }
+}
+
+//------------------------------------------------------------------------------
+// DEPARSER
+//------------------------------------------------------------------------------
+
+control DeparserImpl(packet_out packet, in headers_t hdr) {
+ apply {
+ /*
+ Deparse headers in order. Only valid headers are emitted.
+ */
+ packet.emit(hdr.packet_in);
+ packet.emit(hdr.ethernet);
+ packet.emit(hdr.ipv4);
+ }
+}
+
+//------------------------------------------------------------------------------
+// SWITCH INSTANTIATION
+//------------------------------------------------------------------------------
+
+V1Switch(ParserImpl(),
+ VerifyChecksumImpl(),
+ IngressImpl(),
+ EgressImpl(),
+ ComputeChecksumImpl(),
+ DeparserImpl()) main;
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/main.p4info b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4info
new file mode 100644
index 0000000..c4882d2
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4info
@@ -0,0 +1,147 @@
+tables {
+ preamble {
+ id: 33617813
+ name: "table0"
+ alias: "table0"
+ }
+ match_fields {
+ id: 1
+ name: "standard_metadata.ingress_port"
+ bitwidth: 9
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 2
+ name: "hdr.ethernet.dst_addr"
+ bitwidth: 48
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 3
+ name: "hdr.ethernet.src_addr"
+ bitwidth: 48
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 4
+ name: "hdr.ethernet.ether_type"
+ bitwidth: 16
+ match_type: TERNARY
+ }
+ action_refs {
+ id: 16794308
+ }
+ action_refs {
+ id: 16829080
+ }
+ action_refs {
+ id: 16784184
+ }
+ size: 1024
+}
+tables {
+ preamble {
+ id: 33573361
+ name: "ip_proto_filter_table"
+ alias: "ip_proto_filter_table"
+ }
+ match_fields {
+ id: 1
+ name: "hdr.ipv4.src_addr"
+ bitwidth: 32
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 2
+ name: "hdr.ipv4.protocol"
+ bitwidth: 8
+ match_type: EXACT
+ }
+ action_refs {
+ id: 16784184
+ }
+ action_refs {
+ id: 16800567
+ annotations: "@defaultonly()"
+ }
+ size: 1024
+}
+actions {
+ preamble {
+ id: 16800567
+ name: "NoAction"
+ alias: "NoAction"
+ }
+}
+actions {
+ preamble {
+ id: 16829080
+ name: "send_to_cpu"
+ alias: "send_to_cpu"
+ }
+}
+actions {
+ preamble {
+ id: 16794308
+ name: "set_egress_port"
+ alias: "set_egress_port"
+ }
+ params {
+ id: 1
+ name: "port"
+ bitwidth: 9
+ }
+}
+actions {
+ preamble {
+ id: 16784184
+ name: "_drop"
+ alias: "_drop"
+ }
+}
+counters {
+ preamble {
+ id: 302012419
+ name: "egr_port_counter"
+ alias: "egr_port_counter"
+ }
+ spec {
+ unit: PACKETS
+ }
+ size: 511
+}
+counters {
+ preamble {
+ id: 302054463
+ name: "igr_port_counter"
+ alias: "igr_port_counter"
+ }
+ spec {
+ unit: PACKETS
+ }
+ size: 511
+}
+controller_packet_metadata {
+ preamble {
+ id: 2868941301
+ name: "packet_in"
+ annotations: "@controller_header(\"packet_in\")"
+ }
+ metadata {
+ id: 1
+ name: "ingress_port"
+ bitwidth: 9
+ }
+}
+controller_packet_metadata {
+ preamble {
+ id: 2868916615
+ name: "packet_out"
+ annotations: "@controller_header(\"packet_out\")"
+ }
+ metadata {
+ id: 1
+ name: "egress_port"
+ bitwidth: 9
+ }
+}
diff --git a/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/api/GatewayNodeConfig.java b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/api/GatewayNodeConfig.java
index caee767..d219fe7 100644
--- a/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/api/GatewayNodeConfig.java
+++ b/apps/scalablegateway/src/main/java/org/onosproject/scalablegateway/api/GatewayNodeConfig.java
@@ -46,7 +46,7 @@
/**
* Returns the set of nodes read from network config.
*
- * @return set of OpensatckNodeConfig or null
+ * @return set of OpenstackNodeConfig or null
*/
public Set<GatewayNode> gatewayNodes() {
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
index f65e636..d9d0c14 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.css
@@ -58,6 +58,16 @@
display: inline-block;
margin: 8px 0;
padding-left: 6px;
+ font-size: 16pt;
+ font-weight: lighter;
+}
+
+#yang-model-details-panel h3 {
+ display: inline-block;
+ margin: 8px 0;
+ padding-left: 6px;
+ font-size: 13pt;
+ font-weight: lighter;
}
#yang-model-details-panel .src-frame {
diff --git a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
index 42fa343..e35b8df 100644
--- a/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
+++ b/apps/yang-gui/src/main/resources/app/view/yangModel/yangModel.js
@@ -89,7 +89,7 @@
top.append('hr');
bottom = container.append('div').classed('bottom', true);
- bottom.append('h2').text('YANG Source');
+ bottom.append('h3').text('YANG Source');
srcFrame = bottom.append('div').classed('src-frame', true);
srcDiv = srcFrame.append('div').classed('module-source', true);
diff --git a/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java b/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java
index 1848bf3..3f93ced 100644
--- a/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java
+++ b/cli/src/main/java/org/onosproject/cli/app/ApplicationNameCompleter.java
@@ -22,10 +22,19 @@
import org.onosproject.cli.AbstractCompleter;
import org.onosproject.core.Application;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
import java.util.Iterator;
import java.util.List;
+import java.util.Map.Entry;
import java.util.SortedSet;
+import java.util.stream.Collectors;
+import static java.util.Arrays.asList;
import static org.onosproject.app.ApplicationState.ACTIVE;
import static org.onosproject.app.ApplicationState.INSTALLED;
import static org.onosproject.cli.AbstractShellCommand.get;
@@ -70,6 +79,33 @@
}
}
+ // add unique suffix to candidates, if user has something in buffer
+ if (!Strings.isNullOrEmpty(buffer)) {
+ List<String> suffixCandidates = strings.stream()
+ // remove onos common prefix
+ .map(full -> full.replaceFirst("org\\.onosproject\\.", ""))
+ // a.b.c -> [c, b.c, a.b.c]
+ .flatMap(appName -> {
+ List<String> suffixes = new ArrayList<>();
+ Deque<String> frags = new ArrayDeque<>();
+ // a.b.c -> [c, b, a] -> [c, b.c, a.b.c]
+ Lists.reverse(asList(appName.split("\\."))).forEach(frag -> {
+ frags.addFirst(frag);
+ suffixes.add(frags.stream().collect(Collectors.joining(".")));
+ });
+ return suffixes.stream();
+ })
+ // convert to occurrence map
+ .collect(Collectors.groupingBy(e -> e, Collectors.counting()))
+ .entrySet().stream()
+ // only accept unique suffix
+ .filter(e -> e.getValue() == 1L)
+ .map(Entry::getKey)
+ .collect(Collectors.toList());
+
+ delegate.getStrings().addAll(suffixCandidates);
+ }
+
// Now let the completer do the work for figuring out what to offer.
return delegate.complete(buffer, cursor, candidates);
}
diff --git a/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java b/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
index 487380f..d0a4732 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
@@ -342,7 +342,7 @@
*
* @return PiCriterion
*/
- public Criterion build() {
+ public PiCriterion build() {
ImmutableMap<PiHeaderFieldId, PiFieldMatch> fieldMatchMap = fieldMatchMapBuilder.build();
checkArgument(fieldMatchMap.size() > 0, "Cannot build PI criterion with 0 field matches");
return new PiCriterion(fieldMatchMap);
diff --git a/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java b/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java
index 364d44c..f760abb 100644
--- a/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java
+++ b/core/api/src/main/java/org/onosproject/ui/GlyphConstants.java
@@ -55,6 +55,7 @@
public static final String PORT_TABLE = "portTable";
public static final String GROUP_TABLE = "groupTable";
public static final String METER_TABLE = "meterTable";
+ public static final String PIPECONF_TABLE = "pipeconfTable";
public static final String SUMMARY = "m_summary";
public static final String DETAILS = "m_details";
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
index cd39d47..7a88366 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
@@ -85,6 +85,15 @@
import org.onosproject.net.meter.Meter;
import org.onosproject.net.meter.MeterRequest;
import org.onosproject.net.packet.PacketRequest;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiActionParamModel;
+import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
+import org.onosproject.net.pi.model.PiHeaderModel;
+import org.onosproject.net.pi.model.PiHeaderTypeModel;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableMatchFieldModel;
+import org.onosproject.net.pi.model.PiTableModel;
import org.onosproject.net.region.Region;
import org.onosproject.net.statistic.Load;
import org.onosproject.net.topology.Topology;
@@ -176,6 +185,15 @@
registerCodec(FilteredConnectPoint.class, new FilteredConnectPointCodec());
registerCodec(TransportEndpointDescription.class, new TransportEndpointDescriptionCodec());
registerCodec(PacketRequest.class, new PacketRequestCodec());
+ registerCodec(PiActionModel.class, new PiActionModelCodec());
+ registerCodec(PiHeaderModel.class, new PiHeaderModelCodec());
+ registerCodec(PiPipelineModel.class, new PiPipelineModelCodec());
+ registerCodec(PiPipeconf.class, new PiPipeconfCodec());
+ registerCodec(PiTableModel.class, new PiTableModelCodec());
+ registerCodec(PiTableMatchFieldModel.class, new PiTableMatchFieldModelCodec());
+ registerCodec(PiHeaderFieldTypeModel.class, new PiHeaderFieldTypeModelCodec());
+ registerCodec(PiHeaderTypeModel.class, new PiHeaderTypeModelCodec());
+ registerCodec(PiActionParamModel.class, new PiActionParamModelCodec());
log.info("Started");
}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
index 7c26302..da34c5e 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
@@ -24,8 +24,10 @@
import org.onosproject.net.DeviceId;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.IndexTableId;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.pi.runtime.PiTableId;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.util.Tools.nullIsIllegal;
@@ -43,6 +45,8 @@
private static final String DEVICE_ID = "deviceId";
private static final String TREATMENT = "treatment";
private static final String SELECTOR = "selector";
+ private static final String ID = "id";
+ private static final String TABLE_NAME = "tableName";
private static final String MISSING_MEMBER_MESSAGE =
" member is required in FlowRule";
public static final String REST_APP_ID = "org.onosproject.rest";
@@ -56,24 +60,25 @@
String strAppId = (appId == null) ? "<none>" : appId.name();
final ObjectNode result = context.mapper().createObjectNode()
- .put("id", Long.toString(flowRule.id().value()))
- .put("tableId", flowRule.tableId())
- .put("appId", strAppId)
- .put("priority", flowRule.priority())
- .put("timeout", flowRule.timeout())
- .put("isPermanent", flowRule.isPermanent())
- .put("deviceId", flowRule.deviceId().toString());
+ .put(ID, Long.toString(flowRule.id().value()))
+ .put(APP_ID, strAppId)
+ .put(PRIORITY, flowRule.priority())
+ .put(TIMEOUT, flowRule.timeout())
+ .put(IS_PERMANENT, flowRule.isPermanent())
+ .put(DEVICE_ID, flowRule.deviceId().toString())
+ .put(TABLE_ID, flowRule.tableId())
+ .put(TABLE_NAME, flowRule.table().toString());
if (flowRule.treatment() != null) {
final JsonCodec<TrafficTreatment> treatmentCodec =
context.codec(TrafficTreatment.class);
- result.set("treatment", treatmentCodec.encode(flowRule.treatment(), context));
+ result.set(TREATMENT, treatmentCodec.encode(flowRule.treatment(), context));
}
if (flowRule.selector() != null) {
final JsonCodec<TrafficSelector> selectorCodec =
context.codec(TrafficSelector.class);
- result.set("selector", selectorCodec.encode(flowRule.selector(), context));
+ result.set(SELECTOR, selectorCodec.encode(flowRule.selector(), context));
}
return result;
@@ -109,7 +114,13 @@
JsonNode tableIdJson = json.get(TABLE_ID);
if (tableIdJson != null) {
- resultBuilder.forTable(tableIdJson.asInt());
+ String tableId = tableIdJson.asText();
+ try {
+ int tid = Integer.parseInt(tableId);
+ resultBuilder.forTable(IndexTableId.of(tid));
+ } catch (NumberFormatException e) {
+ resultBuilder.forTable(PiTableId.of(tableId));
+ }
}
DeviceId deviceId = DeviceId.deviceId(nullIsIllegal(json.get(DEVICE_ID),
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiActionModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiActionModelCodec.java
new file mode 100644
index 0000000..10cef43
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiActionModelCodec.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiActionParamModel;
+
+/**
+ * Codec for PiActionModel.
+ */
+public class PiActionModelCodec extends JsonCodec<PiActionModel> {
+ private static final String NAME = "name";
+ private static final String PARAMS = "params";
+
+ @Override
+ public ObjectNode encode(PiActionModel action, CodecContext context) {
+ ObjectNode result = context.mapper().createObjectNode();
+ result.put(NAME, action.name());
+ ArrayNode params = result.putArray(PARAMS);
+ action.params().forEach(param -> {
+ ObjectNode paramData = context.encode(param, PiActionParamModel.class);
+ params.add(paramData);
+ });
+ return result;
+ }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiActionParamModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiActionParamModelCodec.java
new file mode 100644
index 0000000..ac553fe
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiActionParamModelCodec.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiActionParamModel;
+
+/**
+ * Codec for PiActionParamModel.
+ */
+public class PiActionParamModelCodec extends JsonCodec<PiActionParamModel> {
+
+ private static final String NAME = "name";
+ private static final String BIT_WIDTH = "bitWidth";
+
+ @Override
+ public ObjectNode encode(PiActionParamModel param, CodecContext context) {
+ ObjectNode result = context.mapper().createObjectNode();
+ result.put(NAME, param.name());
+ result.put(BIT_WIDTH, param.bitWidth());
+ return result;
+ }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderFieldTypeModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderFieldTypeModelCodec.java
new file mode 100644
index 0000000..09a1566
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderFieldTypeModelCodec.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
+
+/**
+ * Codec for PiHeaderFieldTypeModel.
+ */
+public class PiHeaderFieldTypeModelCodec extends JsonCodec<PiHeaderFieldTypeModel> {
+ private static final String NAME = "name";
+ private static final String BIT_WIDTH = "bitWidth";
+
+ @Override
+ public ObjectNode encode(PiHeaderFieldTypeModel headerFieldType, CodecContext context) {
+ ObjectNode result = context.mapper().createObjectNode();
+ result.put(NAME, headerFieldType.name());
+ result.put(BIT_WIDTH, headerFieldType.bitWidth());
+ return result;
+ }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderModelCodec.java
new file mode 100644
index 0000000..7bcd134
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderModelCodec.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiHeaderModel;
+import org.onosproject.net.pi.model.PiHeaderTypeModel;
+
+/**
+ * Codec for PiHeaderModel.
+ */
+public class PiHeaderModelCodec extends JsonCodec<PiHeaderModel> {
+ private static final String NAME = "name";
+ private static final String TYPE = "type";
+ private static final String IS_META = "isMetadata";
+ private static final String INDEX = "index";
+
+ @Override
+ public ObjectNode encode(PiHeaderModel header, CodecContext context) {
+ ObjectNode result = context.mapper().createObjectNode();
+ ObjectNode headerTypeData = context.encode(header.type(), PiHeaderTypeModel.class);
+ result.put(NAME, header.name());
+ result.set(TYPE, headerTypeData);
+ result.put(IS_META, header.isMetadata());
+ result.put(INDEX, header.index());
+ return result;
+ }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderTypeModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderTypeModelCodec.java
new file mode 100644
index 0000000..1065a94
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderTypeModelCodec.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
+import org.onosproject.net.pi.model.PiHeaderTypeModel;
+
+/**
+ * Codec for PiHeaderTypeModel.
+ */
+public class PiHeaderTypeModelCodec extends JsonCodec<PiHeaderTypeModel> {
+ private static final String NAME = "name";
+ private static final String FIELDS = "fields";
+
+ @Override
+ public ObjectNode encode(PiHeaderTypeModel headerType, CodecContext context) {
+ ObjectNode result = context.mapper().createObjectNode();
+ result.put(NAME, headerType.name());
+ ArrayNode fields = result.putArray(FIELDS);
+
+ headerType.fields().forEach(field -> {
+ fields.add(context.encode(field, PiHeaderFieldTypeModel.class));
+ });
+
+ return result;
+ }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiPipeconfCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiPipeconfCodec.java
new file mode 100644
index 0000000..a719741
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiPipeconfCodec.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Lists;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiPipeconf;
+
+/**
+ * Codec for PiPipeconf.
+ */
+public class PiPipeconfCodec extends JsonCodec<PiPipeconf> {
+
+ private static final String ID = "id";
+ private static final String BEHAVIORS = "behaviors";
+ private static final String EXTENSIONS = "extensions";
+
+ @Override
+ public ObjectNode encode(PiPipeconf pipeconf, CodecContext context) {
+ ObjectNode result = context.mapper().createObjectNode();
+ result.put(ID, pipeconf.id().id());
+ ArrayNode behaviors = result.putArray(BEHAVIORS);
+ pipeconf.behaviours().forEach(behavior -> {
+ behaviors.add(behavior.getSimpleName());
+ });
+ ArrayNode extensions = result.putArray(EXTENSIONS);
+ Lists.newArrayList(PiPipeconf.ExtensionType.values()).forEach(extension -> {
+ if (pipeconf.extension(extension).isPresent()) {
+ extensions.add(extension.toString());
+ }
+ });
+ return result;
+ }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiPipelineModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiPipelineModelCodec.java
new file mode 100644
index 0000000..6b42c8d
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiPipelineModelCodec.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiHeaderModel;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableModel;
+
+/**
+ * Codec for PiPipelineModel.
+ */
+public class PiPipelineModelCodec extends JsonCodec<PiPipelineModel> {
+ private static final String HEADERS = "headers";
+ private static final String ACTIONS = "actions";
+ private static final String TABLES = "tables";
+
+ @Override
+ public ObjectNode encode(PiPipelineModel pipeline, CodecContext context) {
+ ObjectNode result = context.mapper().createObjectNode();
+ ArrayNode headers = result.putArray(HEADERS);
+ pipeline.headers().stream()
+ .map(header -> context.encode(header, PiHeaderModel.class))
+ .forEach(headers::add);
+ ArrayNode actions = result.putArray(ACTIONS);
+ pipeline.actions().stream()
+ .map(action -> context.encode(action, PiActionModel.class))
+ .forEach(actions::add);
+ ArrayNode tables = result.putArray(TABLES);
+ pipeline.tables().stream()
+ .map(table -> context.encode(table, PiTableModel.class))
+ .forEach(tables::add);
+ return result;
+ }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiTableMatchFieldModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiTableMatchFieldModelCodec.java
new file mode 100644
index 0000000..a94bcba
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiTableMatchFieldModelCodec.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
+import org.onosproject.net.pi.model.PiHeaderModel;
+import org.onosproject.net.pi.model.PiTableMatchFieldModel;
+
+/**
+ * Codec for PiTableMatchFieldModel.
+ */
+public class PiTableMatchFieldModelCodec extends JsonCodec<PiTableMatchFieldModel> {
+ private static final String MATCH_TYPE = "matchType";
+ private static final String HEADER = "header";
+ private static final String FIELD = "field";
+ @Override
+ public ObjectNode encode(PiTableMatchFieldModel tableMatchField, CodecContext context) {
+ ObjectNode result = context.mapper().createObjectNode();
+ result.put(MATCH_TYPE, tableMatchField.matchType().toString());
+ PiHeaderModel header = tableMatchField.field().header();
+ PiHeaderFieldTypeModel field = tableMatchField.field().type();
+ ObjectNode headerData = context.encode(header, PiHeaderModel.class);
+ ObjectNode headerFieldData = context.encode(field, PiHeaderFieldTypeModel.class);
+ result.set(HEADER, headerData);
+ result.set(FIELD, headerFieldData);
+ return result;
+ }
+}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiTableModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiTableModelCodec.java
new file mode 100644
index 0000000..052e083
--- /dev/null
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiTableModelCodec.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.pi.model.PiTableMatchFieldModel;
+import org.onosproject.net.pi.model.PiTableModel;
+
+/**
+ * Codec for PiTableModel.
+ */
+public class PiTableModelCodec extends JsonCodec<PiTableModel> {
+ private static final String NAME = "name";
+ private static final String MAX_SIZE = "maxSize";
+ private static final String HAS_COUNTERS = "hasCounters";
+ private static final String SUPPORT_AGING = "supportAging";
+ private static final String ACTIONS = "actions";
+ private static final String MATCH_FIELDS = "matchFields";
+
+
+ @Override
+ public ObjectNode encode(PiTableModel table, CodecContext context) {
+
+ ObjectNode result = context.mapper().createObjectNode();
+
+ result.put(NAME, table.name());
+ result.put(MAX_SIZE, table.maxSize());
+ result.put(HAS_COUNTERS, table.hasCounters());
+ result.put(SUPPORT_AGING, table.supportsAging());
+
+ ArrayNode matchFields = result.putArray(MATCH_FIELDS);
+ table.matchFields().forEach(matchField -> {
+ ObjectNode matchFieldData =
+ context.encode(matchField, PiTableMatchFieldModel.class);
+ matchFields.add(matchFieldData);
+ });
+
+ ArrayNode actions = result.putArray(ACTIONS);
+ table.actions().forEach(action -> {
+ actions.add(action.name());
+ });
+
+ return result;
+ }
+}
diff --git a/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java b/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
index 93fa3f0..e1fea28 100644
--- a/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
+++ b/core/common/src/test/java/org/onosproject/store/trivial/SimpleHostStore.java
@@ -106,6 +106,7 @@
descr.vlan(),
descr.location(),
ImmutableSet.copyOf(descr.ipAddress()),
+ descr.configured(),
descr.annotations());
synchronized (this) {
hosts.put(hostId, newhost);
@@ -141,6 +142,7 @@
StoredHost updated = new StoredHost(providerId, host.id(),
host.mac(), host.vlan(),
descr.location(), addresses,
+ descr.configured(),
annotations);
event = new HostEvent(HOST_UPDATED, updated);
synchronized (this) {
@@ -257,8 +259,8 @@
*/
public StoredHost(ProviderId providerId, HostId id,
MacAddress mac, VlanId vlan, HostLocation location,
- Set<IpAddress> ips, Annotations... annotations) {
- super(providerId, id, mac, vlan, location, ips, annotations);
+ Set<IpAddress> ips, boolean configured, Annotations... annotations) {
+ super(providerId, id, mac, vlan, location, ips, configured, annotations);
this.location = location;
}
diff --git a/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java b/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java
index b097e2a..6d5abf1 100644
--- a/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java
+++ b/core/net/src/main/java/org/onosproject/cluster/impl/ConfigFileBasedClusterMetadataProvider.java
@@ -204,16 +204,21 @@
version = file.lastModified();
metadata = mapper.readValue(new FileInputStream(file), ClusterMetadata.class);
} else if ("http".equals(url.getProtocol())) {
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+ try {
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+ log.warn("Could not reach metadata URL {}. Retrying...", url);
+ return null;
+ }
+ if (conn.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) {
+ return null;
+ }
+ version = conn.getLastModified();
+ metadata = mapper.readValue(conn.getInputStream(), ClusterMetadata.class);
+ } catch (IOException e) {
log.warn("Could not reach metadata URL {}. Retrying...", url);
return null;
}
- if (conn.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) {
- return null;
- }
- version = conn.getLastModified();
- metadata = mapper.readValue(conn.getInputStream(), ClusterMetadata.class);
}
if (null == metadata) {
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java b/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
index 66159f7..b7a38e6 100644
--- a/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
@@ -464,10 +464,13 @@
store.removePendingHostLocation(probeMac);
}
+ /**
+ * Providers should only be able to remove a host that is provided by itself,
+ * or a host that is not configured.
+ */
private boolean allowedToChange(HostId hostId) {
- // Disallow removing inexistent host or host provided by others
Host host = store.getHost(hostId);
- return host != null && host.providerId().equals(provider().id());
+ return host == null || !host.configured() || host.providerId().equals(provider().id());
}
}
diff --git a/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java b/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java
index ba27dcb..f2beda1 100644
--- a/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java
+++ b/core/net/src/test/java/org/onosproject/net/host/impl/HostManagerTest.java
@@ -259,7 +259,8 @@
}
/**
- * Providers should only be able to remove a host provided by itself.
+ * Providers should only be able to remove a host that is provided by itself,
+ * or a host that is not configured.
*/
@Test
public void hostVanishedWithMultipleProviders() {
@@ -267,16 +268,15 @@
configured(HID2, MAC2, VLAN2, LOC2, IP2);
providerService2.hostVanished(HID1);
- assertNotNull("host should not be removed", mgr.getHost(HID1));
+ assertNull("Should be able to remove learnt host", mgr.getHost(HID1));
providerService.hostVanished(HID2);
- assertNotNull("host should not be removed", mgr.getHost(HID2));
-
- providerService.hostVanished(HID1);
- assertNull("host should be removed", mgr.getHost(HID1));
+ assertNotNull("Should not be able to remove configured host since the provider is different",
+ mgr.getHost(HID2));
providerService2.hostVanished(HID2);
- assertNull("host should be removed", mgr.getHost(HID2));
+ assertNull("Should be able to remove configured host when provider is the same",
+ mgr.getHost(HID2));
}
private void validateHosts(
diff --git a/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java b/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
index c64942f..532b279 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/app/DistributedApplicationStore.java
@@ -554,7 +554,7 @@
log.warn("Unable to fetch bits for application {} from node {}",
app.id().name(), node.id());
}
- }, executor);
+ }, messageHandlingExecutor);
}
try {
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java
index b7bb8f6..8d5668f 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/FederatedDistributedPrimitiveCreator.java
@@ -147,7 +147,7 @@
Map<PartitionId, AsyncDocumentTree<V>> trees =
Maps.transformValues(members, partition -> partition.<V>newAsyncDocumentTree(name, serializer));
Hasher<DocumentPath> hasher = key -> {
- int bucket = Math.abs(Hashing.murmur3_32().hashUnencodedChars(key.toString()).asInt()) % buckets;
+ int bucket = Math.abs(Hashing.murmur3_32().hashUnencodedChars(String.valueOf(key)).asInt()) % buckets;
return sortedMemberPartitionIds.get(Hashing.consistentHash(bucket, sortedMemberPartitionIds.size()));
};
return new PartitionedAsyncDocumentTree<>(name, trees, hasher);
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/PartitionedAsyncDocumentTree.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/PartitionedAsyncDocumentTree.java
index 3c53e30..71758a0 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/PartitionedAsyncDocumentTree.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/PartitionedAsyncDocumentTree.java
@@ -107,11 +107,16 @@
@Override
public CompletableFuture<Boolean> create(DocumentPath path, V value) {
+ if (path.parent() == null) {
+ // create value on root
+ return partition(path).createRecursive(path, value);
+ }
// TODO: This operation is not atomic
- return partition(path.parent()).get(path).thenCompose(parentValue -> {
+ return partition(path.parent()).get(path.parent()).thenCompose(parentValue -> {
if (parentValue == null) {
- return Tools.exceptionalFuture(new NoSuchDocumentPathException(path.parent().toString()));
+ return Tools.exceptionalFuture(new NoSuchDocumentPathException(String.valueOf(path.parent())));
} else {
+ // not atomic: parent did exist at some point, so moving forward
return partition(path).createRecursive(path, value);
}
});
diff --git a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/StoragePartitionServer.java b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/StoragePartitionServer.java
index 36b854f..bb071a2 100644
--- a/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/StoragePartitionServer.java
+++ b/core/store/primitives/src/main/java/org/onosproject/store/primitives/impl/StoragePartitionServer.java
@@ -40,7 +40,6 @@
private final Logger log = getLogger(getClass());
- private static final int MAX_ENTRIES_PER_LOG_SEGMENT = 32768;
private static final int MAX_SEGMENT_SIZE = 1024 * 1024 * 64;
private static final long ELECTION_TIMEOUT_MILLIS = 2500;
private static final long HEARTBEAT_INTERVAL_MILLIS = 1000;
@@ -108,7 +107,6 @@
.withStorageLevel(StorageLevel.MAPPED)
.withSerializer(new AtomixSerializerAdapter(Serializer.using(StorageNamespaces.RAFT_STORAGE)))
.withDirectory(dataFolder)
- .withMaxEntriesPerSegment(MAX_ENTRIES_PER_LOG_SEGMENT)
.withMaxSegmentSize(MAX_SEGMENT_SIZE)
.build());
StoragePartition.RAFT_SERVICES.forEach(builder::addService);
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 49e2969..6413037 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -186,6 +186,7 @@
import org.onosproject.net.intent.constraint.AnnotationConstraint;
import org.onosproject.net.intent.constraint.BandwidthConstraint;
import org.onosproject.net.intent.constraint.BooleanConstraint;
+import org.onosproject.net.intent.constraint.DomainConstraint;
import org.onosproject.net.intent.constraint.EncapsulationConstraint;
import org.onosproject.net.intent.constraint.HashedPathSelectionConstraint;
import org.onosproject.net.intent.constraint.LatencyConstraint;
@@ -590,6 +591,7 @@
.register(MarkerResource.class)
.register(new BitSetSerializer(), BitSet.class)
.register(DomainIntent.class)
+ .register(DomainConstraint.class)
.register(
// PI model
PiMatchType.class,
diff --git a/drivers/optical/src/test/resources/org/onosproject/driver/optical/config/flow_table_config.json b/drivers/optical/src/test/resources/org/onosproject/driver/optical/config/flow_table_config.json
index 3d8a8c3..ca08279 100644
--- a/drivers/optical/src/test/resources/org/onosproject/driver/optical/config/flow_table_config.json
+++ b/drivers/optical/src/test/resources/org/onosproject/driver/optical/config/flow_table_config.json
@@ -7,6 +7,7 @@
{
"id": "3",
"tableId": 0,
+ "tableName": "0",
"appId": "test",
"priority": 4,
"timeout": 0,
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
index 5fb0497..e14363e 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/DefaultP4Interpreter.java
@@ -80,6 +80,7 @@
public static final String TABLE0 = "table0";
public static final String TABLE0_COUNTER = "table0_counter";
+ public static final String ECMP = "ecmp";
public static final String SEND_TO_CPU = "send_to_cpu";
public static final String PORT = "port";
public static final String DROP = "_drop";
@@ -88,13 +89,15 @@
public static final String INGRESS_PORT = "ingress_port";
private static final PiTableId TABLE0_ID = PiTableId.of(TABLE0);
+ private static final PiTableId ECMP_ID = PiTableId.of(ECMP);
protected static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of("ethernet", "dstAddr");
protected static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of("ethernet", "srcAddr");
protected static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of("ethernet", "etherType");
private static final ImmutableBiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
- 0, TABLE0_ID);
+ 0, TABLE0_ID,
+ 1, ECMP_ID);
private static final ImmutableBiMap<PiTableId, PiCounterId> TABLE_COUNTER_MAP = ImmutableBiMap.of(
TABLE0_ID, PiCounterId.of(TABLE0_COUNTER, PiCounterType.DIRECT));
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
index 50b808e..c2cc0b5 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
@@ -76,8 +76,7 @@
/*
If true, we avoid querying the device and return the content of the ENTRY_STORE.
*/
- // TODO: can remove this check as soon as the BMv2 bug when reading ECMP entries is fixed.
- private boolean ignoreDeviceWhenGet = true;
+ private boolean ignoreDeviceWhenGet = false;
/*
If true, we read all direct counters of a table with one request. Otherwise, send as many request as the number of
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java
index 02d2274..b19ef61 100644
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java
+++ b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java
@@ -68,7 +68,7 @@
Map<String, PiHeaderModel> headerModelMap = Maps.newHashMap();
headerModels.stream()
.filter(Objects::nonNull)
- .forEach(hm -> headerModelMap.put(hm.type().name(), hm));
+ .forEach(hm -> headerModelMap.put(hm.name(), hm));
this.headerModels = ImmutableMap.copyOf(headerModelMap);
Map<String, PiActionModel> actionModelMap = Maps.newHashMap();
diff --git a/incubator/protobuf/models/src/main/proto/cluster/NodeIdProto.proto b/incubator/protobuf/models/src/main/proto/cluster/NodeIdProto.proto
new file mode 100644
index 0000000..2d062fd
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/cluster/NodeIdProto.proto
@@ -0,0 +1,8 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.cluster.models";
+
+package cluster;
+
+message NodeIdProto {
+ string node_id = 1;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/BandEnumsProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/BandEnumsProto.proto
new file mode 100644
index 0000000..10879a7
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/BandEnumsProto.proto
@@ -0,0 +1,28 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+/**
+* Specifies the type of band.
+*/
+enum BandTypeProto {
+ /**
+ * Simple rate limiter which drops packets
+ * when the rate is exceeded.
+ */
+ DROP = 0;
+
+ /**
+ * defines a simple DiffServ policer that remark
+ * the drop precedence of the DSCP field in the
+ * IP header of the packets that exceed the band
+ * rate value.
+ */
+ REMARK = 1;
+
+ /**
+ * defines an experimental meter band.
+ */
+ EXPERIMENTAL = 2;
+}
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/BandProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/BandProto.proto
new file mode 100644
index 0000000..ca7f28b
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/BandProto.proto
@@ -0,0 +1,16 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+import "net/meter/BandEnumsProto.proto";
+
+// Corresponds to org.onosproject.net.meter.Band.
+message BandProto {
+ uint64 rate = 1;
+ uint64 burst = 2;
+ uint32 drop_precedence = 3;
+ BandTypeProto type = 4;
+ uint64 packets = 5;
+ uint64 bytes = 6;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/MeterEnumsProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/MeterEnumsProto.proto
new file mode 100644
index 0000000..3d1a67f
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/MeterEnumsProto.proto
@@ -0,0 +1,66 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+enum MeterUnitProto {
+ /**
+ * Packets per second.
+ */
+ PKTS_PER_SEC = 0;
+
+ /**
+ * Kilo bits per second.
+ */
+ KB_PER_SEC = 1;
+}
+
+enum MeterStateProto {
+ /**
+ * The meter is in the process of being added.
+ */
+ PENDING_ADD = 0;
+
+ /**
+ * THe meter has been added.
+ */
+ ADDED = 1;
+
+ /**
+ * The meter is in the process of being removed.
+ */
+ PENDING_REMOVE = 2;
+
+ /**
+ * The meter has been removed.
+ */
+ REMOVED = 3;
+}
+
+enum MeterRequestTypeProto {
+ ADD = 0;
+ MODIFY = 1;
+ REMOVE = 2;
+}
+
+enum MeterEventTypeProto {
+ /**
+ * A meter addition was requested.
+ */
+ METER_ADD_REQ = 0;
+
+ /**
+ * A meter removal was requested.
+ */
+ METER_REM_REQ = 1;
+
+ /**
+ * A meter was finally added to device.
+ */
+ METER_ADDED = 2;
+
+ /**
+ * A meter was finally removed from device.
+ */
+ METER_REMOVED = 3;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/MeterEventProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/MeterEventProto.proto
new file mode 100644
index 0000000..54ad1cc
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/MeterEventProto.proto
@@ -0,0 +1,13 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+import "net/meter/MeterEnumsProto.proto";
+import "net/meter/MeterProto.proto";
+
+// Corresponds to org.onosproject.net.meter.MeterEvent.
+message MeterNotificationProto {
+ MeterEventTypeProto type = 1;
+ MeterProto meter = 2;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/MeterProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/MeterProto.proto
new file mode 100644
index 0000000..02be225
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/MeterProto.proto
@@ -0,0 +1,22 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+import "net/meter/BandProto.proto";
+import "net/meter/MeterEnumsProto.proto";
+import "core/ApplicationIdProto.proto";
+
+message MeterProto {
+ string device_id = 1;
+ uint64 meter_id = 2;
+ core.ApplicationIdProto application_id = 3;
+ MeterUnitProto unit = 4;
+ bool is_burst = 5;
+ repeated BandProto bands = 6;
+ MeterStateProto state = 7;
+ uint64 life = 8;
+ uint64 reference_count = 9;
+ uint64 packets_seen = 10;
+ uint64 bytes_seen = 11;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/models/src/main/proto/net/meter/MeterRequestProto.proto b/incubator/protobuf/models/src/main/proto/net/meter/MeterRequestProto.proto
new file mode 100644
index 0000000..5fdebb6
--- /dev/null
+++ b/incubator/protobuf/models/src/main/proto/net/meter/MeterRequestProto.proto
@@ -0,0 +1,17 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.net.meter.models";
+
+package net.meter;
+
+import "net/meter/BandProto.proto";
+import "net/meter/MeterEnumsProto.proto";
+import "core/ApplicationIdProto.proto";
+
+message MeterRequestProto {
+ string device_id = 1;
+ core.ApplicationIdProto application_id = 2;
+ MeterUnitProto unit = 3;
+ bool is_burst = 4;
+ repeated BandProto bands = 5;
+ MeterRequestTypeProto type = 6;
+}
\ No newline at end of file
diff --git a/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/GrpcNbMeterService.java b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/GrpcNbMeterService.java
new file mode 100644
index 0000000..64ced8a
--- /dev/null
+++ b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/GrpcNbMeterService.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.grpc.nb.net.meter;
+
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.net.meter.MeterService;
+import org.onosproject.grpc.nb.utils.GrpcNbMeterServiceUtil;
+
+import org.onosproject.grpc.nb.net.meter.MeterServiceGrpc.MeterServiceImplBase;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.submitRequest;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.submitReply;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.withdrawRequest;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.withdrawReply;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getMeterRequest;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getMeterReply;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getAllMetersRequest;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getAllMetersReply;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getMetersRequest;
+import org.onosproject.grpc.nb.net.meter.MeterServiceNbProto.getMetersReply;
+
+import io.grpc.stub.StreamObserver;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Deactivate;
+import com.google.common.annotations.Beta;
+import org.apache.felix.scr.annotations.Component;
+
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.DeviceId;
+
+/**
+ * A server that provides access to the methods exposed by {@link MeterService}.
+ * TODO this requires major refactoring, translation should be delegated to calls to
+ * TODO{@link GrpcNbMeterServiceUtil}.
+ */
+@Beta
+@Component(immediate = true)
+public class GrpcNbMeterService {
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MeterService meterService;
+
+ @Activate
+ public void activate() {
+ //TODO this should contact the registry service and register an instance
+ // of this service.
+ }
+
+ @Deactivate
+ public void deactivate() {
+ }
+
+ private class MeterServiceNbServerInternal extends MeterServiceImplBase {
+
+ public MeterServiceNbServerInternal() {
+ super();
+ }
+
+ @Override
+ public void submit(submitRequest request,
+ StreamObserver<submitReply> responseObserver) {
+ submitReply.Builder replyBuilder = submitReply.newBuilder();
+ Meter meter = meterService.submit(GrpcNbMeterServiceUtil.translate(request.getMeter()));
+ responseObserver.onNext(replyBuilder.setSubmitMeter(GrpcNbMeterServiceUtil.translate(meter)).build());
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void withdraw(withdrawRequest request,
+ StreamObserver<withdrawReply> responseObserver) {
+ withdrawReply.Builder replyBuilder = withdrawReply.newBuilder();
+ meterService.withdraw(GrpcNbMeterServiceUtil.translate(request.getMeter()),
+ MeterId.meterId(request.getMeterId()));
+ responseObserver.onNext(replyBuilder.build());
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void getMeter(getMeterRequest request,
+ StreamObserver<getMeterReply> responseObserver) {
+ getMeterReply.Builder replyBuilder = getMeterReply.newBuilder();
+ Meter meter = meterService.getMeter(DeviceId.deviceId(request.getDeviceId()),
+ MeterId.meterId(request.getMeterId()));
+ responseObserver.onNext(replyBuilder.setMeter(GrpcNbMeterServiceUtil.translate(meter)).build());
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void getAllMeters(getAllMetersRequest request,
+ StreamObserver<getAllMetersReply> responseObserver) {
+ getAllMetersReply.Builder replyBuilder = getAllMetersReply.newBuilder();
+ meterService.getAllMeters().forEach(d -> {
+ replyBuilder.addMeters(GrpcNbMeterServiceUtil.translate(d));
+ });
+ responseObserver.onNext(replyBuilder.build());
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void getMeters(getMetersRequest request,
+ StreamObserver<getMetersReply> responseObserver) {
+ getMetersReply.Builder replyBuilder = getMetersReply.newBuilder();
+ meterService.getMeters(DeviceId.deviceId(request.getDeviceId())).forEach(d -> {
+ replyBuilder.addMeters(GrpcNbMeterServiceUtil.translate(d));
+ });
+ responseObserver.onNext(replyBuilder.build());
+ responseObserver.onCompleted();
+ }
+ }
+}
+
diff --git a/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/package-info.java b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/package-info.java
new file mode 100644
index 0000000..3b63c95
--- /dev/null
+++ b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/net/meter/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * gRPC server implementations for northbound services.
+ */
+package org.onosproject.grpc.nb.net.meter;
\ No newline at end of file
diff --git a/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/GrpcNbMeterServiceUtil.java b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/GrpcNbMeterServiceUtil.java
new file mode 100644
index 0000000..43c4908
--- /dev/null
+++ b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/GrpcNbMeterServiceUtil.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.grpc.nb.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.onosproject.net.meter.Band;
+import org.onosproject.net.meter.MeterRequest;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.meter.DefaultBand;
+import org.onosproject.net.meter.DefaultMeterRequest;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterState;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.grpc.core.models.ApplicationIdProtoOuterClass.ApplicationIdProto;
+import org.onosproject.grpc.net.meter.models.BandProtoOuterClass.BandProto;
+import org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterRequestTypeProto;
+import org.onosproject.grpc.net.meter.models.MeterRequestProtoOuterClass.MeterRequestProto;
+import org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterUnitProto;
+import org.onosproject.grpc.net.meter.models.BandEnumsProto.BandTypeProto;
+import org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto;
+import org.onosproject.grpc.net.meter.models.MeterProtoOuterClass.MeterProto;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.stream.Collectors;
+import com.google.common.annotations.Beta;
+
+/**
+ * gRPC message conversion related utilities for meter service.
+ */
+@Beta
+public final class GrpcNbMeterServiceUtil {
+ private static final Logger log = LoggerFactory.getLogger(GrpcNbMeterServiceUtil.class);
+
+ /**
+ * Translates gRPC ApplicationId to {@link ApplicationId}.
+ *
+ * @param gAppId gRPC message
+ * @return {@link ApplicationId}
+ */
+ public static ApplicationId translate(ApplicationIdProto gAppId) {
+ int id = gAppId.getId();
+ String name = gAppId.getName();
+ ApplicationId appId = new DefaultApplicationId(id, name);
+ return appId;
+ }
+
+ /**
+ * Translates gRPC Band to {@link Band}.
+ *
+ * @param gBand gRPC message
+ * @return {@link Band}
+ */
+ public static Band translate(BandProto gBand) {
+ Band.Type type = translate(gBand.getType());
+ long rate = gBand.getRate();
+ long burstSize = gBand.getBurst();
+ short prec = (short) gBand.getDropPrecedence();
+ Band band = new DefaultBand(type, rate, burstSize, prec);
+ return band;
+ }
+
+ /**
+ * Translates gRPC List Bands to Collection Band.
+ *
+ * @param listBands gRPC message
+ * @return Collection Band
+ */
+ public static Collection<Band> translate(java.util.List<BandProto> listBands) {
+ Collection<Band> bands = new ArrayList<>();
+ listBands.forEach(d -> {
+ bands.add(translate(d));
+ });
+ return bands;
+ }
+
+ /**
+ * Translates gRPC MeterRequestType to {@link MeterRequest.Type}.
+ *
+ * @param type gRPC message
+ * @return {@link MeterRequest.Type}
+ */
+ public static MeterRequest.Type translate(MeterRequestTypeProto type) {
+ switch (type) {
+ case ADD:
+ return MeterRequest.Type.ADD;
+ case MODIFY:
+ return MeterRequest.Type.MODIFY;
+ case REMOVE:
+ return MeterRequest.Type.REMOVE;
+ case UNRECOGNIZED:
+ log.warn("Unrecognized MeterRequest type gRPC message: {}", type);
+ return null;
+ default:
+ log.warn("Unrecognized MeterRequest type gRPC message: {}", type);
+ return null;
+ }
+ }
+
+ /**
+ * Translates gRPC MeterRequest to {@link MeterRequest}.
+ *
+ * @param meterRequest gRPC message
+ * @return {@link MeterRequest}
+ */
+ public static MeterRequest translate(MeterRequestProto meterRequest) {
+
+ DeviceId deviceid = DeviceId.deviceId(meterRequest.getDeviceId());
+ ApplicationId appId = translate(meterRequest.getApplicationId());
+ Meter.Unit unit = translate(meterRequest.getUnit());
+ boolean burst = meterRequest.getIsBurst();
+ Collection<Band> bands = translate(meterRequest.getBandsList());
+ MeterRequest.Type type = translate(meterRequest.getType());
+ if (type == MeterRequest.Type.ADD) {
+ return DefaultMeterRequest.builder()
+ .forDevice(deviceid)
+ .fromApp(appId)
+ .withUnit(unit)
+ .withBands(bands)
+ .add();
+ } else {
+ return DefaultMeterRequest.builder()
+ .forDevice(deviceid)
+ .fromApp(appId)
+ .withUnit(unit)
+ .withBands(bands)
+ .remove();
+ }
+ }
+
+ /**
+ * Translates {@link ApplicationId} to gRPC ApplicationId message.
+ *
+ * @param appId {@link ApplicationId}
+ * @return gRPC ApplicationId message
+ */
+ public static ApplicationIdProto translate(ApplicationId appId) {
+ return ApplicationIdProto.newBuilder()
+ .setId(appId.id())
+ .setName(appId.name())
+ .build();
+ }
+
+ /**
+ * Translates gRPC enum MeterUnit to ONOS enum.
+ *
+ * @param unit meterUnit in gRPC enum
+ * @return equivalent in ONOS enum
+ */
+ public static Meter.Unit translate(MeterUnitProto unit) {
+ switch (unit) {
+ case PKTS_PER_SEC:
+ return Meter.Unit.PKTS_PER_SEC;
+ case KB_PER_SEC:
+ return Meter.Unit.KB_PER_SEC;
+ case UNRECOGNIZED:
+ log.warn("Unrecognized MeterUnit gRPC message: {}", unit);
+ return null;
+ default:
+ log.warn("Unrecognized MeterUnit gRPC message: {}", unit);
+ return null;
+ }
+ }
+
+ /**
+ * Translates ONOS enum Meter.Unit Type to gRPC enum.
+ *
+ * @param unit Meter.Unit in ONOS enum
+ * @return equivalent in gRPC enum
+ */
+ public static MeterUnitProto translate(Meter.Unit unit) {
+ switch (unit) {
+ case PKTS_PER_SEC:
+ return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterUnitProto.PKTS_PER_SEC;
+ case KB_PER_SEC:
+ return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterUnitProto.KB_PER_SEC;
+ default:
+ log.warn("Unrecognized MeterUnit ONOS message: {}", unit);
+ return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterUnitProto.UNRECOGNIZED;
+ }
+ }
+
+ /**
+ * Translates gRPC enum Band Type to ONOS enum.
+ *
+ * @param bandType BandType in gRPC enum
+ * @return equivalent in ONOS enum
+ */
+ public static Band.Type translate(BandTypeProto bandType) {
+ switch (bandType) {
+ case DROP:
+ return Band.Type.DROP;
+ case REMARK:
+ return Band.Type.REMARK;
+ case EXPERIMENTAL:
+ return Band.Type.EXPERIMENTAL;
+ case UNRECOGNIZED:
+ log.warn("Unrecognized BandType gRPC message: {}", bandType);
+ return null;
+ default:
+ log.warn("Unrecognized BandType gRPC message: {}", bandType);
+ return null;
+ }
+ }
+
+ /**
+ * Translates ONOS enum Band Type to gRPC enum.
+ *
+ * @param bandType BandType in ONOS enum
+ * @return equivalent in gRPC enum
+ */
+ public static BandTypeProto translate(Band.Type bandType) {
+ switch (bandType) {
+ case DROP:
+ return org.onosproject.grpc.net.meter.models.BandEnumsProto.BandTypeProto.DROP;
+ case REMARK:
+ return org.onosproject.grpc.net.meter.models.BandEnumsProto.BandTypeProto.REMARK;
+ case EXPERIMENTAL:
+ return org.onosproject.grpc.net.meter.models.BandEnumsProto.BandTypeProto.EXPERIMENTAL;
+ default:
+ log.warn("Unrecognized BandType ONOS message: {}", bandType);
+ return null;
+ }
+ }
+
+ /**
+ * Translates ONOS enum MeterState Type to gRPC enum.
+ *
+ * @param meterState MeterState in ONOS enum
+ * @return equivalent in gRPC enum
+ */
+ public static MeterStateProto translate(MeterState meterState) {
+ switch (meterState) {
+ case PENDING_ADD:
+ return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto.PENDING_ADD;
+ case ADDED:
+ return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto.ADDED;
+ case PENDING_REMOVE:
+ return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto.PENDING_REMOVE;
+ case REMOVED:
+ return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto.REMOVED;
+ default:
+ log.warn("Unrecognized MeterState ONOS message: {}", meterState);
+ return org.onosproject.grpc.net.meter.models.MeterEnumsProto.MeterStateProto.UNRECOGNIZED;
+ }
+ }
+
+ /**
+ * Translates {@link Meter} to gRPC MeterCore message.
+ *
+ * @param meter {@link Meter}
+ * @return gRPC MeterCore message
+ */
+ public static MeterProto translate(Meter meter) {
+ return MeterProto.newBuilder()
+ .setDeviceId(meter.deviceId().toString())
+ .setApplicationId(translate(meter.appId()))
+ .setUnit(translate(meter.unit()))
+ .setIsBurst(meter.isBurst())
+ .addAllBands(meter.bands().stream()
+ .map(b -> BandProto.newBuilder()
+ .setRate(b.rate())
+ .setBurst(b.burst())
+ .setDropPrecedence(b.dropPrecedence())
+ .setType(translate(b.type()))
+ .setPackets(b.packets())
+ .setBytes(b.bytes())
+ .build())
+ .collect(Collectors.toList()))
+ .setState(translate(meter.state()))
+ .setLife(meter.life())
+ .setReferenceCount(meter.referenceCount())
+ .setPacketsSeen(meter.packetsSeen())
+ .setBytesSeen(meter.bytesSeen())
+ .build();
+ }
+
+ // Utility class not intended for instantiation.
+ private GrpcNbMeterServiceUtil() {
+ }
+}
diff --git a/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/package-info.java b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/package-info.java
new file mode 100644
index 0000000..341744c
--- /dev/null
+++ b/incubator/protobuf/services/nb/src/main/java/org/onosproject/grpc/nb/utils/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A package contains a set of utilities that are used to convert gRPC model
+ * object to ONOS data model object.
+ */
+package org.onosproject.grpc.nb.utils;
\ No newline at end of file
diff --git a/incubator/protobuf/services/nb/src/main/proto/net/meter/MeterServiceNbProto.proto b/incubator/protobuf/services/nb/src/main/proto/net/meter/MeterServiceNbProto.proto
new file mode 100644
index 0000000..0289f26
--- /dev/null
+++ b/incubator/protobuf/services/nb/src/main/proto/net/meter/MeterServiceNbProto.proto
@@ -0,0 +1,55 @@
+syntax = "proto3";
+option java_package = "org.onosproject.grpc.nb.net.meter";
+
+package nb.net.meter;
+
+import "net/meter/MeterProto.proto";
+import "net/meter/MeterRequestProto.proto";
+
+message submitRequest {
+ .net.meter.MeterRequestProto meter = 1;
+}
+
+message submitReply {
+ .net.meter.MeterProto submit_meter = 1;
+}
+
+message withdrawRequest {
+ .net.meter.MeterRequestProto meter = 1;
+ uint64 meter_id = 2;
+}
+
+message withdrawReply {
+}
+
+message getMeterRequest {
+ string device_id = 1;
+ uint64 meter_id = 2;
+}
+
+message getMeterReply {
+ .net.meter.MeterProto meter = 1;
+}
+
+message getAllMetersRequest {
+}
+
+message getAllMetersReply {
+ repeated .net.meter.MeterProto meters = 1;
+}
+
+message getMetersRequest {
+ string device_id = 1;
+}
+
+message getMetersReply {
+ repeated .net.meter.MeterProto meters = 1;
+}
+
+service MeterService {
+ rpc submit(submitRequest) returns (submitReply) {}
+ rpc withdraw(withdrawRequest) returns (withdrawReply) {}
+ rpc getMeter(getMeterRequest) returns (getMeterReply) {}
+ rpc getAllMeters(getAllMetersRequest) returns (getAllMetersReply) {}
+ rpc getMeters(getMetersRequest) returns (getMetersReply) {}
+}
\ No newline at end of file
diff --git a/modules.defs b/modules.defs
index 589b236..87428a3 100644
--- a/modules.defs
+++ b/modules.defs
@@ -221,6 +221,8 @@
'//apps/evpn-route-service:onos-apps-evpn-route-service-oar',
'//incubator/protobuf/registry:onos-incubator-protobuf-registry-oar',
'//apps/openstacknetworkingui:onos-apps-openstacknetworkingui-oar',
+ '//apps/p4-tutorial/pipeconf:onos-apps-p4-tutorial-pipeconf-oar',
+ '//apps/p4-tutorial/icmpdropper:onos-apps-p4-tutorial-icmpdropper-oar',
]
PROTOCOL_APPS = [
diff --git a/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfSessionMinaImpl.java b/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfSessionMinaImpl.java
index dfb82ca..99f79cd 100644
--- a/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfSessionMinaImpl.java
+++ b/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/impl/NetconfSessionMinaImpl.java
@@ -578,7 +578,7 @@
* @return XML RPC message
*/
private String formatXmlHeader(String request) {
- if (!request.startsWith(XML_HEADER)) {
+ if (!request.contains(XML_HEADER)) {
//FIXME if application provides his own XML header of different type there is a clash
if (request.startsWith(LF + HASH)) {
request = request.split("<")[0] + XML_HEADER + request.substring(request.split("<")[0].length());
diff --git a/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfSessionImplTest.java b/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfSessionImplTest.java
index e0386f7..51bf91c 100644
--- a/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfSessionImplTest.java
+++ b/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/impl/NetconfSessionImplTest.java
@@ -667,7 +667,7 @@
Pattern.DOTALL);
public static final Pattern HELLO_REQ_PATTERN_1_1 =
- Pattern.compile("(<\\?xml).*"
+ Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
+ "(<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
+ "( *)(<capabilities>)\\R?"
+ "( *)(<capability>urn:ietf:params:netconf:base:1.0</capability>)\\R?"
@@ -677,7 +677,7 @@
Pattern.DOTALL);
public static final Pattern EDIT_CONFIG_REQ_PATTERN =
- Pattern.compile("(<\\?xml).*"
+ Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
+ "(<rpc message-id=\")[0-9]*(\") *(xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
+ "(<edit-config>)\\R?"
+ "(<target>\\R?((<" + DatastoreId.CANDIDATE.toString() + "/>)|"
@@ -689,7 +689,7 @@
public static final Pattern LOCK_REQ_PATTERN =
- Pattern.compile("(<\\?xml).*"
+ Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
+ "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
+ "message-id=\")[0-9]*(\">)\\R?"
+ "(<lock>)\\R?"
@@ -699,7 +699,7 @@
+ "(</lock>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
public static final Pattern UNLOCK_REQ_PATTERN =
- Pattern.compile("(<\\?xml).*"
+ Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
+ "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
+ "message-id=\")[0-9]*(\">)\\R?"
+ "(<unlock>)\\R?"
@@ -709,7 +709,7 @@
+ "(</unlock>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
public static final Pattern COPY_CONFIG_REQ_PATTERN =
- Pattern.compile("(<\\?xml).*"
+ Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
+ "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\")[0-9]*(\">)\\R?"
+ "(<copy-config>)\\R?"
+ "(<target>\\R?"
@@ -730,7 +730,7 @@
+ "(</copy-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
public static final Pattern GET_CONFIG_REQ_PATTERN =
- Pattern.compile("(<\\?xml).*"
+ Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
+ "(<rpc message-id=\")[0-9]*(\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
+ "(<get-config>)\\R?" + "(<source>)\\R?((<"
+ DatastoreId.CANDIDATE.toString()
@@ -743,7 +743,7 @@
public static final Pattern GET_REQ_PATTERN =
- Pattern.compile("(<\\?xml).*"
+ Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
+ "(<rpc message-id=\")[0-9]*(\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
+ "(<get>)\\R?"
+ "(<filter type=\"subtree\">).*(</filter>)\\R?"
diff --git a/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java b/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
index 40c5370..6fd0ff6 100644
--- a/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
+++ b/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
@@ -79,6 +79,7 @@
import java.util.Collections;
import java.util.Dictionary;
import java.util.List;
+import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -345,17 +346,22 @@
} else {
log.info("Connecting to device {} with driver {}", deviceId, basicDeviceConfig.driver());
- Driver driver = driverService.getDriver(basicDeviceConfig.driver());
+ Driver driver;
+ try {
+ driver = driverService.getDriver(basicDeviceConfig.driver());
+ } catch (ItemNotFoundException e) {
+ log.warn("The driver of {} is not found : {}", deviceId, e.getMessage());
+ return;
+ }
+
DriverData driverData = new DefaultDriverData(driver, deviceId);
-
- DeviceHandshaker handshaker =
- getBehaviour(driver, DeviceHandshaker.class, driverData);
-
+ DeviceHandshaker handshaker = getBehaviour(driver, DeviceHandshaker.class, driverData);
if (handshaker == null) {
log.error("Device {}, with driver {} does not support DeviceHandshaker " +
"behaviour, {}", deviceId, driver.name(), driver.behaviours());
return;
}
+
//Storing deviceKeyId and all other config values
// as data in the driver with protocol_<info>
// name as the key. e.g protocol_ip
@@ -511,12 +517,17 @@
}
private void updatePortStatistics(DeviceId deviceId) {
- Collection<PortStatistics> statistics = deviceService.getDevice(deviceId)
- .as(PortStatisticsDiscovery.class)
- .discoverPortStatistics();
- //updating statistcs only if not empty
- if (!statistics.isEmpty()) {
- providerService.updatePortStatistics(deviceId, statistics);
+ Device device = deviceService.getDevice(deviceId);
+ if (!Objects.isNull(device) && deviceService.isAvailable(deviceId) &&
+ device.is(PortStatisticsDiscovery.class)) {
+ Collection<PortStatistics> statistics = device.as(PortStatisticsDiscovery.class)
+ .discoverPortStatistics();
+ //updating statistcs only if not empty
+ if (!statistics.isEmpty()) {
+ providerService.updatePortStatistics(deviceId, statistics);
+ }
+ } else {
+ log.debug("Can't update port statistics for device {}", deviceId);
}
}
@@ -539,7 +550,7 @@
log.debug("{} is not my scheme, skipping", deviceId);
return;
}
- if (deviceService.getDevice(deviceId) != null || deviceService.isAvailable(deviceId)) {
+ if (deviceService.getDevice(deviceId) != null && deviceService.isAvailable(deviceId)) {
log.info("Device {} is already connected to ONOS and is available", deviceId);
return;
}
@@ -628,19 +639,17 @@
@Override
public void event(DeviceEvent event) {
Type type = event.type();
+ DeviceId deviceId = event.subject().id();
if (type.equals((Type.DEVICE_ADDED))) {
//For now this is scheduled periodically, when streaming API will
// be available we check and base it on the streaming API (e.g. gNMI)
- if (deviceService.getDevice(event.subject().id()).
- is(PortStatisticsDiscovery.class)) {
- scheduledTasks.put(event.subject().id(), schedulePolling(event.subject().id(), false));
- updatePortStatistics(event.subject().id());
- }
+ scheduledTasks.put(deviceId, schedulePolling(deviceId, false));
+ updatePortStatistics(deviceId);
} else if (type.equals(Type.DEVICE_REMOVED)) {
connectionExecutor.submit(exceptionSafe(() ->
- disconnectDevice(event.subject().id())));
+ disconnectDevice(deviceId)));
}
}
diff --git a/tools/dev/bin/onos-setup-p4-dev b/tools/dev/bin/onos-setup-p4-dev
index b8483c5..38ba91b 100755
--- a/tools/dev/bin/onos-setup-p4-dev
+++ b/tools/dev/bin/onos-setup-p4-dev
@@ -74,6 +74,8 @@
tcpdump \
wget \
unzip
+
+ sudo pip install setuptools cffi
}
function do_requirements_1404 {
@@ -139,6 +141,30 @@
unset LDFLAGS
}
+function checkout_bmv2 {
+ cd ${BUILD_DIR}
+ if [ ! -d bmv2 ]; then
+ git clone https://github.com/p4lang/behavioral-model.git bmv2
+ fi
+ cd bmv2
+ git fetch
+ git checkout ${BMV2_COMMIT}
+}
+
+function do_pi_bmv2_deps {
+ checkout_bmv2
+ # From bmv2's install_deps.sh.
+ # Nanomsg is required also by p4runtime.
+ tmpdir=`mktemp -d -p .`
+ cd ${tmpdir}
+ bash ../travis/install-thrift.sh
+ bash ../travis/install-nanomsg.sh
+ sudo ldconfig
+ bash ../travis/install-nnpy.sh
+ cd ..
+ sudo rm -rf $tmpdir
+}
+
function do_p4runtime {
cd ${BUILD_DIR}
if [ ! -d p4runtime ]; then
@@ -157,23 +183,7 @@
}
function do_bmv2 {
- cd ${BUILD_DIR}
- if [ ! -d bmv2 ]; then
- git clone https://github.com/p4lang/behavioral-model.git bmv2
- fi
- cd bmv2
- git fetch
- git checkout ${BMV2_COMMIT}
-
- # From bmv2's install_deps.sh
- tmpdir=`mktemp -d -p .`
- cd ${tmpdir}
- bash ../travis/install-thrift.sh
- bash ../travis/install-nanomsg.sh
- sudo ldconfig
- bash ../travis/install-nnpy.sh
- cd ..
- sudo rm -rf $tmpdir
+ checkout_bmv2
./autogen.sh
./configure --enable-debugger --with-pi 'CXXFLAGS=-O0 -g'
@@ -234,9 +244,10 @@
commit_id="$1"
proj_dir="$2"
func_name="$3"
+ simple_name="$4"
if ${MUST_DO_ALL} = true || check_commit ${commit_id} ${proj_dir}/.last_built_commit; then
echo "#"
- echo "# Building ${proj_dir} (${commit_id})"
+ echo "# Building ${simple_name} (${commit_id})"
echo "#"
# Print commands used to install to aid debugging
set -x
@@ -268,10 +279,11 @@
mkdir -p ${BUILD_DIR}
cd ${BUILD_DIR}
# In dependency order.
-check_and_do ${PROTOBUF_COMMIT} protobuf do_protobuf
-check_and_do ${GRPC_COMMIT} grpc do_grpc
-check_and_do ${PI_COMMIT} p4runtime do_p4runtime
-check_and_do ${BMV2_COMMIT} bmv2 do_bmv2
-check_and_do ${P4C_COMMIT} p4c do_p4c
+check_and_do ${PROTOBUF_COMMIT} protobuf do_protobuf protobuf
+check_and_do ${GRPC_COMMIT} grpc do_grpc grpc
+check_and_do ${BMV2_COMMIT} bmv2 do_pi_bmv2_deps bmv2-deps
+check_and_do ${PI_COMMIT} p4runtime do_p4runtime p4runtime
+check_and_do ${BMV2_COMMIT} bmv2 do_bmv2 bmv2
+check_and_do ${P4C_COMMIT} p4c do_p4c p4c
echo "Done!"
diff --git a/tools/gui/gulp-tasks/bundles/bundle-css/index.js b/tools/gui/gulp-tasks/bundles/bundle-css/index.js
index 0c5c8d6..69c4c70 100644
--- a/tools/gui/gulp-tasks/bundles/bundle-css/index.js
+++ b/tools/gui/gulp-tasks/bundles/bundle-css/index.js
@@ -1,6 +1,7 @@
import gulp from 'gulp';
import concat from 'gulp-concat';
import BundleResources from '../helpers/bundleResources';
+import { reload } from '../../dev-server';
const GUI_BASE = '../../web/gui/src/main/webapp/';
const bundleFiles = [
@@ -11,10 +12,18 @@
'app/view/**/*.css',
];
-const task = gulp.task('bundle-css', function () {
- return gulp.src(BundleResources(GUI_BASE, bundleFiles))
- .pipe(concat('onos.css'))
- .pipe(gulp.dest(GUI_BASE + '/dist/'));
-});
+const task = () => {
-export default task;
\ No newline at end of file
+ gulp.task('bundle-css', function () {
+ return gulp.src(BundleResources(GUI_BASE, bundleFiles))
+ .pipe(concat('onos.css'))
+ .pipe(gulp.dest(GUI_BASE + '/dist/'))
+ .on('end', () => { reload(); });
+ });
+
+ gulp.task('watch-css', () => {
+ gulp.watch([GUI_BASE + 'app/**/*.css'], ['bundle-css']);
+ });
+}
+
+export default task();
\ No newline at end of file
diff --git a/tools/gui/gulp-tasks/dev-server/index.js b/tools/gui/gulp-tasks/dev-server/index.js
index 119c3f2..135ee9f 100644
--- a/tools/gui/gulp-tasks/dev-server/index.js
+++ b/tools/gui/gulp-tasks/dev-server/index.js
@@ -42,6 +42,7 @@
ws: true,
middleware: [
proxy(['**/*.js', '!/onos/ui/onos.js'], { target: 'http://localhost:8189' }),
+ proxy(['**/*.css'], { target: 'http://localhost:8189' }),
proxy('**/*.js.map', {
target: 'http://localhost:8189',
changeOrigin: true,
diff --git a/tools/gui/gulpfile.babel.js b/tools/gui/gulpfile.babel.js
index 24327e0..51aead1 100644
--- a/tools/gui/gulpfile.babel.js
+++ b/tools/gui/gulpfile.babel.js
@@ -3,4 +3,4 @@
gulp.task('build', ['bower', 'bundle-css', 'bundle-js']);
gulp.task('tests', ['bower', 'test']);
-gulp.task('default', ['bundle-js', 'serve', 'watch-js']);
\ No newline at end of file
+gulp.task('default', ['bundle-js', 'bundle-css', 'serve', 'watch-js', 'watch-css']);
\ No newline at end of file
diff --git a/tools/test/p4src/p4-16/include/defines.p4 b/tools/test/p4src/p4-16/include/defines.p4
index ea6747a..c8640a8 100644
--- a/tools/test/p4src/p4-16/include/defines.p4
+++ b/tools/test/p4src/p4-16/include/defines.p4
@@ -17,7 +17,7 @@
#ifndef DEFINES
#define DEFINES
-#define MAX_PORTS 254
+#define MAX_PORTS 255
#define ETH_TYPE_IPV4 16w0x800
#define IP_TYPE_TCP 8w6
diff --git a/tools/test/p4src/p4-16/include/port_counters.p4 b/tools/test/p4src/p4-16/include/port_counters.p4
index 15ed02f..d8667a3 100644
--- a/tools/test/p4src/p4-16/include/port_counters.p4
+++ b/tools/test/p4src/p4-16/include/port_counters.p4
@@ -24,9 +24,11 @@
apply {
if (standard_metadata.egress_spec < MAX_PORTS) {
- ingress_port_counter.count((bit<32>)standard_metadata.ingress_port);
egress_port_counter.count((bit<32>)standard_metadata.egress_spec);
}
+ if (standard_metadata.ingress_port < MAX_PORTS) {
+ ingress_port_counter.count((bit<32>)standard_metadata.ingress_port);
+ }
}
}
#endif
diff --git a/tools/test/p4src/p4-16/p4c-out/default.json b/tools/test/p4src/p4-16/p4c-out/default.json
index 2dd5c49..241cbe1 100644
--- a/tools/test/p4src/p4-16/p4c-out/default.json
+++ b/tools/test/p4src/p4-16/p4c-out/default.json
@@ -447,7 +447,7 @@
"column" : 38,
"source_fragment" : "egress_port_counter"
},
- "size" : 254,
+ "size" : 255,
"is_direct" : false
},
{
@@ -459,7 +459,7 @@
"column" : 38,
"source_fragment" : "ingress_port_counter"
},
- "size" : 254,
+ "size" : 255,
"is_direct" : false
}
],
@@ -3248,51 +3248,6 @@
"op" : "&",
"left" : {
"type" : "field",
- "value" : ["standard_metadata", "ingress_port"]
- },
- "right" : {
- "type" : "hexstr",
- "value" : "0xffffffff"
- }
- }
- }
- }
- ]
- },
- {
- "op" : "count",
- "parameters" : [
- {
- "type" : "counter_array",
- "value" : "port_counters_control.ingress_port_counter"
- },
- {
- "type" : "field",
- "value" : ["scalars", "tmp"]
- }
- ],
- "source_info" : {
- "filename" : "include/port_counters.p4",
- "line" : 27,
- "column" : 12,
- "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
- }
- },
- {
- "op" : "assign",
- "parameters" : [
- {
- "type" : "field",
- "value" : ["scalars", "tmp_0"]
- },
- {
- "type" : "expression",
- "value" : {
- "type" : "expression",
- "value" : {
- "op" : "&",
- "left" : {
- "type" : "field",
"value" : ["standard_metadata", "egress_spec"]
},
"right" : {
@@ -3313,12 +3268,12 @@
},
{
"type" : "field",
- "value" : ["scalars", "tmp_0"]
+ "value" : ["scalars", "tmp"]
}
],
"source_info" : {
"filename" : "include/port_counters.p4",
- "line" : 28,
+ "line" : 27,
"column" : 12,
"source_fragment" : "egress_port_counter.count((bit<32>)standard_metadata.egress_spec)"
}
@@ -3331,6 +3286,58 @@
"runtime_data" : [],
"primitives" : [
{
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp_0"]
+ },
+ {
+ "type" : "expression",
+ "value" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "&",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0xffffffff"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "op" : "count",
+ "parameters" : [
+ {
+ "type" : "counter_array",
+ "value" : "port_counters_control.ingress_port_counter"
+ },
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp_0"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "include/port_counters.p4",
+ "line" : 30,
+ "column" : 12,
+ "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "act_2",
+ "id" : 9,
+ "runtime_data" : [],
+ "primitives" : [
+ {
"op" : "add_header",
"parameters" : [
{
@@ -3367,8 +3374,8 @@
]
},
{
- "name" : "act_2",
- "id" : 9,
+ "name" : "act_3",
+ "id" : 10,
"runtime_data" : [],
"primitives" : [
{
@@ -3518,9 +3525,9 @@
"direct_meters" : null,
"action_ids" : [7],
"actions" : ["act_0"],
- "base_default_next" : null,
+ "base_default_next" : "node_9",
"next_tables" : {
- "act_0" : null
+ "act_0" : "node_9"
},
"default_entry" : {
"action_id" : 7,
@@ -3528,6 +3535,29 @@
"action_data" : [],
"action_entry_const" : true
}
+ },
+ {
+ "name" : "tbl_act_1",
+ "id" : 4,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [8],
+ "actions" : ["act_1"],
+ "base_default_next" : null,
+ "next_tables" : {
+ "act_1" : null
+ },
+ "default_entry" : {
+ "action_id" : 8,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
}
],
"action_profiles" : [
@@ -3630,7 +3660,7 @@
"filename" : "include/port_counters.p4",
"line" : 26,
"column" : 12,
- "source_fragment" : "standard_metadata.egress_spec < 254"
+ "source_fragment" : "standard_metadata.egress_spec < 255"
},
"expression" : {
"type" : "expression",
@@ -3642,12 +3672,38 @@
},
"right" : {
"type" : "hexstr",
- "value" : "0x00fe"
+ "value" : "0x00ff"
+ }
+ }
+ },
+ "true_next" : "tbl_act_0",
+ "false_next" : "node_9"
+ },
+ {
+ "name" : "node_9",
+ "id" : 3,
+ "source_info" : {
+ "filename" : "include/port_counters.p4",
+ "line" : 29,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.ingress_port < 255"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "<",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x00ff"
}
}
},
"false_next" : null,
- "true_next" : "tbl_act_0"
+ "true_next" : "tbl_act_1"
}
]
},
@@ -3660,32 +3716,9 @@
"column" : 8,
"source_fragment" : "egress"
},
- "init_table" : "tbl_act_1",
+ "init_table" : "tbl_act_2",
"tables" : [
{
- "name" : "tbl_act_1",
- "id" : 4,
- "key" : [],
- "match_type" : "exact",
- "type" : "simple",
- "max_size" : 1024,
- "with_counters" : false,
- "support_timeout" : false,
- "direct_meters" : null,
- "action_ids" : [9],
- "actions" : ["act_2"],
- "base_default_next" : "node_12",
- "next_tables" : {
- "act_2" : "node_12"
- },
- "default_entry" : {
- "action_id" : 9,
- "action_const" : true,
- "action_data" : [],
- "action_entry_const" : true
- }
- },
- {
"name" : "tbl_act_2",
"id" : 5,
"key" : [],
@@ -3695,14 +3728,37 @@
"with_counters" : false,
"support_timeout" : false,
"direct_meters" : null,
- "action_ids" : [8],
- "actions" : ["act_1"],
- "base_default_next" : null,
+ "action_ids" : [10],
+ "actions" : ["act_3"],
+ "base_default_next" : "node_14",
"next_tables" : {
- "act_1" : null
+ "act_3" : "node_14"
},
"default_entry" : {
- "action_id" : 8,
+ "action_id" : 10,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
+ },
+ {
+ "name" : "tbl_act_3",
+ "id" : 6,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [9],
+ "actions" : ["act_2"],
+ "base_default_next" : null,
+ "next_tables" : {
+ "act_2" : null
+ },
+ "default_entry" : {
+ "action_id" : 9,
"action_const" : true,
"action_data" : [],
"action_entry_const" : true
@@ -3712,8 +3768,8 @@
"action_profiles" : [],
"conditionals" : [
{
- "name" : "node_12",
- "id" : 3,
+ "name" : "node_14",
+ "id" : 4,
"source_info" : {
"filename" : "include/packet_io.p4",
"line" : 31,
@@ -3735,7 +3791,7 @@
}
},
"false_next" : null,
- "true_next" : "tbl_act_2"
+ "true_next" : "tbl_act_3"
}
]
}
diff --git a/tools/test/p4src/p4-16/p4c-out/default.p4info b/tools/test/p4src/p4-16/p4c-out/default.p4info
index 6bf4a27..d08d40a 100644
--- a/tools/test/p4src/p4-16/p4c-out/default.p4info
+++ b/tools/test/p4src/p4-16/p4c-out/default.p4info
@@ -130,7 +130,7 @@
spec {
unit: PACKETS
}
- size: 254
+ size: 255
}
counters {
preamble {
@@ -141,7 +141,7 @@
spec {
unit: PACKETS
}
- size: 254
+ size: 255
}
direct_counters {
preamble {
diff --git a/tools/test/p4src/p4-16/p4c-out/ecmp.json b/tools/test/p4src/p4-16/p4c-out/ecmp.json
index 506db53..2a23882 100644
--- a/tools/test/p4src/p4-16/p4c-out/ecmp.json
+++ b/tools/test/p4src/p4-16/p4c-out/ecmp.json
@@ -448,7 +448,7 @@
"column" : 38,
"source_fragment" : "egress_port_counter"
},
- "size" : 254,
+ "size" : 255,
"is_direct" : false
},
{
@@ -460,7 +460,7 @@
"column" : 38,
"source_fragment" : "ingress_port_counter"
},
- "size" : 254,
+ "size" : 255,
"is_direct" : false
}
],
@@ -3312,51 +3312,6 @@
"op" : "&",
"left" : {
"type" : "field",
- "value" : ["standard_metadata", "ingress_port"]
- },
- "right" : {
- "type" : "hexstr",
- "value" : "0xffffffff"
- }
- }
- }
- }
- ]
- },
- {
- "op" : "count",
- "parameters" : [
- {
- "type" : "counter_array",
- "value" : "port_counters_control.ingress_port_counter"
- },
- {
- "type" : "field",
- "value" : ["scalars", "tmp"]
- }
- ],
- "source_info" : {
- "filename" : "include/port_counters.p4",
- "line" : 27,
- "column" : 12,
- "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
- }
- },
- {
- "op" : "assign",
- "parameters" : [
- {
- "type" : "field",
- "value" : ["scalars", "tmp_0"]
- },
- {
- "type" : "expression",
- "value" : {
- "type" : "expression",
- "value" : {
- "op" : "&",
- "left" : {
- "type" : "field",
"value" : ["standard_metadata", "egress_spec"]
},
"right" : {
@@ -3377,12 +3332,12 @@
},
{
"type" : "field",
- "value" : ["scalars", "tmp_0"]
+ "value" : ["scalars", "tmp"]
}
],
"source_info" : {
"filename" : "include/port_counters.p4",
- "line" : 28,
+ "line" : 27,
"column" : 12,
"source_fragment" : "egress_port_counter.count((bit<32>)standard_metadata.egress_spec)"
}
@@ -3395,6 +3350,58 @@
"runtime_data" : [],
"primitives" : [
{
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp_0"]
+ },
+ {
+ "type" : "expression",
+ "value" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "&",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0xffffffff"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "op" : "count",
+ "parameters" : [
+ {
+ "type" : "counter_array",
+ "value" : "port_counters_control.ingress_port_counter"
+ },
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp_0"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "include/port_counters.p4",
+ "line" : 30,
+ "column" : 12,
+ "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "act_2",
+ "id" : 9,
+ "runtime_data" : [],
+ "primitives" : [
+ {
"op" : "add_header",
"parameters" : [
{
@@ -3431,8 +3438,8 @@
]
},
{
- "name" : "act_2",
- "id" : 9,
+ "name" : "act_3",
+ "id" : 10,
"runtime_data" : [],
"primitives" : [
{
@@ -3592,9 +3599,9 @@
"direct_meters" : null,
"action_ids" : [7],
"actions" : ["act_0"],
- "base_default_next" : null,
+ "base_default_next" : "node_9",
"next_tables" : {
- "act_0" : null
+ "act_0" : "node_9"
},
"default_entry" : {
"action_id" : 7,
@@ -3602,6 +3609,29 @@
"action_data" : [],
"action_entry_const" : true
}
+ },
+ {
+ "name" : "tbl_act_1",
+ "id" : 4,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [8],
+ "actions" : ["act_1"],
+ "base_default_next" : null,
+ "next_tables" : {
+ "act_1" : null
+ },
+ "default_entry" : {
+ "action_id" : 8,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
}
],
"action_profiles" : [],
@@ -3665,7 +3695,7 @@
"filename" : "include/port_counters.p4",
"line" : 26,
"column" : 12,
- "source_fragment" : "standard_metadata.egress_spec < 254"
+ "source_fragment" : "standard_metadata.egress_spec < 255"
},
"expression" : {
"type" : "expression",
@@ -3677,12 +3707,38 @@
},
"right" : {
"type" : "hexstr",
- "value" : "0x00fe"
+ "value" : "0x00ff"
+ }
+ }
+ },
+ "true_next" : "tbl_act_0",
+ "false_next" : "node_9"
+ },
+ {
+ "name" : "node_9",
+ "id" : 3,
+ "source_info" : {
+ "filename" : "include/port_counters.p4",
+ "line" : 29,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.ingress_port < 255"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "<",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x00ff"
}
}
},
"false_next" : null,
- "true_next" : "tbl_act_0"
+ "true_next" : "tbl_act_1"
}
]
},
@@ -3695,32 +3751,9 @@
"column" : 8,
"source_fragment" : "egress"
},
- "init_table" : "tbl_act_1",
+ "init_table" : "tbl_act_2",
"tables" : [
{
- "name" : "tbl_act_1",
- "id" : 4,
- "key" : [],
- "match_type" : "exact",
- "type" : "simple",
- "max_size" : 1024,
- "with_counters" : false,
- "support_timeout" : false,
- "direct_meters" : null,
- "action_ids" : [9],
- "actions" : ["act_2"],
- "base_default_next" : "node_12",
- "next_tables" : {
- "act_2" : "node_12"
- },
- "default_entry" : {
- "action_id" : 9,
- "action_const" : true,
- "action_data" : [],
- "action_entry_const" : true
- }
- },
- {
"name" : "tbl_act_2",
"id" : 5,
"key" : [],
@@ -3730,14 +3763,37 @@
"with_counters" : false,
"support_timeout" : false,
"direct_meters" : null,
- "action_ids" : [8],
- "actions" : ["act_1"],
- "base_default_next" : null,
+ "action_ids" : [10],
+ "actions" : ["act_3"],
+ "base_default_next" : "node_14",
"next_tables" : {
- "act_1" : null
+ "act_3" : "node_14"
},
"default_entry" : {
- "action_id" : 8,
+ "action_id" : 10,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
+ },
+ {
+ "name" : "tbl_act_3",
+ "id" : 6,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [9],
+ "actions" : ["act_2"],
+ "base_default_next" : null,
+ "next_tables" : {
+ "act_2" : null
+ },
+ "default_entry" : {
+ "action_id" : 9,
"action_const" : true,
"action_data" : [],
"action_entry_const" : true
@@ -3747,8 +3803,8 @@
"action_profiles" : [],
"conditionals" : [
{
- "name" : "node_12",
- "id" : 3,
+ "name" : "node_14",
+ "id" : 4,
"source_info" : {
"filename" : "include/packet_io.p4",
"line" : 31,
@@ -3770,7 +3826,7 @@
}
},
"false_next" : null,
- "true_next" : "tbl_act_2"
+ "true_next" : "tbl_act_3"
}
]
}
diff --git a/tools/test/p4src/p4-16/p4c-out/ecmp.p4info b/tools/test/p4src/p4-16/p4c-out/ecmp.p4info
index 29fd5c0..2355b90 100644
--- a/tools/test/p4src/p4-16/p4c-out/ecmp.p4info
+++ b/tools/test/p4src/p4-16/p4c-out/ecmp.p4info
@@ -126,7 +126,7 @@
spec {
unit: PACKETS
}
- size: 254
+ size: 255
}
counters {
preamble {
@@ -137,7 +137,7 @@
spec {
unit: PACKETS
}
- size: 254
+ size: 255
}
direct_counters {
preamble {
diff --git a/tools/test/p4src/p4-16/p4c-out/wcmp.json b/tools/test/p4src/p4-16/p4c-out/wcmp.json
index dc50c96..3367017 100644
--- a/tools/test/p4src/p4-16/p4c-out/wcmp.json
+++ b/tools/test/p4src/p4-16/p4c-out/wcmp.json
@@ -393,7 +393,7 @@
"parser_ops" : [],
"transitions" : [
{
- "value" : "0xff",
+ "value" : "0x00ff",
"mask" : null,
"next_state" : "parse_packet_out"
},
@@ -449,7 +449,7 @@
"column" : 38,
"source_fragment" : "egress_port_counter"
},
- "size" : 254,
+ "size" : 255,
"is_direct" : false
},
{
@@ -461,7 +461,7 @@
"column" : 38,
"source_fragment" : "ingress_port_counter"
},
- "size" : 254,
+ "size" : 255,
"is_direct" : false
}
],
@@ -3422,51 +3422,6 @@
"op" : "&",
"left" : {
"type" : "field",
- "value" : ["standard_metadata", "ingress_port"]
- },
- "right" : {
- "type" : "hexstr",
- "value" : "0xffffffff"
- }
- }
- }
- }
- ]
- },
- {
- "op" : "count",
- "parameters" : [
- {
- "type" : "counter_array",
- "value" : "port_counters_control.ingress_port_counter"
- },
- {
- "type" : "field",
- "value" : ["scalars", "tmp"]
- }
- ],
- "source_info" : {
- "filename" : "include/port_counters.p4",
- "line" : 27,
- "column" : 12,
- "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
- }
- },
- {
- "op" : "assign",
- "parameters" : [
- {
- "type" : "field",
- "value" : ["scalars", "tmp_0"]
- },
- {
- "type" : "expression",
- "value" : {
- "type" : "expression",
- "value" : {
- "op" : "&",
- "left" : {
- "type" : "field",
"value" : ["standard_metadata", "egress_spec"]
},
"right" : {
@@ -3487,12 +3442,12 @@
},
{
"type" : "field",
- "value" : ["scalars", "tmp_0"]
+ "value" : ["scalars", "tmp"]
}
],
"source_info" : {
"filename" : "include/port_counters.p4",
- "line" : 28,
+ "line" : 27,
"column" : 12,
"source_fragment" : "egress_port_counter.count((bit<32>)standard_metadata.egress_spec)"
}
@@ -3505,6 +3460,58 @@
"runtime_data" : [],
"primitives" : [
{
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp_0"]
+ },
+ {
+ "type" : "expression",
+ "value" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "&",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0xffffffff"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "op" : "count",
+ "parameters" : [
+ {
+ "type" : "counter_array",
+ "value" : "port_counters_control.ingress_port_counter"
+ },
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp_0"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "include/port_counters.p4",
+ "line" : 30,
+ "column" : 12,
+ "source_fragment" : "ingress_port_counter.count((bit<32>)standard_metadata.ingress_port)"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "act_2",
+ "id" : 10,
+ "runtime_data" : [],
+ "primitives" : [
+ {
"op" : "add_header",
"parameters" : [
{
@@ -3541,8 +3548,8 @@
]
},
{
- "name" : "act_2",
- "id" : 10,
+ "name" : "act_3",
+ "id" : 11,
"runtime_data" : [],
"primitives" : [
{
@@ -3725,9 +3732,9 @@
"direct_meters" : null,
"action_ids" : [8],
"actions" : ["act_0"],
- "base_default_next" : null,
+ "base_default_next" : "node_10",
"next_tables" : {
- "act_0" : null
+ "act_0" : "node_10"
},
"default_entry" : {
"action_id" : 8,
@@ -3735,6 +3742,29 @@
"action_data" : [],
"action_entry_const" : true
}
+ },
+ {
+ "name" : "tbl_act_1",
+ "id" : 5,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [9],
+ "actions" : ["act_1"],
+ "base_default_next" : null,
+ "next_tables" : {
+ "act_1" : null
+ },
+ "default_entry" : {
+ "action_id" : 9,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
}
],
"action_profiles" : [],
@@ -3798,7 +3828,7 @@
"filename" : "include/port_counters.p4",
"line" : 26,
"column" : 12,
- "source_fragment" : "standard_metadata.egress_spec < 254"
+ "source_fragment" : "standard_metadata.egress_spec < 255"
},
"expression" : {
"type" : "expression",
@@ -3810,12 +3840,38 @@
},
"right" : {
"type" : "hexstr",
- "value" : "0x00fe"
+ "value" : "0x00ff"
+ }
+ }
+ },
+ "true_next" : "tbl_act_0",
+ "false_next" : "node_10"
+ },
+ {
+ "name" : "node_10",
+ "id" : 3,
+ "source_info" : {
+ "filename" : "include/port_counters.p4",
+ "line" : 29,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.ingress_port < 255"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "<",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x00ff"
}
}
},
"false_next" : null,
- "true_next" : "tbl_act_0"
+ "true_next" : "tbl_act_1"
}
]
},
@@ -3828,32 +3884,9 @@
"column" : 8,
"source_fragment" : "egress"
},
- "init_table" : "tbl_act_1",
+ "init_table" : "tbl_act_2",
"tables" : [
{
- "name" : "tbl_act_1",
- "id" : 5,
- "key" : [],
- "match_type" : "exact",
- "type" : "simple",
- "max_size" : 1024,
- "with_counters" : false,
- "support_timeout" : false,
- "direct_meters" : null,
- "action_ids" : [10],
- "actions" : ["act_2"],
- "base_default_next" : "node_13",
- "next_tables" : {
- "act_2" : "node_13"
- },
- "default_entry" : {
- "action_id" : 10,
- "action_const" : true,
- "action_data" : [],
- "action_entry_const" : true
- }
- },
- {
"name" : "tbl_act_2",
"id" : 6,
"key" : [],
@@ -3863,14 +3896,37 @@
"with_counters" : false,
"support_timeout" : false,
"direct_meters" : null,
- "action_ids" : [9],
- "actions" : ["act_1"],
- "base_default_next" : null,
+ "action_ids" : [11],
+ "actions" : ["act_3"],
+ "base_default_next" : "node_15",
"next_tables" : {
- "act_1" : null
+ "act_3" : "node_15"
},
"default_entry" : {
- "action_id" : 9,
+ "action_id" : 11,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
+ },
+ {
+ "name" : "tbl_act_3",
+ "id" : 7,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [10],
+ "actions" : ["act_2"],
+ "base_default_next" : null,
+ "next_tables" : {
+ "act_2" : null
+ },
+ "default_entry" : {
+ "action_id" : 10,
"action_const" : true,
"action_data" : [],
"action_entry_const" : true
@@ -3880,8 +3936,8 @@
"action_profiles" : [],
"conditionals" : [
{
- "name" : "node_13",
- "id" : 3,
+ "name" : "node_15",
+ "id" : 4,
"source_info" : {
"filename" : "include/packet_io.p4",
"line" : 31,
@@ -3903,7 +3959,7 @@
}
},
"false_next" : null,
- "true_next" : "tbl_act_2"
+ "true_next" : "tbl_act_3"
}
]
}
diff --git a/tools/test/p4src/p4-16/p4c-out/wcmp.p4info b/tools/test/p4src/p4-16/p4c-out/wcmp.p4info
index 41aabf4..b6c3b50 100644
--- a/tools/test/p4src/p4-16/p4c-out/wcmp.p4info
+++ b/tools/test/p4src/p4-16/p4c-out/wcmp.p4info
@@ -133,7 +133,7 @@
spec {
unit: PACKETS
}
- size: 254
+ size: 255
}
counters {
preamble {
@@ -144,7 +144,7 @@
spec {
unit: PACKETS
}
- size: 254
+ size: 255
}
direct_counters {
preamble {
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
index 6e49dba..71c7d5a 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
@@ -37,6 +37,7 @@
import org.onosproject.ui.table.CellFormatter;
import org.onosproject.ui.table.TableModel;
import org.onosproject.ui.table.TableRequestHandler;
+import org.onosproject.ui.table.cell.DefaultCellFormatter;
import org.onosproject.ui.table.cell.EnumFormatter;
import org.onosproject.ui.table.cell.HexLongFormatter;
import org.onosproject.ui.table.cell.NumberFormatter;
@@ -69,7 +70,7 @@
private static final String APP_ID = "appId";
private static final String APP_NAME = "appName";
private static final String GROUP_ID = "groupId";
- private static final String TABLE_ID = "tableId";
+ private static final String TABLE_NAME = "tableName";
private static final String PRIORITY = "priority";
private static final String SELECTOR_C = "selector_c"; // for table column
private static final String SELECTOR = "selector";
@@ -128,7 +129,7 @@
PACKETS,
DURATION,
PRIORITY,
- TABLE_ID,
+ TABLE_NAME,
APP_ID,
APP_NAME,
@@ -206,6 +207,7 @@
tm.setFormatter(SELECTOR, new SelectorFormatter());
tm.setFormatter(TREATMENT_C, new TreatmentShortFormatter());
tm.setFormatter(TREATMENT, new TreatmentFormatter());
+ tm.setFormatter(TABLE_NAME, DefaultCellFormatter.INSTANCE);
return tm;
}
@@ -231,7 +233,7 @@
.cell(PACKETS, flow.packets())
.cell(DURATION, flow.life())
.cell(PRIORITY, flow.priority())
- .cell(TABLE_ID, flow.tableId())
+ .cell(TABLE_NAME, flow.table())
.cell(APP_ID, flow.appId())
.cell(APP_NAME, makeAppName(flow.appId(), lookup))
@@ -407,7 +409,7 @@
data.put(DURATION, NumberFormatter.INTEGER.format(flow.life()));
data.put(FLOW_PRIORITY, flow.priority());
- data.put(TABLE_ID, flow.tableId());
+ data.put(TABLE_NAME, flow.table().toString());
data.put(APP_ID, flow.appId());
// NOTE: horribly inefficient... make a map and retrieve a single value...
data.put(APP_NAME, makeAppName(flow.appId(), appShortMap()));
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/PipeconfViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/PipeconfViewMessageHandler.java
new file mode 100644
index 0000000..9ecbeca
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/PipeconfViewMessageHandler.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.ui.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiHeaderFieldModel;
+import org.onosproject.net.pi.model.PiHeaderModel;
+import org.onosproject.net.pi.model.PiHeaderTypeModel;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableMatchFieldModel;
+import org.onosproject.net.pi.model.PiTableModel;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
+
+public class PipeconfViewMessageHandler extends UiMessageHandler {
+ private static final Logger log =
+ LoggerFactory.getLogger(PipeconfViewMessageHandler.class);
+ private static final String PIPECONF_REQUEST = "pipeconfRequest";
+ private static final String PIPECONF_RESP = "pipeConfResponse";
+ private static final String DEVICE_ID = "devId";
+ private static final String PIPECONF = "pipeconf";
+ private static final String PIPELINE_MODEL = "pipelineModel";
+ private static final String NO_PIPECONF_RESP = "noPipeconfResp";
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new PipeconfRequestHandler());
+ }
+
+ private class PipeconfRequestHandler extends RequestHandler {
+
+ public PipeconfRequestHandler() {
+ super(PIPECONF_REQUEST);
+ }
+
+ @Override
+ public void process(ObjectNode payload) {
+ PiPipeconfService piPipeconfService = get(PiPipeconfService.class);
+ DeviceService deviceService = get(DeviceService.class);
+ ObjectNode responseData = objectNode();
+ String devId = string(payload, DEVICE_ID);
+ if (devId == null || devId.isEmpty()) {
+ log.warn("{}: Invalid device id", PIPECONF_REQUEST);
+ sendMessage(NO_PIPECONF_RESP, null);
+ return;
+ }
+ DeviceId deviceId = DeviceId.deviceId(devId);
+ Optional<PiPipeconfId> pipeconfId = piPipeconfService.ofDevice(deviceId);
+ if (!pipeconfId.isPresent()) {
+ log.warn("{}: Can't find pipeconf id for device {}", PIPECONF_REQUEST, deviceId);
+ sendMessage(NO_PIPECONF_RESP, null);
+ return;
+ }
+
+ Optional<PiPipeconf> pipeconf = piPipeconfService.getPipeconf(pipeconfId.get());
+ if (!pipeconf.isPresent()) {
+ log.warn("{}: Can't find pipeconf {}", PIPECONF_REQUEST, pipeconfId);
+ sendMessage(NO_PIPECONF_RESP, null);
+ return;
+ }
+ CodecContext codecContext = getJsonCodecContext();
+
+ ObjectNode pipeconfData = codecContext.encode(pipeconf.get(), PiPipeconf.class);
+ responseData.set(PIPECONF, pipeconfData);
+
+ // Filtered out models not exists in interpreter
+ // usually they generated by compiler automatically
+ Device device = deviceService.getDevice(deviceId);
+ if (device == null || !deviceService.isAvailable(deviceId)) {
+ log.warn("{}: Device {} is not available", PIPECONF_REQUEST, deviceId);
+ sendMessage(NO_PIPECONF_RESP, null);
+ return;
+ }
+ PiPipelineInterpreter interpreter = device.as(PiPipelineInterpreter.class);
+ PiPipelineModel pipelineModel =
+ filteredOutAdditionalData(pipeconf.get().pipelineModel(), interpreter);
+
+ ObjectNode pipelineModelData =
+ codecContext.encode(pipelineModel, PiPipelineModel.class);
+ responseData.set(PIPELINE_MODEL, pipelineModelData);
+
+ sendMessage(PIPECONF_RESP, responseData);
+ }
+ }
+
+ private PiPipelineModel filteredOutAdditionalData(PiPipelineModel piPipelineModel,
+ PiPipelineInterpreter interpreter) {
+ if (interpreter == null) {
+ // Do nothing if there is no interpreter
+ return piPipelineModel;
+ }
+ // filter out actions, headers and tables if not exists in interpreter
+ Set<PiHeaderTypeModel> newHeaderTypesModels = Sets.newHashSet();
+ Set<PiHeaderModel> newHeaderModels = Sets.newHashSet();
+ Set<PiActionModel> newActionModels = Sets.newHashSet();
+ Set<PiTableModel> newTableModels = Sets.newHashSet();
+
+ piPipelineModel.tables().forEach(table -> {
+ String tableName = table.name();
+ PiTableId tableId = PiTableId.of(tableName);
+
+ if (interpreter.mapPiTableId(tableId).isPresent()) {
+ newTableModels.add(table);
+
+ newActionModels.addAll(table.actions());
+ table.matchFields().stream()
+ .map(PiTableMatchFieldModel::field)
+ .map(PiHeaderFieldModel::header)
+ .forEach(header -> {
+ newHeaderModels.add(header);
+ newHeaderTypesModels.add(header.type());
+ });
+
+ }
+ });
+
+ return new FilteredPipelineModel(newHeaderTypesModels,
+ newHeaderModels,
+ newActionModels,
+ newTableModels);
+ }
+
+ /**
+ * Pipeline model for UI message.
+ * FIXME: Is it necessary to create this class?
+ */
+ private class FilteredPipelineModel implements PiPipelineModel {
+
+ private Set<PiHeaderTypeModel> headerTypesModels;
+ private Set<PiHeaderModel> headerModels;
+ private Set<PiActionModel> actionModels;
+ private Set<PiTableModel> tableModels;
+
+ public FilteredPipelineModel(Set<PiHeaderTypeModel> headerTypesModels,
+ Set<PiHeaderModel> headerModels,
+ Set<PiActionModel> actionModels,
+ Set<PiTableModel> tableModels) {
+ this.headerTypesModels = headerTypesModels;
+ this.headerModels = headerModels;
+ this.actionModels = actionModels;
+ this.tableModels = tableModels;
+ }
+
+ @Override
+ public Optional<PiHeaderTypeModel> headerType(String name) {
+ return headerTypesModels.stream()
+ .filter(headerType -> headerType.name().equals(name))
+ .findFirst();
+ }
+
+ @Override
+ public Collection<PiHeaderTypeModel> headerTypes() {
+ return headerTypesModels;
+ }
+
+ @Override
+ public Optional<PiHeaderModel> header(String name) {
+ return headerModels.stream()
+ .filter(headerModel -> headerModel.name().equals(name))
+ .findFirst();
+ }
+
+ @Override
+ public Collection<PiHeaderModel> headers() {
+ return headerModels;
+ }
+
+ @Override
+ public Optional<PiActionModel> action(String name) {
+ return actionModels.stream()
+ .filter(actionModel -> actionModel.name().equals(name))
+ .findFirst();
+ }
+
+ @Override
+ public Collection<PiActionModel> actions() {
+ return actionModels;
+ }
+
+ @Override
+ public Optional<PiTableModel> table(String name) {
+ return tableModels.stream()
+ .filter(tableModel -> tableModel.name().equals(name))
+ .findFirst();
+ }
+
+ @Override
+ public Collection<PiTableModel> tables() {
+ return tableModels;
+ }
+ }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
index 121df58..f3afa1a 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
@@ -194,6 +194,7 @@
new UiViewHidden("port"),
new UiViewHidden("group"),
new UiViewHidden("meter"),
+ new UiViewHidden("pipeconf"),
mkView(NETWORK, "link", "nav_links"),
mkView(NETWORK, "host", "nav_hosts"),
@@ -221,7 +222,8 @@
new ClusterViewMessageHandler(),
new ProcessorViewMessageHandler(),
new TunnelViewMessageHandler(),
- new PartitionViewMessageHandler()
+ new PartitionViewMessageHandler(),
+ new PipeconfViewMessageHandler()
);
UiTopoOverlayFactory topoOverlayFactory =
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_TW.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_TW.properties
index 923dc04..f3dabae 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_TW.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/fw/Mast_zh_TW.properties
@@ -19,5 +19,14 @@
# Tooltip for context-sensitive help button [?]
tt_help=顯示本頁說明
+# unknown user
+unknown_user=(未知使用者)
+
# Logout button
logout=登出
+
+# UI components added/removed, etc.
+uicomp_added=新元件已新增
+uicomp_removed=有元件已被移除
+ui_ok_to_update=點擊確定更新介面
+confirm_refresh_title=確認重整介面
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_es.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_es.properties
new file mode 100644
index 0000000..6631bc5
--- /dev/null
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo_es.properties
@@ -0,0 +1,143 @@
+#
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+# Text that appears in the navigation panel
+nav_item_topo=Topologia
+
+# Message when no devices are connected
+no_devices_are_connected=Ningun dispositivo conectado
+
+# Action Key Toolbar Tooltips ...
+tbtt_tog_instances=Muestra el panel de instancias de ONOS
+tbtt_tog_summary=Muestra el panel de sumario de ONOS
+tbtt_tog_use_detail=Habilita el panel de detalles
+tbtt_tog_host=Activa visibilidad de hosts
+tbtt_tog_offline=Activa la visibilidad offline
+tbtt_tog_porthi=Muestra puertos resaltados
+tbtt_bad_links=Muestra enlaces con problemas
+tbtt_tog_map=Muestra mapa geográfico en el fondo
+tbtt_sel_map=Selecciona mapa geográfico de fondo
+tbtt_tog_sprite=Activa capa sprite
+tbtt_reset_loc=Inicializa localización de nodos
+tbtt_tog_oblique=Activa la vista oblicua (experimental)
+tbtt_cyc_layers=Capas cíclicas de nodo
+tbtt_cyc_dev_labs=Etiquetas cíclicas de dispositivo
+tbtt_cyc_host_labs=Etiquetas cíclicas de hoste
+tbtt_unpin_node=Unpin node (hover mouse over)
+tbtt_reset_zoom=Reinicia nivel de zoom
+tbtt_tog_toolbar=Muestra la barra de herramientas
+tbtt_eq_master=Equalize mastership roles
+
+# Quick Help Gestures
+qh_gest_click=Selecciona item y muestra detalles
+qh_gest_shift_click=Activa seleccion de estado
+qh_gest_drag=Vuelve a fijar en un punto el dispositivo / host
+qh_gest_cmd_scroll=Zoom in / out
+qh_gest_cmd_drag=Pan
+
+# Flash Messages
+fl_background_map=Mapa de fondo
+fl_sprite_layer=Capa sprite
+fl_pan_zoom_reset=Reinicializa nivel de zoom
+fl_eq_masters=Equalizing master roles
+
+fl_device_labels_hide=Oculta etiquetas de dispositivo
+fl_device_labels_show_friendly=Muestra etiquetas con el nombre del dispositivo
+fl_device_labels_show_id=Muestra etiquetas con el indicador (device ID)
+fl_host_labels_show_friendly=Muestra etiquetas con el nombre del host
+fl_host_labels_show_ip=Muestra dirección IP del host
+fl_host_labels_show_mac=Muestra la dirección MAC del host
+fl_offline_devices=Dispositivos fuera de línea
+fl_bad_links=Enlaces con problemas
+fl_reset_node_locations=Reinicia la localización de nodos
+
+fl_layer_all=Muestra todas las capas
+fl_layer_pkt=Muestra la capa de paquetes
+fl_layer_opt=Muestra la capa óptica
+
+fl_panel_instances=Instances Panel de instancias
+fl_panel_summary=Panel de sumario
+fl_panel_details=Panel de detalles
+
+fl_port_highlighting=Puerto resalatado
+
+fl_oblique_view=Vista oblicua
+fl_normal_view=Vista Normal
+
+fl_monitoring_canceled=Monitorización cancelada
+fl_selecting_intent=Seleccionando Intent
+
+# Core Overlays
+ov_tt_protected_intents=Capa de Intents protegidos
+ov_tt_traffic=Capa de tráfico
+ov_tt_none=Sin capas
+
+# Traffic Overlay
+tr_btn_create_h2h_flow=Crear flujo Host-to-Host Flow
+tr_btn_create_msrc_flow=Crear flujo Multi-Source
+tr_btn_show_device_flows=Muestra flujos de dispositivo
+tr_btn_show_related_traffic=Muestra el tráfico relacionado
+tr_btn_cancel_monitoring=Cancela la motitorización de tráfico
+tr_btn_monitor_all=Monitoriza todo el tráfico
+tr_btn_show_dev_link_flows=Muestra flujos en enlace de dispositivo
+tr_btn_show_all_rel_intents=Muestra todos los intents relacionados
+tr_btn_show_prev_rel_intent=Muestra los anteriores intents relacionados
+tr_btn_show_next_rel_intent=Muestra los siguientes intents relacionados
+tr_btn_monitor_sel_intent=Monitoriza el trafico del intent seleccionado
+tr_fl_fstats_bytes=Flow estadísticas (bytes)
+tr_fl_pstats_bits=Port estadísticas (bits / second)
+tr_fl_pstats_pkts=Port estadísticas (packets / second)
+tr_fl_dev_flows=Flujos de dispositivo
+tr_fl_rel_paths=Caminos relacionados
+tr_fl_prev_rel_int=Previos intent relacionados
+tr_fl_next_rel_int=Siguientes intent relacionados
+tr_fl_traf_on_path=Tráfico en el camino seleccionado
+tr_fl_h2h_flow_added=Añadido flujo Host-to-Host
+tr_fl_multisrc_flow=Flujo Multi-Source
+
+# Button tooltips
+btn_show_view_device=Muestra la vista de dispositivo
+btn_show_view_flow=Muestra vista de flujos de este dispositivo
+btn_show_view_port=Muestra la vista de puertos de este dispositivo
+btn_show_view_group=Muestra la vista de grupo para este dispositivo
+btn_show_view_meter=Muestra la vista de contadores para este dispositivo
+
+# Panel Titles
+title_select_map=Mapa seleccionado
+title_panel_summary=Sumario de ONOS
+title_selected_items=Items Seleccionados
+title_edge_link=Enlace de terminación
+title_infra_link=Enlace de infraestructura
+
+# Custom Panel Labels / Values
+lp_label_friendly=Friendly
+
+lp_label_a_type=Tipo A
+lp_label_a_id=A id
+lp_label_a_friendly=A friendly
+lp_label_a_port=Puerto A
+
+lp_label_b_type=Tipo B
+lp_label_b_id=B id
+lp_label_b_friendly=B friendly
+lp_label_b_port=Puerto B
+
+lp_label_a2b=A to B
+lp_label_b2a=B to A
+
+lp_value_no_link=[no link]
+
diff --git a/web/gui/src/main/webapp/app/fw/layer/details-panel.css b/web/gui/src/main/webapp/app/fw/layer/details-panel.css
new file mode 100644
index 0000000..1c41835
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/layer/details-panel.css
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
diff --git a/web/gui/src/main/webapp/app/fw/layer/details-panel.js b/web/gui/src/main/webapp/app/fw/layer/details-panel.js
new file mode 100644
index 0000000..cc72a06
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/layer/details-panel.js
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function () {
+
+ var ps, fs, mast, wss, is, EditableTextComponent;
+
+ var panel,
+ pStartY,
+ wSize,
+ wssHandlers = {},
+ options;
+
+ // Constants
+ var topPdg = 28,
+ defaultLabelWidth = 110,
+ defaultValueWidth = 80;
+
+ // Elements
+ var container,
+ top,
+ bottom;
+
+ function createDetailsPanel(name, _options) {
+ options = _options;
+ scope = options.scope;
+
+ panel = ps.createPanel(name, options);
+
+ calculatePositions();
+
+ panel.el().style({
+ position: 'absolute',
+ top: pStartY + 'px',
+ });
+
+ hide();
+
+ return panel;
+ }
+
+ function calculatePositions() {
+ pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
+ + mast.mastHeight() + topPdg;
+ wSize = fs.windowSize(pStartY);
+ pHeight = wSize.height;
+ }
+
+ function hide() {
+ panel.hide();
+ }
+
+ function setResponse(name, callback) {
+ var additionalHandler = {};
+ additionalHandler[name] = callback;
+
+ wss.bindHandlers(additionalHandler);
+ wssHandlers = _.extend({}, wssHandlers, additionalHandler);
+ }
+
+ function addContainers() {
+ container = panel.append('div').classed('container', true);
+ top = container.append('div').classed('top', true);
+ bottom = container.append('div').classed('bottom', true);
+ }
+
+ function addCloseButton(onClose) {
+ var closeBtn = top.append('div').classed('close-btn', true);
+
+ is.loadEmbeddedIcon(closeBtn, 'close', 20);
+ closeBtn.on('click', onClose || function () {});
+ }
+
+ function addHeading(icon) {
+ top.append('div').classed('iconDiv ' + icon, true);
+ new EditableTextComponent(top.append('h2'), {
+ scope: options.scope,
+ nameChangeRequest: options.nameChangeRequest,
+ keyBindings: options.keyBindings,
+ });
+ }
+
+ function addTable(parent, className) {
+ return parent.append('div').classed(className, true).append('table');
+ }
+
+ function addProp(tbody, key, value) {
+ console.log(tbody);
+ var tr = tbody.append('tr');
+
+ function addCell(cls, txt, width) {
+ tr.append('td').attr('class', cls).attr('width', width).text(txt);
+ }
+
+ addCell('label', key + ' :', defaultLabelWidth);
+ addCell('value', value, defaultValueWidth);
+ }
+
+ function addPropsList(el, props) {
+ var tblDiv = el.append('div').classed('top-tables', true);
+ var left = addTable(tblDiv, 'left').append('tbody');
+ var right = addTable(tblDiv, 'right').append('tbody');
+
+ var keys = _.keys(props);
+
+ _.each(props, function (value, key) {
+ var index = keys.indexOf(key);
+
+ if (index < keys.length / 2) {
+ addProp(left, key, value);
+ } else {
+ addProp(right, key, value);
+ }
+ });
+ }
+
+ function empty() {
+ panel.empty();
+ }
+
+ function select(id) {
+ return panel.el().select(id);
+ }
+
+ function destroy() {
+ wss.unbindHandlers(wssHandlers);
+ }
+
+ angular.module('onosLayer')
+ .factory('DetailsPanelService', [
+
+ 'PanelService', 'FnService', 'MastService', 'WebSocketService',
+ 'IconService', 'EditableTextComponent',
+
+ function (_ps_, _fs_, _mast_, _wss_, _is_, _etc_) {
+
+ ps = _ps_;
+ fs = _fs_;
+ mast = _mast_;
+ wss = _wss_;
+ is = _is_;
+ EditableTextComponent = _etc_;
+
+ return {
+ create: createDetailsPanel,
+ setResponse: setResponse,
+
+ addContainers: addContainers,
+ addCloseButton: addCloseButton,
+ addHeading: addHeading,
+ addPropsList: addPropsList,
+
+ // Elements
+ container: function () { return container; },
+ top: function () { return top; },
+ bottom: function () { return bottom; },
+ select: select,
+
+ empty: empty,
+ hide: hide,
+ destroy: destroy,
+ };
+ },
+ ]);
+})();
diff --git a/web/gui/src/main/webapp/app/fw/layer/editable-text.js b/web/gui/src/main/webapp/app/fw/layer/editable-text.js
new file mode 100644
index 0000000..09da1ec
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/layer/editable-text.js
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function () {
+
+ var ks, wss;
+
+ var EditableText = function (el, options) {
+ // constructor
+ this.el = el;
+ this.scope = options.scope;
+ this.options = options;
+
+ this.el.classed('editable clickable', true).on('click', this.onEdit.bind(this));
+ this.editingName = false;
+ };
+
+ EditableText.prototype = {
+
+ bindHandlers: function () {
+ ks.keyBindings({
+ 'enter': this.save.bind(this),
+ 'esc': [this.cancel.bind(this), 'Close the details panel']
+ });
+ },
+
+ unbindHandlers: function () {
+ ks.unbindKeys();
+
+ if (this.options.keyBindings) {
+ // Reset to original bindings before editable text
+ ks.keyBindings(this.options.keyBindings);
+ }
+ },
+
+ addTextField: function () {
+ return this.el.append('input').classed('name-input', true)
+ .attr('type', 'text')
+ .attr('value', this.scope.panelData.name)[0][0];
+ },
+
+ onEdit: function () {
+ if (!this.editingName) {
+ this.el.classed('editable clickable', false);
+ this.el.text('');
+
+ var el = this.addTextField();
+ el.focus();
+ el.select();
+ this.editingName = true;
+
+ this.bindHandlers();
+
+ ks.enableGlobalKeys(false);
+ }
+ },
+
+ exit: function (name) {
+ this.el.text(name);
+ this.el.classed('editable clickable', true);
+ this.editingName = false;
+ ks.enableGlobalKeys(true);
+ this.unbindHandlers();
+ },
+
+ cancel: function (a, b, ev) {
+
+ if (this.editingName) {
+ this.exit(this.scope.panelData.name);
+ return true;
+ }
+
+ return false;
+ },
+
+ save: function () {
+ var id = this.scope.panelData.id,
+ val,
+ newVal;
+
+ if (this.editingName) {
+ val = this.el.select('input').property('value').trim();
+ newVal = val || id;
+
+ this.exit(newVal);
+ this.scope.panelData.name = newVal;
+ wss.sendEvent(this.options.nameChangeRequest, { id: id, name: val });
+ }
+ },
+ };
+
+
+ angular.module('onosLayer')
+ .factory('EditableTextComponent', [
+
+ 'KeyService', 'WebSocketService',
+
+ function (_ks_, _wss_) {
+ ks = _ks_;
+ wss = _wss_;
+
+ return EditableText;
+ },
+ ]);
+
+})();
diff --git a/web/gui/src/main/webapp/app/fw/svg/glyphData.js b/web/gui/src/main/webapp/app/fw/svg/glyphData.js
index 386a74c..9667aeb 100644
--- a/web/gui/src/main/webapp/app/fw/svg/glyphData.js
+++ b/web/gui/src/main/webapp/app/fw/svg/glyphData.js
@@ -355,6 +355,16 @@
'H39.4c0-7.2,8.4-13.1,15.7-13.1S70.6,72,70.6,79.2H96.3z' +
'M48,65.6L36.8,53c0-.5-1.5.5-1.4,0.7l5.3,16.6z',
+ pipeconfTable: tableFrame +
+ 'M10,66h13v3h-13z' +
+ 'M23,62.5L28,67.5L23,72.5z' +
+ 'M30,55h12.5v25h-12.5z' +
+ 'M45,66h13v3h-13z' +
+ 'M58,62.5L63,67.5L58,72.5z' +
+ 'M65,55h12.5v25h-12.5z' +
+ 'M79,66h15v3h-15z' +
+ 'M94,62.5L99,67.5L94,72.5z',
+
// --- Topology toolbar specific glyphs ----------------------
summary: 'M95.8,9.2H14.2c-2.8,0-5,2.2-5,5v81.5c0,2.8,2.2,5,5,' +
diff --git a/web/gui/src/main/webapp/app/fw/svg/icon.js b/web/gui/src/main/webapp/app/fw/svg/icon.js
index 9027960..f2a6b66 100644
--- a/web/gui/src/main/webapp/app/fw/svg/icon.js
+++ b/web/gui/src/main/webapp/app/fw/svg/icon.js
@@ -63,6 +63,7 @@
portTable: 'portTable',
groupTable: 'groupTable',
meterTable: 'meterTable',
+ pipeconfTable: 'pipeconfTable',
hostIcon_endstation: 'endstation',
hostIcon_router: 'router',
diff --git a/web/gui/src/main/webapp/app/fw/widget/table.css b/web/gui/src/main/webapp/app/fw/widget/table.css
index 2569031..4d4649a 100644
--- a/web/gui/src/main/webapp/app/fw/widget/table.css
+++ b/web/gui/src/main/webapp/app/fw/widget/table.css
@@ -52,10 +52,12 @@
padding: 4px;
text-align: left;
word-wrap: break-word;
- font-size: 12pt;
+ font-size: 10pt;
}
div.summary-list td.table-icon {
+ padding-top: 4px;
+ padding-bottom: 0px;
padding-left: 4px;
text-align: center;
}
@@ -64,10 +66,9 @@
font-weight: bold;
font-variant: small-caps;
text-transform: uppercase;
- font-size: 11pt;
- padding-top: 14px;
- padding-bottom: 14px;
-
+ font-size: 10pt;
+ padding-top: 8px;
+ padding-bottom: 8px;
letter-spacing: 0.02em;
cursor: pointer;
}
diff --git a/web/gui/src/main/webapp/app/onos.css b/web/gui/src/main/webapp/app/onos.css
index 52a0d63f..fa485ac 100644
--- a/web/gui/src/main/webapp/app/onos.css
+++ b/web/gui/src/main/webapp/app/onos.css
@@ -46,6 +46,6 @@
-webkit-margin-after: 0;
margin: 32px 0 4px 16px;
padding: 0;
- font-size: 21pt;
+ font-size: 18pt;
font-weight: lighter;
}
diff --git a/web/gui/src/main/webapp/app/view/app/app.css b/web/gui/src/main/webapp/app/view/app/app.css
index 4d61f81..a1963c0 100644
--- a/web/gui/src/main/webapp/app/view/app/app.css
+++ b/web/gui/src/main/webapp/app/view/app/app.css
@@ -55,7 +55,6 @@
/* -- Details Panel -- */
#application-details-panel.floatpanel {
z-index: 0;
- font-size: 12pt;
}
#application-details-panel.floatpanel a {
@@ -83,7 +82,7 @@
#application-details-panel h2 {
display: inline-block;
margin: 12px 0 6px 0;
- font-size: 13pt;
+ font-size: 11pt;
font-variant: small-caps;
text-transform: uppercase;
}
@@ -91,7 +90,7 @@
#application-details-panel .top .app-title {
width: 90%;
height: 62px;
- font-size: 20pt;
+ font-size: 16pt;
font-weight: lighter;
overflow: hidden;
white-space: nowrap;
@@ -115,8 +114,7 @@
#application-details-panel .app-url i {
font-weight: bold;
text-align: right;
- font-size: 12pt;
-
+ font-size: 10pt;
padding-right: 6px;
}
@@ -127,6 +125,7 @@
#application-details-panel .app-url {
padding: 10px 6px 6px;
overflow: hidden;
+ clear: left;
}
#application-details-panel hr {
@@ -139,7 +138,7 @@
}
#application-details-panel .bottom {
- padding: 12px 0 0 6px;
+ padding: 0px;
}
#application-details-panel .bottom table {
diff --git a/web/gui/src/main/webapp/app/view/app/app.html b/web/gui/src/main/webapp/app/view/app/app.html
index 54a5390..db5d69b 100644
--- a/web/gui/src/main/webapp/app/view/app/app.html
+++ b/web/gui/src/main/webapp/app/view/app/app.html
@@ -76,8 +76,8 @@
<td class="table-icon">
<div icon icon-id="{{app._iconid_state}}"></div>
</td>
- <td><img data-ng-src="./rs/applications/{{app.icon}}/icon"
- height="28px" width="28px" /></td>
+ <td class="table-icon"><img data-ng-src="./rs/applications/{{app.icon}}/icon"
+ height="24px" width="24px" /></td>
<td>{{app.title}}</td>
<td>{{app.id}}</td>
<td>{{app.version}}</td>
diff --git a/web/gui/src/main/webapp/app/view/cluster/cluster-theme.css b/web/gui/src/main/webapp/app/view/cluster/cluster-theme.css
new file mode 100644
index 0000000..b908721
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/cluster/cluster-theme.css
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Cluster View (theme) -- CSS file
+ */
+
+.light #cluster-details-panel .bottom th {
+ background-color: #e5e5e6;
+}
+
+.light #cluster-details-panel .bottom tr:nth-child(odd) {
+ background-color: #fbfbfb;
+}
+.light #cluster-details-panel .bottom tr:nth-child(even) {
+ background-color: #f4f4f4;
+}
+
diff --git a/web/gui/src/main/webapp/app/view/cluster/cluster.css b/web/gui/src/main/webapp/app/view/cluster/cluster.css
index 50622ad..1a93224 100644
--- a/web/gui/src/main/webapp/app/view/cluster/cluster.css
+++ b/web/gui/src/main/webapp/app/view/cluster/cluster.css
@@ -57,7 +57,7 @@
}
#cluster-details-panel .top-content table {
- font-size: 12pt;
+ font-size: 10pt;
}
#cluster-details-panel td.label {
@@ -65,3 +65,18 @@
text-align: right;
padding-right: 6px;
}
+
+#cluster-details-panel .bottom table {
+ border-spacing: 0;
+}
+
+#cluster-details-panel .bottom th {
+ letter-spacing: 0.02em;
+}
+
+#cluster-details-panel .bottom th,
+#cluster-details-panel .bottom td {
+ padding: 6px 12px;
+ text-align: center;
+ white-space: nowrap;
+}
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/view/device/device.css b/web/gui/src/main/webapp/app/view/device/device.css
index 2e23b3b..c578112 100644
--- a/web/gui/src/main/webapp/app/view/device/device.css
+++ b/web/gui/src/main/webapp/app/view/device/device.css
@@ -62,7 +62,8 @@
}
#device-details-panel .top-tables {
- font-size: 12pt;
+ font-size: 10pt;
+ white-space: nowrap;
}
#device-details-panel .top div.left {
diff --git a/web/gui/src/main/webapp/app/view/device/device.html b/web/gui/src/main/webapp/app/view/device/device.html
index ac0febf..c26e067 100644
--- a/web/gui/src/main/webapp/app/view/device/device.html
+++ b/web/gui/src/main/webapp/app/view/device/device.html
@@ -31,6 +31,11 @@
icon icon-id="meterTable" icon-size="42"
tooltip tt-msg="meterTip"
ng-click="nav('meter')"></div>
+
+ <div ng-class="{active: !!selId}"
+ icon icon-id="pipeconfTable" icon-size="42"
+ tooltip tt-msg="pipeconfTip"
+ ng-click="nav('pipeconf')"></div>
</div>
</div>
diff --git a/web/gui/src/main/webapp/app/view/device/device.js b/web/gui/src/main/webapp/app/view/device/device.js
index 4fbb0df..536b789 100644
--- a/web/gui/src/main/webapp/app/view/device/device.js
+++ b/web/gui/src/main/webapp/app/view/device/device.js
@@ -22,17 +22,14 @@
'use strict';
// injected refs
- var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks;
+ var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks, dps;
// internal state
var detailsPanel,
pStartY,
pHeight,
top,
- bottom,
- iconDiv,
wSize,
- editingName = false,
device;
// constants
@@ -40,19 +37,12 @@
ctnrPdg = 24,
scrollSize = 17,
portsTblPdg = 50,
- defaultLabelWidth = 110,
- defaultValueWidth = 80,
pName = 'device-details-panel',
detailsReq = 'deviceDetailsRequest',
detailsResp = 'deviceDetailsResponse',
nameChangeReq = 'deviceNameChangeRequest',
nameChangeResp = 'deviceNameChangeResponse',
- friendlyProps = [
- 'URI', 'Type', 'Master ID', 'Chassis ID',
- 'Vendor', 'H/W Version', 'S/W Version', 'Protocol', 'Serial #',
- 'Pipeconf',
- ],
portCols = [
'enabled', 'id', 'speed', 'type', 'elinks_dest', 'name',
],
@@ -60,6 +50,11 @@
'Enabled', 'ID', 'Speed', 'Type', 'Egress Links', 'Name',
];
+ var keyBindings = {
+ esc: [closePanel, 'Close the details panel'],
+ _helpFormat: ['esc'],
+ };
+
function closePanel() {
if (detailsPanel.isVisible()) {
$scope.selId = null;
@@ -69,119 +64,44 @@
return false;
}
- function addCloseBtn(div) {
- is.loadEmbeddedIcon(div, 'close', 20);
- div.on('click', closePanel);
- }
-
- function exitEditMode(nameH2, name) {
- nameH2.text(name);
- nameH2.classed('editable clickable', true);
- editingName = false;
- ks.enableGlobalKeys(true);
- }
-
- function editNameSave() {
- var nameH2 = top.select('h2'),
- id = $scope.panelData.id,
- val,
- newVal;
-
- if (editingName) {
- val = nameH2.select('input').property('value').trim();
- newVal = val || id;
-
- exitEditMode(nameH2, newVal);
- $scope.panelData.name = newVal;
- wss.sendEvent(nameChangeReq, { id: id, name: val });
- }
- }
-
- function editNameCancel() {
- if (editingName) {
- exitEditMode(top.select('h2'), $scope.panelData.name);
- return true;
- }
- return false;
- }
-
- function editName() {
- var nameH2 = top.select('h2'),
- tf, el;
-
- if (!editingName) {
- nameH2.classed('editable clickable', false);
- nameH2.text('');
- tf = nameH2.append('input').classed('name-input', true)
- .attr('type', 'text')
- .attr('value', $scope.panelData.name);
- el = tf[0][0];
- el.focus();
- el.select();
- editingName = true;
- ks.enableGlobalKeys(false);
- }
- }
-
- function handleEscape() {
- return editNameCancel() || closePanel();
- }
-
function setUpPanel() {
- var container, closeBtn, tblDiv;
- detailsPanel.empty();
+ // var container, closeBtn, tblDiv;
- container = detailsPanel.append('div').classed('container', true);
+ dps.empty();
+ dps.addContainers();
+ dps.addCloseButton(closePanel);
- top = container.append('div').classed('top', true);
- closeBtn = top.append('div').classed('close-btn', true);
- addCloseBtn(closeBtn);
- iconDiv = top.append('div').classed('dev-icon', true);
- top.append('h2').classed('editable clickable', true).on('click', editName);
+ var top = dps.top();
+ var bottom = dps.bottom();
- tblDiv = top.append('div').classed('top-tables', true);
- tblDiv.append('div').classed('left', true).append('table');
- tblDiv.append('div').classed('right', true).append('table');
+ dps.addHeading('dev-icon');
+ top.append('div').classed('top-content', true);
top.append('hr');
- bottom = container.append('div').classed('bottom', true);
bottom.append('h2').classed('ports-title', true).text('Ports');
bottom.append('table');
}
- function addProp(tbody, index, value) {
- var tr = tbody.append('tr');
-
- function addCell(cls, txt, width) {
- tr.append('td').attr('class', cls).attr('width', width).text(txt);
- }
- addCell('label', friendlyProps[index] + ' :', defaultLabelWidth);
- addCell('value', value, defaultValueWidth);
+ function friendlyPropsList(details) {
+ return {
+ 'URI': device.id,
+ 'Type': device.type,
+ 'Master ID': details['masterid'],
+ 'Chassis ID': details['chassid'],
+ 'Vendor': device.mfr,
+ 'H/W Version': device.hw,
+ 'S/W Version': device.sw,
+ 'Protocol': details['protocol'],
+ 'Serial #': device.serial,
+ 'Pipeconf': details['pipeconf'],
+ };
}
function populateTop(tblDiv, details) {
- var leftTbl = tblDiv.select('.left')
- .select('table')
- .append('tbody'),
- rightTbl = tblDiv.select('.right')
- .select('table')
- .append('tbody');
-
- is.loadEmbeddedIcon(iconDiv, details._iconid_type, 40);
- top.select('h2').text(details.name);
-
- // === demonstrate use of JsonCodec object see ONOS-5976
- addProp(leftTbl, 0, device.id);
- addProp(leftTbl, 1, device.type);
- addProp(leftTbl, 2, details['masterid']);
- addProp(leftTbl, 3, details['chassid']);
- addProp(leftTbl, 4, device.mfr);
- addProp(rightTbl, 5, device.hw);
- addProp(rightTbl, 6, device.sw);
- addProp(rightTbl, 7, details['protocol']);
- addProp(rightTbl, 8, device.serial);
- addProp(rightTbl, 9, details['pipeconf']);
+ is.loadEmbeddedIcon(dps.select('.iconDiv'), details._iconid_type, 40);
+ dps.top().select('h2').text(details.name);
+ dps.addPropsList(tblDiv, friendlyPropsList(details));
}
function addPortRow(tbody, port) {
@@ -193,6 +113,7 @@
}
function populateBottom(table, ports) {
+
var theader = table.append('thead').append('tr'),
tbody = table.append('tbody'),
tbWidth, tbHeight;
@@ -200,16 +121,15 @@
friendlyPortCols.forEach(function (col) {
theader.append('th').text(col);
});
+
ports.forEach(function (port) {
addPortRow(tbody, port);
});
tbWidth = fs.noPxStyle(tbody, 'width') + scrollSize;
tbHeight = pHeight
- - (fs.noPxStyle(detailsPanel.el()
- .select('.top'), 'height')
- + fs.noPxStyle(detailsPanel.el()
- .select('.ports-title'), 'height')
+ - (fs.noPxStyle(detailsPanel.el().select('.top'), 'height')
+ + fs.noPxStyle(detailsPanel.el().select('.ports-title'), 'height')
+ portsTblPdg);
table.style({
@@ -223,15 +143,14 @@
}
function populateDetails(details) {
- var topTbs, btmTbl, ports;
+ var btmTbl, ports;
setUpPanel();
- topTbs = top.select('.top-tables');
- btmTbl = bottom.select('table');
+ btmTbl = dps.bottom().select('table');
ports = details.ports;
- populateTop(topTbs, details);
+ populateTop(dps.select('.top-content'), details);
populateBottom(btmTbl, ports);
detailsPanel.height(pHeight);
@@ -250,18 +169,19 @@
}
}
- function createDetailsPane() {
- detailsPanel = ps.createPanel(pName, {
+ function createDetailsPanel() {
+ detailsPanel = dps.create(pName, {
width: wSize.width,
margin: 0,
hideMargin: 0,
+ scope: $scope,
+ keyBindings: keyBindings,
+ nameChangeRequest: nameChangeReq,
});
- detailsPanel.el().style({
- position: 'absolute',
- top: pStartY + 'px',
- });
+
+ dps.setResponse(detailsResp, respDetailsCb);
+
$scope.hidePanel = function () { detailsPanel.hide(); };
- detailsPanel.hide();
}
// Sample functions for detail panel creation
@@ -283,10 +203,10 @@
['$log', '$scope', '$location', 'TableBuilderService',
'TableDetailService', 'FnService',
'MastService', 'PanelService', 'WebSocketService', 'IconService',
- 'NavService', 'KeyService',
+ 'NavService', 'KeyService', 'DetailsPanelService',
function (_$log_, _$scope_, _$location_,
- tbs, tds, _fs_, _mast_, _ps_, _wss_, _is_, _ns_, _ks_) {
+ tbs, tds, _fs_, _mast_, _ps_, _wss_, _is_, _ns_, _ks_, _dps_) {
var params,
handlers = {};
@@ -300,6 +220,7 @@
is = _is_;
ns = _ns_;
ks = _ks_;
+ dps = _dps_;
params = $loc.search();
@@ -308,9 +229,10 @@
$scope.portTip = 'Show port view for selected device';
$scope.groupTip = 'Show group view for selected device';
$scope.meterTip = 'Show meter view for selected device';
+ $scope.pipeconfTip = 'Show pipeconf view for selected device';
// details panel handlers
- handlers[detailsResp] = respDetailsCb;
+ // handlers[detailsResp] = respDetailsCb;
handlers[nameChangeResp] = respNameCb;
wss.bindHandlers(handlers);
@@ -352,6 +274,7 @@
};
$scope.$on('$destroy', function () {
+ dps.destroy();
wss.unbindHandlers(handlers);
});
@@ -373,7 +296,7 @@
function initPanel() {
heightCalc();
- createDetailsPane();
+ createDetailsPanel();
}
// Safari has a bug where it renders the fixed-layout table wrong
@@ -384,11 +307,8 @@
initPanel();
}
// create key bindings to handle panel
- ks.keyBindings({
- enter: editNameSave,
- esc: [handleEscape, 'Close the details panel'],
- _helpFormat: ['esc'],
- });
+ ks.keyBindings(keyBindings);
+
ks.gestureNotes([
['click', 'Select a row to show device details'],
['scroll down', 'See more devices'],
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.html b/web/gui/src/main/webapp/app/view/flow/flow.html
index af24a63..a6fed1a 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.html
+++ b/web/gui/src/main/webapp/app/view/flow/flow.html
@@ -53,6 +53,11 @@
tooltip tt-msg="meterTip"
ng-click="nav('meter')"></div>
+ <div class="active"
+ icon icon-id="pipeconfTable" icon-size="42"
+ tooltip tt-msg="pipeconfTip"
+ ng-click="nav('pipeconf')"></div>
+
</div>
<div class="search">
@@ -62,7 +67,7 @@
<option value="$">All Fields</option>
<!--<option value="id">Flow ID</option>-->
<option value="priority">Priority</option>
- <option value="tableId">Table ID</option>
+ <option value="tableName">Table Name</option>
<option value="selector">Selector</option>
<option value="treatment">Treatment</option>
<option value="appName">App Name</option>
@@ -81,7 +86,7 @@
<td class="right" colId="packets" col-width="90px" sortable> Packets </td>
<td class="right" colId="duration" col-width="90px" sortable> Duration </td>
<td colId="priority" col-width="80px" sortable> Priority </td>
- <td colId="tableId" col-width="80px" sortable> Table ID </td>
+ <td colId="tableName" col-width="120px" sortable> Table Name </td>
<td colId="selector" sortable> Selector </td>
<td colId="treatment" sortable> Treatment </td>
<td colId="appName" sortable> App Name </td>
@@ -108,7 +113,7 @@
<td class="right">{{flow.packets}}</td>
<td class="right">{{flow.duration}}</td>
<td>{{flow.priority}}</td>
- <td>{{flow.tableId}}</td>
+ <td>{{flow.tableName}}</td>
<td>{{flow.selector_c}}</td>
<td>{{flow.treatment_c}}</td>
<td>{{flow.appName}}</td>
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.js b/web/gui/src/main/webapp/app/view/flow/flow.js
index 2000997..71794bd 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.js
+++ b/web/gui/src/main/webapp/app/view/flow/flow.js
@@ -54,7 +54,7 @@
'duration',
'priority',
- 'tableId',
+ 'tableName',
'appName',
'appId',
@@ -71,7 +71,7 @@
'Duration',
'Flow Priority',
- 'Table ID',
+ 'Table Name',
'App Name',
'App ID',
@@ -246,8 +246,10 @@
$scope.portTip = 'Show port view for this device';
$scope.groupTip = 'Show group view for this device';
$scope.meterTip = 'Show meter view for selected device';
+ $scope.pipeconfTip = 'Show pipeconf view for selected device';
$scope.briefTip = 'Switch to brief view';
$scope.detailTip = 'Switch to detailed view';
+
$scope.brief = true;
params = $location.search();
if (params.hasOwnProperty('devId')) {
diff --git a/web/gui/src/main/webapp/app/view/group/group.html b/web/gui/src/main/webapp/app/view/group/group.html
index a09dd18..fb6b277 100644
--- a/web/gui/src/main/webapp/app/view/group/group.html
+++ b/web/gui/src/main/webapp/app/view/group/group.html
@@ -52,6 +52,11 @@
icon icon-id="meterTable" icon-size="42"
tooltip tt-msg="meterTip"
ng-click="nav('meter')"></div>
+
+ <div class="active"
+ icon icon-id="pipeconfTable" icon-size="42"
+ tooltip tt-msg="pipeconfTip"
+ ng-click="nav('pipeconf')"></div>
</div>
<div class="search">
diff --git a/web/gui/src/main/webapp/app/view/group/group.js b/web/gui/src/main/webapp/app/view/group/group.js
index 063daef..0b36d2c 100644
--- a/web/gui/src/main/webapp/app/view/group/group.js
+++ b/web/gui/src/main/webapp/app/view/group/group.js
@@ -41,6 +41,7 @@
$scope.flowTip = 'Show flow view for this device';
$scope.portTip = 'Show port view for this device';
$scope.meterTip = 'Show meter view for selected device';
+ $scope.pipeconfTip = 'Show pipeconf view for selected device';
$scope.briefTip = 'Switch to brief view';
$scope.detailTip = 'Switch to detailed view';
$scope.brief = true;
diff --git a/web/gui/src/main/webapp/app/view/host/host.css b/web/gui/src/main/webapp/app/view/host/host.css
index b1aa914..461373f 100644
--- a/web/gui/src/main/webapp/app/view/host/host.css
+++ b/web/gui/src/main/webapp/app/view/host/host.css
@@ -62,7 +62,7 @@
}
#host-details-panel .top-tables {
- font-size: 12pt;
+ font-size: 10pt;
}
#host-details-panel .top div.left {
diff --git a/web/gui/src/main/webapp/app/view/host/host.js b/web/gui/src/main/webapp/app/view/host/host.js
index 9cb674c..37668bb 100644
--- a/web/gui/src/main/webapp/app/view/host/host.js
+++ b/web/gui/src/main/webapp/app/view/host/host.js
@@ -22,16 +22,14 @@
'use strict';
// injected refs
- var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks;
+ var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks, dps;
// internal state
var detailsPanel,
pStartY,
pHeight,
top,
- iconDiv,
- wSize,
- editingName = false;
+ wSize;
// constants
var topPdg = 28,
@@ -41,13 +39,10 @@
nameChangeReq = 'hostNameChangeRequest',
nameChangeResp = 'hostNameChangeResponse';
- var propOrder = [
- 'id', 'ip', 'mac', 'vlan', 'configured', 'location',
- ],
- friendlyProps = [
- 'Host ID', 'IP Address', 'MAC Address', 'VLAN',
- 'Configured', 'Location',
- ];
+ var keyBindings = {
+ esc: [closePanel, 'Close the details panel'],
+ _helpFormat: ['esc'],
+ };
function closePanel() {
if (detailsPanel.isVisible()) {
@@ -58,105 +53,41 @@
return false;
}
- function addCloseBtn(div) {
- is.loadEmbeddedIcon(div, 'close', 20);
- div.on('click', closePanel);
- }
-
- function exitEditMode(nameH2, name) {
- nameH2.text(name);
- nameH2.classed('editable clickable', true);
- editingName = false;
- ks.enableGlobalKeys(true);
- }
-
- function editNameSave() {
- var nameH2 = top.select('h2'),
- id = $scope.panelData.id,
- ip = $scope.panelData.ip,
- val,
- newVal;
-
- if (editingName) {
- val = nameH2.select('input').property('value').trim();
- newVal = val || ip;
-
- exitEditMode(nameH2, newVal);
- $scope.panelData.name = newVal;
- wss.sendEvent(nameChangeReq, { id: id, name: val });
- }
- }
-
- function editNameCancel() {
- if (editingName) {
- exitEditMode(top.select('h2'), $scope.panelData.name);
- return true;
- }
- return false;
- }
-
- function editName() {
- var nameH2 = top.select('h2'),
- tf, el;
-
- if (!editingName) {
- nameH2.classed('editable clickable', false);
- nameH2.text('');
- tf = nameH2.append('input').classed('name-input', true)
- .attr('type', 'text')
- .attr('value', $scope.panelData.name);
- el = tf[0][0];
- el.focus();
- el.select();
- editingName = true;
- ks.enableGlobalKeys(false);
- }
- }
-
- function handleEscape() {
- return editNameCancel() || closePanel();
- }
function setUpPanel() {
- var container, closeBtn;
- detailsPanel.empty();
- container = detailsPanel.append('div').classed('container', true);
+ dps.empty();
+ dps.addContainers();
+ dps.addCloseButton(closePanel);
- top = container.append('div').classed('top', true);
- closeBtn = top.append('div').classed('close-btn', true);
- addCloseBtn(closeBtn);
- iconDiv = top.append('div').classed('host-icon', true);
- top.append('h2').classed('editable clickable', true).on('click', editName);
+ var top = dps.top();
- top.append('div').classed('top-tables', true);
+ dps.addHeading('host-icon');
+ top.append('div').classed('top-content', true);
+
top.append('hr');
}
- function addProp(tbody, index, value) {
- var tr = tbody.append('tr');
-
- function addCell(cls, txt) {
- tr.append('td').attr('class', cls).text(txt);
- }
- addCell('label', friendlyProps[index] + ' :');
- addCell('value', value);
+ function friendlyPropsList(details) {
+ return {
+ 'Host ID': details.id,
+ 'IP Address': details.ip[0],
+ 'MAC Address': details.mac,
+ 'VLAN': details.vlan,
+ 'Configured': details.configured,
+ 'Location': details.location,
+ };
}
- function populateTop(details) {
- var tab = top.select('.top-tables').append('tbody');
-
- is.loadEmbeddedIcon(iconDiv, details._iconid_type, 40);
- top.select('h2').text(details.name);
-
- propOrder.forEach(function (prop, i) {
- addProp(tab, i, details[prop]);
- });
+ function populateTop(tblDiv, details) {
+ is.loadEmbeddedIcon(dps.select('.iconDiv'), details._iconid_type, 40);
+ dps.top().select('h2').text(details.name);
+ dps.addPropsList(tblDiv, friendlyPropsList(details));
}
function populateDetails(details) {
setUpPanel();
- populateTop(details);
+ populateTop(dps.select('.top-content'), details);
detailsPanel.height(pHeight);
// configure width based on content.. for now hardcoded
detailsPanel.width(400);
@@ -174,18 +105,19 @@
}
}
- function createDetailsPane() {
- detailsPanel = ps.createPanel(pName, {
+ function createDetailsPanel() {
+ detailsPanel = dps.create(pName, {
width: wSize.width,
margin: 0,
hideMargin: 0,
+ scope: $scope,
+ keyBindings: keyBindings,
+ nameChangeRequest: nameChangeReq,
});
- detailsPanel.el().style({
- position: 'absolute',
- top: pStartY + 'px',
- });
+
+ dps.setResponse(detailsResp, respDetailsCb);
+
$scope.hidePanel = function () { detailsPanel.hide(); };
- detailsPanel.hide();
}
@@ -196,12 +128,12 @@
'$location',
'TableBuilderService',
'FnService', 'MastService', 'PanelService', 'WebSocketService',
- 'IconService', 'NavService', 'KeyService',
+ 'IconService', 'NavService', 'KeyService', 'DetailsPanelService',
function (_$log_, _$scope_, _$location_,
tbs,
_fs_, _mast_, _ps_, _wss_,
- _is_, _ns_, _ks_) {
+ _is_, _ns_, _ks_, _dps_) {
var params,
handlers = {};
@@ -216,13 +148,13 @@
is = _is_;
ns = _ns_;
ks = _ks_;
+ dps = _dps_;
params = $loc.search();
$scope.panelData = {};
// details panel handlers
- handlers[detailsResp] = respDetailsCb;
handlers[nameChangeResp] = respNameCb;
wss.bindHandlers(handlers);
@@ -254,6 +186,7 @@
};
$scope.$on('$destroy', function () {
+ dps.destroy();
wss.unbindHandlers(handlers);
});
@@ -261,7 +194,7 @@
}])
.directive('hostDetailsPanel',
- ['$rootScope', '$window', '$timeout', 'KeyService',
+ ['$rootScope', '$window', '$timeout', 'KeyService', 'DetailsPanelService',
function ($rootScope, $window, $timeout, ks) {
return function (scope) {
var unbindWatch;
@@ -275,7 +208,7 @@
function initPanel() {
heightCalc();
- createDetailsPane();
+ createDetailsPanel();
}
// Safari has a bug where it renders the fixed-layout table wrong
@@ -286,11 +219,7 @@
initPanel();
}
// create key bindings to handle panel
- ks.keyBindings({
- enter: editNameSave,
- esc: [handleEscape, 'Close the details panel'],
- _helpFormat: ['esc'],
- });
+ ks.keyBindings(keyBindings);
ks.gestureNotes([
['click', 'Select a row to show device details'],
['scroll down', 'See more devices'],
diff --git a/web/gui/src/main/webapp/app/view/meter/meter.html b/web/gui/src/main/webapp/app/view/meter/meter.html
index cd83392..ddd17f0 100644
--- a/web/gui/src/main/webapp/app/view/meter/meter.html
+++ b/web/gui/src/main/webapp/app/view/meter/meter.html
@@ -36,6 +36,11 @@
<div class="current-view"
icon icon-id="meterTable" icon-size="42"></div>
+
+ <div class="active"
+ icon icon-id="pipeconfTable" icon-size="42"
+ tooltip tt-msg="pipeconfTip"
+ ng-click="nav('pipeconf')"></div>
</div>
<div class="search">
diff --git a/web/gui/src/main/webapp/app/view/meter/meter.js b/web/gui/src/main/webapp/app/view/meter/meter.js
index bec7d2e..32058b6 100644
--- a/web/gui/src/main/webapp/app/view/meter/meter.js
+++ b/web/gui/src/main/webapp/app/view/meter/meter.js
@@ -40,6 +40,7 @@
$scope.flowTip = 'Show flow view for this device';
$scope.portTip = 'Show port view for this device';
$scope.groupTip = 'Show group view for this device';
+ $scope.pipeconfTip = 'Show pipeconf view for selected device';
params = $location.search();
if (params.hasOwnProperty('devId')) {
diff --git a/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.css b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.css
new file mode 100644
index 0000000..81eb603
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.css
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Base */
+#pipeconf-info h2 {
+ display: inline-block;
+ margin: 10px 0 10px 0;
+}
+
+#pipeconf-info h3 {
+ display: inline-block;
+ margin-bottom: 10px;
+}
+
+#pipeconf-info {
+ height: inherit;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ padding-left: 16px;
+ padding-right: 20px;
+}
+
+#pipeconf-info::-webkit-scrollbar {
+ display: none;
+}
+
+/* Table */
+.pipeconf-table {
+ width: 100%;
+}
+
+.pipeconf-table tr {
+ transition: background-color 500ms;
+ text-align: left;
+ padding: 8px 4px;
+}
+
+.pipeconf-table .selected {
+ background-color: #dbeffc !important;
+}
+
+.pipeconf-table tr:nth-child(even) {
+ background-color: #f4f4f4;
+}
+
+.pipeconf-table tr:nth-child(odd) {
+ background-color: #fbfbfb;
+}
+
+.pipeconf-table tr th {
+ background-color: #e5e5e6;
+ color: #3c3a3a;
+ font-weight: bold;
+ font-variant: small-caps;
+ text-transform: uppercase;
+ font-size: 10pt;
+ letter-spacing: 0.02em;
+}
+
+.pipeconf-table tr td {
+ padding: 4px;
+ text-align: left;
+ word-wrap: break-word;
+ font-size: 10pt;
+}
+
+.pipeconf-table tr td p {
+ margin: 5px 0;
+}
+
+/* Detail panel */
+.container {
+ padding: 10px;
+ overflow: hidden;
+}
+
+.container .top, .bottom {
+ padding: 15px;
+}
+.container .bottom {
+ overflow-y: scroll;
+}
+
+.container .bottom h2 {
+ margin: 0;
+}
+
+.container .top .detail-panel-header {
+ display: inline-block;
+ margin: 0 0 10px;
+}
+
+
+.detail-panel-table {
+ width: 100%;
+ overflow-y: hidden;
+}
+
+.detail-panel-table td, th {
+ text-align: left;
+ padding: 6px 12px;
+}
+
+.top-info {
+ font-size: 12pt;
+}
+
+.top-info .label {
+ font-weight: bold;
+ text-align: right;
+ display: inline-block;
+ margin: 0;
+ padding-right:6px;
+}
+
+.top-info .value {
+ margin: 0;
+ text-align: left;
+ display: inline-block;
+}
+
+/* Widgets */
+#ov-pipeconf h2 {
+ display: inline-block;
+}
+
+.collapse-btn {
+ cursor: pointer;
+ display: inline-block;
+ max-height: 30px;
+ overflow-y: hidden;
+ position: relative;
+ top: 8px;
+}
+
+.close-btn {
+ display: inline-block;
+ float: right;
+ margin: 0.1em;
+ cursor: pointer;
+}
+
+.text-center {
+ text-align: center !important;
+}
+
+.no-data {
+ text-align: center !important;
+ font-style: italic;
+}
diff --git a/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.html b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.html
new file mode 100644
index 0000000..4d4e8f6
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.html
@@ -0,0 +1,204 @@
+<!--
+ ~ Copyright 2017-present Open Networking Foundation
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<div id="ov-pipeconf">
+ <div class="tabular-header">
+ <h2>
+ Pipeconf for Device {{devId || "(No device selected)"}}
+ </h2>
+
+ <div class="ctrl-btns">
+ <div class="refresh" ng-class="{active: autoRefresh}"
+ icon icon-size="42" icon-id="refresh"
+ tooltip tt-msg="autoRefreshTip"
+ ng-click="autoRefresh = !autoRefresh"></div>
+
+ <div class="separator"></div>
+
+ <div class="active"
+ icon icon-id="deviceTable" icon-size="42"
+ tooltip tt-msg="deviceTip"
+ ng-click="nav('device')"></div>
+
+ <div class="active"
+ icon icon-id="flowTable" icon-size="42"
+ tooltip tt-msg="flowTip"
+ ng-click="nav('flow')"></div>
+
+ <div class="active"
+ icon icon-id="portTable" icon-size="42"
+ tooltip tt-msg="portTip"
+ ng-click="nav('port')"></div>
+
+ <div class="active"
+ icon icon-id="groupTable" icon-size="42"
+ tooltip tt-msg="groupTip"
+ ng-click="nav('group')"></div>
+
+ <div class="active"
+ icon icon-id="meterTable" icon-size="42"
+ tooltip tt-msg="meterTip"
+ ng-click="nav('meter')"></div>
+
+ <div class="current-view"
+ icon icon-id="pipeconfTable" icon-size="42"
+ tooltip tt-msg="pipeconfTip"></div>
+ </div>
+ </div>
+ <div id="pipeconf-info" auto-height>
+ <div id="pipeconf-basic">
+ <h2>Basic information</h2>
+ <table class="pipeconf-table">
+ <tr>
+ <th class="text-center" style="width: 160px">Name</th>
+ <th>Info</th>
+ </tr>
+ <tr ng-show="collapsePipeconf">
+ <td colspan="2" class="text-center">....</td>
+ </tr>
+ <tr ng-show="pipeconf === null">
+ <td colspan="2" class="no-data">
+ No PiPipeconf for this device
+ </td>
+ </tr>
+ <tr ng-show-start="!collapsePipeconf && pipeconf !== null">
+ <td class="text-center">ID</td>
+ <td>{{pipeconf.id}}</td>
+ </tr>
+ <tr>
+ <td class="text-center">Behaviors</td>
+ <td>{{pipeconf.behaviors.join(", ")}}</td>
+ </tr>
+ <tr ng-show-end>
+ <td class="text-center">Extensions</td>
+ <td>{{pipeconf.extensions.join(", ")}}</td>
+ </tr>
+ </table>
+ </div>
+ <!-- ng-show-start for checking pipeconf !== null -->
+ <h2 ng-show-start="pipeconf !== null">Pipeline Model</h2>
+ <div id="pipeconf-headers">
+ <h3>Headers</h3>
+ <div ng-show="!collapseHeaders"
+ class="collapse-btn" icon icon-id="plus" icon-size="30"
+ ng-click="collapseHeaders = !collapseHeaders"></div>
+ <div ng-show="collapseHeaders"
+ class="collapse-btn" icon icon-id="minus" icon-size="30"
+ ng-click="collapseHeaders = !collapseHeaders"></div>
+ <table class="pipeconf-table">
+ <tr>
+ <th style="width: 160px">Name</th>
+ <th class="text-center" style="width: 100px">Is metadata</th>
+ <th class="text-center" style="width: 100px">Index</th>
+ <th>Header type</th>
+ </tr>
+ <tr ng-show="collapseHeaders">
+ <td colspan="4" class="clickable text-center"
+ ng-click="collapseHeaders = !collapseHeaders">....</td>
+ </tr>
+ <tr ng-show="!collapseHeaders && pipelineModel.headers.length === 0">
+ <td colspan="4" class="no-data">No Data</td>
+ </tr>
+ <tr ng-show="!collapseHeaders"
+ ng-repeat="header in pipelineModel.headers"
+ ng-click="headerSelectCb($event, header)"
+ ng-class="{selected: header.name === selectedId.name && selectedId.type === 'header'}"
+ class="clickable">
+ <td>{{header.name}}</td>
+ <td class="text-center">{{header.isMetadata}}</td>
+ <td class="text-center">{{header.index}}</td>
+ <td>{{header.type.name}}</td>
+ </tr>
+ </table>
+ </div>
+ <div id="pipeconf-actions">
+ <h3>Actions</h3>
+ <div ng-show="!collapseActions"
+ class="collapse-btn" icon icon-id="plus" icon-size="30"
+ ng-click="collapseActions = !collapseActions"></div>
+ <div ng-show="collapseActions"
+ class="collapse-btn" icon icon-id="minus" icon-size="30"
+ ng-click="collapseActions = !collapseActions"></div>
+ <table class="pipeconf-table">
+ <tr>
+ <th style="width: 160px">Name</th>
+ <th>Action parameters</th>
+ </tr>
+ <tr ng-show="collapseActions">
+ <td colspan="2" class="clickable text-center"
+ ng-click="collapseActions = !collapseActions">....</td>
+ </tr>
+ <tr ng-show="!collapseActions && pipelineModel.actions.length === 0">
+ <td colspan="2" class="no-data">No Data</td>
+ </tr>
+ <tr ng-show="!collapseActions"
+ ng-repeat="action in pipelineModel.actions"
+ ng-click="actionSelectCb($event, action)"
+ ng-class="{selected: action.name === selectedId.name && selectedId.type === 'action'}"
+ class="clickable">
+ <td style="width: 160px">{{action.name}}</td>
+ <td ng-show="action.params.length != 0">{{ mapToNames(action.params).join(', ') }}</td>
+ <td ng-show="action.params.length == 0">No action params</td>
+ </tr>
+ </table>
+ </div>
+
+ <div id="pipeconf-tables" ng-show-end>
+ <h3>Tables</h3>
+ <div ng-show="!collapseTables" class="collapse-btn"
+ icon icon-id="plus" icon-size="30"
+ ng-click="collapseTables = !collapseTables"></div>
+ <div ng-show="collapseTables" class="collapse-btn"
+ icon icon-id="minus" icon-size="30"
+ ng-click="collapseTables = !collapseTables"></div>
+ <table class="pipeconf-table">
+ <tr>
+ <th class="text-center" style="width: 160px">Name</th>
+ <th class="text-center" style="width: 100px">Max size</th>
+ <th class="text-center" style="width: 100px">Has Counters</th>
+ <th class="text-center" style="width: 100px">Support Aging</th>
+ <th>Match fields</th>
+ <th>Actions</th>
+ </tr>
+ <tr ng-show="collapseTables">
+ <td colspan="6" class="clickable text-center"
+ ng-click="collapseTables = !collapseTables">....</td>
+ </tr>
+ <tr ng-show="!collapseTables && pipelineModel.tables.length === 0">
+ <td colspan="6" class="no-data">No Data</td>
+ </tr>
+ <tr ng-show="!collapseTables"
+ ng-repeat="table in pipelineModel.tables"
+ ng-click="tableSelectCb($event, table)"
+ ng-class="{selected: table.name === selectedId.name && selectedId.type === 'table'}"
+ class="clickable">
+ <td class="text-center">{{table.name}}</td>
+ <td class="text-center">{{table.maxSize}}</td>
+ <td class="text-center">{{table.hasCounters}}</td>
+ <td class="text-center">{{table.supportAging}}</td>
+ <td ng-show="table.matchFields.length != 0">
+ {{matMatchFields(table.matchFields).join(', ')}}
+ </td>
+ <td ng-show="table.matchFields.length == 0">No match fields</td>
+ <td ng-show="table.actions.length != 0">{{table.actions.join(", ")}}</td>
+ <td ng-show="table.actions.length == 0">No table actions</td>
+ </tr>
+ </table>
+ </div>
+ <!-- ng-show-end for checking pipeconf !== null -->
+ </div>
+ <pipeconf-view-detail-panel></pipeconf-view-detail-panel>
+</div>
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.js b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.js
new file mode 100644
index 0000000..f5156bb
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/pipeconf/pipeconf.js
@@ -0,0 +1,464 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Pipeconf View Module
+ */
+
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log, $scope, $loc, $interval, $timeout, fs, ns, wss, ls, ps, mast, is, dps;
+
+ // Constants
+ var pipeconfRequest = "pipeconfRequest",
+ pipeConfResponse = "pipeConfResponse",
+ noPipeconfResp = "noPipeconfResp",
+ invalidDevId = "invalidDevId",
+ pipeconf = "pipeconf",
+ pipelineModel = "pipelineModel",
+ devId = "devId",
+ topPdg = 28,
+ pName = 'pipeconf-detail-panel',
+ refreshRate = 5000;
+
+ // For request handling
+ var handlers,
+ refreshPromise;
+
+ // Details panel
+ var pWidth = 600,
+ pTopHeight = 111,
+ pStartY,
+ wSize,
+ pHeight,
+ detailsPanel;
+
+ // create key bindings to handle panel
+ var keyBindings = {
+ esc: [closePanel, 'Close the details panel'],
+ _helpFormat: ['esc'],
+ };
+
+ function fetchPipeconfData() {
+ if ($scope.autoRefresh && wss.isConnected() && !ls.waiting()) {
+ ls.start();
+ var requestData = {
+ devId: $scope.devId
+ };
+ wss.sendEvent(pipeconfRequest, requestData);
+ }
+ }
+
+ function pipeConfRespCb(data) {
+ ls.stop();
+ if (!data.hasOwnProperty(pipeconf)) {
+ $scope.pipeconf = null;
+ return;
+ }
+ $scope.pipeconf = data[pipeconf];
+ $scope.pipelineModel = data[pipelineModel];
+ $scope.$apply();
+ }
+
+ function noPipeconfRespCb(data) {
+ ls.stop();
+ $scope.pipeconf = null;
+ $scope.pipelineModel = null;
+ $scope.$apply();
+ }
+
+ function viewDestroy() {
+ wss.unbindHandlers(handlers);
+ $interval.cancel(refreshPromise);
+ refreshPromise = null;
+ ls.stop();
+ }
+
+ function headerSelectCb($event, header) {
+ if ($scope.selectedId !== null &&
+ $scope.selectedId.type === 'header' &&
+ $scope.selectedId.name === header.name) {
+
+ // Hide the panel when select same row
+ closePanel();
+ return;
+ }
+
+ $scope.selectedId = {
+ type: 'header',
+ name: header.name,
+ };
+
+ var subtitles = [
+ {
+ label: 'Header Type: ',
+ value: header.type.name,
+ },
+ {
+ label: 'Is metadata: ',
+ value: header.isMetadata,
+ },
+ {
+ label: 'Index: ',
+ value: header.index,
+ },
+ ];
+
+ var tables = [
+ {
+ title: 'Fields',
+ headers: ['Name', 'Bit width'],
+ data: header.type.fields,
+ noDataText: 'No header fields'
+ },
+ ];
+ populateDetailPanel(header.name, subtitles, tables);
+ }
+
+ function actionSelectCb($event, action) {
+ if ($scope.selectedId !== null &&
+ $scope.selectedId.type === 'action' &&
+ $scope.selectedId.name === action.name) {
+
+ // Hide the panel when select same row
+ closePanel();
+ return;
+ }
+
+ $scope.selectedId = {
+ type: 'action',
+ name: action.name,
+ };
+
+ var subtitles = [];
+ var tables = [
+ {
+ title: 'Parameters',
+ headers: ['Name', 'Bit width'],
+ data: action.params,
+ noDataText: 'No action parameters',
+ },
+ ];
+
+ populateDetailPanel(action.name, subtitles, tables);
+ }
+
+ function tableSelectCb($event, table) {
+ if ($scope.selectedId !== null &&
+ $scope.selectedId.type === 'table' &&
+ $scope.selectedId.name === table.name) {
+
+ // Hide the panel when select same row
+ closePanel();
+ return;
+ }
+
+ $scope.selectedId = {
+ type: 'table',
+ name: table.name,
+ };
+
+ var subtitles = [
+ {
+ label: 'Max Size: ',
+ value: table.maxSize,
+ },
+ {
+ label: 'Has counters: ',
+ value: table.hasCounters,
+ },
+ {
+ label: 'Support Aging: ',
+ value: table.supportAging,
+ },
+ ];
+
+ var matchFields = table.matchFields.map(function(mp) {
+ return {
+ name: mp.header.name + '.' + mp.field.name,
+ bitWidth: mp.field.bitWidth,
+ matchType: mp.matchType,
+ }
+ });
+
+ var tables = [
+ {
+ title: 'Match fields',
+ headers: ['Name', 'Bit width', 'Match type'],
+ data: matchFields,
+ noDataText: 'No match fields'
+ },
+ {
+ title: 'Actions',
+ headers: ['Name'],
+ data: table.actions,
+ noDataText: 'No actions'
+ },
+ ];
+
+ populateDetailPanel(table.name, subtitles, tables);
+ }
+
+ function closePanel() {
+ if (detailsPanel.isVisible()) {
+
+ detailsPanel.hide();
+
+ // Avoid Angular inprog error
+ $timeout(function() {
+ $scope.selectedId = null;
+ }, 0);
+ return true;
+ }
+ return false;
+ }
+
+ function populateDetailTable(tableContainer, table) {
+ var tableTitle = table.title;
+ var tableData = table.data;
+ var tableHeaders = table.headers;
+ var noDataText = table.noDataText;
+
+ tableContainer.append('h2').classed('detail-panel-bottom-title', true).text(tableTitle);
+
+ var detailPanelTable = tableContainer.append('table').classed('detail-panel-table', true);
+ var headerTr = detailPanelTable.append('tr').classed('detail-panel-table-header', true);
+
+ tableHeaders.forEach(function(h) {
+ headerTr.append('th').text(h);
+ });
+
+ if (tableData.length === 0) {
+ var row = detailPanelTable.append('tr').classed('detail-panel-table-row', true);
+ row.append('td')
+ .classed('detail-panel-table-col no-data', true)
+ .attr('colspan', tableHeaders.length)
+ .text(noDataText);
+ }
+
+ tableData.forEach(function(data) {
+ var row = detailPanelTable.append('tr').classed('detail-panel-table-row', true);
+ if (fs.isS(data)) {
+ row.append('td').classed('detail-panel-table-col', true).text(data);
+ } else {
+ Object.keys(data).forEach(function(k) {
+ row.append('td').classed('detail-panel-table-col', true).text(data[k]);
+ });
+ }
+ });
+
+ tableContainer.append('hr');
+ }
+
+ function populateDetailTables(tableContainer, tables) {
+ tables.forEach(function(table) {
+ populateDetailTable(tableContainer, table);
+ })
+ }
+
+ function populateDetailPanel(topTitle, topSubtitles, tables) {
+ dps.empty();
+ dps.addContainers();
+ dps.addCloseButton(closePanel);
+
+ var top = dps.top();
+ top.append('h2').classed('detail-panel-header', true).text(topTitle);
+ topSubtitles.forEach(function(st) {
+ var typeText = top.append('div').classed('top-info', true);
+ typeText.append('p').classed('label', true).text(st.label);
+ typeText.append('p').classed('value', true).text(st.value);
+ });
+
+ var bottom = dps.bottom();
+ var bottomHeight = pHeight - pTopHeight - 60;
+ bottom.style('height', bottomHeight + 'px');
+ populateDetailTables(bottom, tables);
+
+ detailsPanel.width(pWidth);
+ detailsPanel.show();
+ resizeDetailPanel();
+ }
+
+ function heightCalc() {
+ pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
+ + mast.mastHeight() + topPdg;
+ wSize = fs.windowSize(pStartY);
+ pHeight = wSize.height - 20;
+ }
+
+ function resizeDetailPanel() {
+ if (detailsPanel.isVisible()) {
+ heightCalc();
+ var bottomHeight = pHeight - pTopHeight - 60;
+ d3.select('.bottom').style('height', bottomHeight + 'px');
+ detailsPanel.height(pHeight);
+ }
+ }
+
+ angular.module('ovPipeconf', [])
+ .controller('OvPipeconfCtrl',
+ ['$log', '$scope', '$location', '$interval', '$timeout', 'FnService', 'NavService', 'WebSocketService',
+ 'LoadingService', 'PanelService', 'MastService', 'IconService', 'DetailsPanelService',
+ function (_$log_, _$scope_, _$loc_, _$interval_, _$timeout_, _fs_,
+ _ns_, _wss_, _ls_, _ps_, _mast_, _is_, _dps_) {
+ $log = _$log_;
+ $scope = _$scope_;
+ $loc = _$loc_;
+ $interval = _$interval_;
+ $timeout = _$timeout_;
+ fs = _fs_;
+ ns = _ns_;
+ wss = _wss_;
+ ls = _ls_;
+ ps = _ps_;
+ mast = _mast_;
+ is = _is_;
+ dps = _dps_;
+
+ $scope.deviceTip = 'Show device table';
+ $scope.flowTip = 'Show flow view for this device';
+ $scope.portTip = 'Show port view for this device';
+ $scope.groupTip = 'Show group view for this device';
+ $scope.meterTip = 'Show meter view for selected device';
+ $scope.pipeconfTip = 'Show pipeconf view for selected device';
+
+ var params = $loc.search();
+ if (params.hasOwnProperty(devId)) {
+ $scope.devId = params[devId];
+ }
+ $scope.nav = function (path) {
+ if ($scope.devId) {
+ ns.navTo(path, { devId: $scope.devId });
+ }
+ };
+ handlers = {
+ pipeConfResponse: pipeConfRespCb,
+ noPipeconfResp: noPipeconfRespCb,
+ invalidDevId: noPipeconfRespCb,
+ };
+ wss.bindHandlers(handlers);
+ $scope.$on('$destroy', viewDestroy);
+
+ $scope.autoRefresh = true;
+ fetchPipeconfData();
+
+ // On click callbacks, initialize select id
+ $scope.selectedId = null;
+ $scope.headerSelectCb = headerSelectCb;
+ $scope.actionSelectCb = actionSelectCb;
+ $scope.tableSelectCb = tableSelectCb;
+
+ // Make them collapsable
+ $scope.collapsePipeconf = false;
+ $scope.collapseHeaders = false;
+ $scope.collapseActions = false;
+ $scope.collapseTables = false;
+
+ $scope.mapToNames = function(data) {
+ return data.map(function(d) {
+ return d.name;
+ });
+ };
+
+ $scope.matMatchFields = function(matchFields) {
+ return matchFields.map(function(mf) {
+ return mf.header.name + '.' + mf.field.name;
+ });
+ };
+
+ refreshPromise = $interval(function() {
+ fetchPipeconfData();
+ }, refreshRate);
+
+ $log.log('OvPipeconfCtrl has been created');
+ }])
+ .directive('autoHeight', ['$window', 'FnService',
+ function($window, fs) {
+ return function(scope, element) {
+ var autoHeightElem = d3.select(element[0]);
+
+ scope.$watchCollection(function() {
+ return {
+ h: $window.innerHeight
+ };
+ }, function() {
+ var wsz = fs.windowSize(140, 0);
+ autoHeightElem.style('height', wsz.height + 'px');
+ });
+ };
+ }
+ ])
+ .directive('pipeconfViewDetailPanel', ['$rootScope', '$window', '$timeout', 'KeyService',
+ function($rootScope, $window, $timeout, ks) {
+ function createDetailsPanel() {
+ detailsPanel = dps.create(pName, {
+ width: wSize.width,
+ margin: 0,
+ hideMargin: 0,
+ scope: $scope,
+ keyBindings: keyBindings,
+ nameChangeRequest: null,
+ });
+ $scope.hidePanel = function () { detailsPanel.hide(); };
+ detailsPanel.hide();
+ }
+
+ function initPanel() {
+ heightCalc();
+ createDetailsPanel();
+ }
+
+ return function(scope) {
+ var unbindWatch;
+ // Safari has a bug where it renders the fixed-layout table wrong
+ // if you ask for the window's size too early
+ if (scope.onos.browser === 'safari') {
+ $timeout(initPanel);
+ } else {
+ initPanel();
+ }
+
+ // if the panelData changes
+ scope.$watch('panelData', function () {
+ if (!fs.isEmptyObject(scope.panelData)) {
+ // populateDetails(scope.panelData);
+ detailsPanel.show();
+ }
+ });
+
+ // if the window size changes
+ unbindWatch = $rootScope.$watchCollection(
+ function () {
+ return {
+ h: $window.innerHeight,
+ w: $window.innerWidth,
+ };
+ }, function () {
+ resizeDetailPanel();
+ }
+ );
+
+ scope.$on('$destroy', function () {
+ unbindWatch();
+ ks.unbindKeys();
+ ps.destroyPanel(pName);
+ });
+ };
+ }
+ ]);
+}());
diff --git a/web/gui/src/main/webapp/app/view/port/port.html b/web/gui/src/main/webapp/app/view/port/port.html
index 753ca22..f6e9b2e 100644
--- a/web/gui/src/main/webapp/app/view/port/port.html
+++ b/web/gui/src/main/webapp/app/view/port/port.html
@@ -48,6 +48,11 @@
icon icon-id="meterTable" icon-size="42"
tooltip tt-msg="meterTip"
ng-click="nav('meter')"></div>
+
+ <div class="active"
+ icon icon-id="pipeconfTable" icon-size="42"
+ tooltip tt-msg="pipeconfTip"
+ ng-click="nav('pipeconf')"></div>
</div>
<div class="search">
diff --git a/web/gui/src/main/webapp/app/view/port/port.js b/web/gui/src/main/webapp/app/view/port/port.js
index 7aad374..5bf7871 100644
--- a/web/gui/src/main/webapp/app/view/port/port.js
+++ b/web/gui/src/main/webapp/app/view/port/port.js
@@ -90,6 +90,7 @@
$scope.flowTip = 'Show flow view for this device';
$scope.groupTip = 'Show group view for this device';
$scope.meterTip = 'Show meter view for selected device';
+ $scope.pipeconfTip = 'Show pipeconf view for selected device';
$scope.toggleDeltaTip = 'Toggle port delta statistics';
$scope.toggleNZTip = 'Toggle non zero port statistics';
diff --git a/web/gui/src/main/webapp/app/view/settings/settings.css b/web/gui/src/main/webapp/app/view/settings/settings.css
index 6f05c63..d57df80 100644
--- a/web/gui/src/main/webapp/app/view/settings/settings.css
+++ b/web/gui/src/main/webapp/app/view/settings/settings.css
@@ -38,14 +38,14 @@
#settings-details-panel .close-btn {
position: absolute;
- right: 26px;
- top: 26px;
+ right: 12px;
+ top: 12px;
cursor: pointer;
}
#settings-details-panel .top .settings-title-1 {
width: 90%;
- height: 28px;
+ height: 20px;
font-size: 14pt;
font-weight: lighter;
overflow: hidden;
@@ -58,7 +58,7 @@
#settings-details-panel .top .settings-title-2 {
width: 90%;
height: 52px;
- font-size: 20pt;
+ font-size: 18pt;
font-weight: lighter;
overflow: hidden;
white-space: nowrap;
@@ -70,7 +70,7 @@
#settings-details-panel td.label {
font-weight: bold;
text-align: right;
- font-size: 12pt;
+ font-size: 10pt;
padding-right: 6px;
vertical-align: top;
width: 120px;
diff --git a/web/gui/src/main/webapp/app/view/settings/settings.html b/web/gui/src/main/webapp/app/view/settings/settings.html
index 8e23617..2346858 100644
--- a/web/gui/src/main/webapp/app/view/settings/settings.html
+++ b/web/gui/src/main/webapp/app/view/settings/settings.html
@@ -16,8 +16,8 @@
<tr>
<td colId="component" sortable col-width="260px">Component </td>
<td colId="prop" sortable col-width="260px">Property </td>
- <td colId="type" sortable col-width="100px">Type </td>
- <td colId="value" sortable col-width="100px">Value </td>
+ <td colId="type" sortable col-width="80px">Type </td>
+ <td colId="value" sortable col-width="260px">Value </td>
<td colId="desc">Description </td>
</tr>
</table>
diff --git a/web/gui/src/main/webapp/app/view/settings/settings.js b/web/gui/src/main/webapp/app/view/settings/settings.js
index 78ae829..caca00c 100644
--- a/web/gui/src/main/webapp/app/view/settings/settings.js
+++ b/web/gui/src/main/webapp/app/view/settings/settings.js
@@ -38,7 +38,7 @@
topPdg = 60,
propOrder = ['fqComponent', 'prop', 'type', 'value', 'defValue', 'desc'],
friendlyProps = [
- 'Component', 'Property', 'Type', 'Value', 'Default Value',
+ 'Component', 'Property', 'Type', 'Value', 'Default',
'Description',
];
@@ -68,7 +68,7 @@
}
function addCloseBtn(div) {
- is.loadEmbeddedIcon(div, 'close', 26);
+ is.loadEmbeddedIcon(div, 'close', 20);
div.on('click', closePanel);
}
@@ -110,7 +110,7 @@
}
function populateTop(details) {
- var propsBody = top.select('.settings-props').append('tbody');
+ var propsBody = top.select('.settings-props table').append('tbody');
top.select('.settings-title-1').text(details.component);
top.select('.settings-title-2').text(details.prop);
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index b4073a0..6f77bed 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -174,26 +174,8 @@
lu[id] = d;
updateNodes();
- function mkLinkKey(devId, devPort) {
- return id + '/0-' + devId + '/' + devPort;
- }
-
// need to handle possible multiple links (multi-homed host)
- d.links = [];
- data.allCps.forEach(function (cp) {
- var linkData = {
- key: mkLinkKey(cp.device, cp.port),
- dst: cp.device,
- dstPort: cp.port,
- };
- d.links.push(linkData);
-
- var lnk = tms.createHostLink(id, cp.device, cp.port);
- if (lnk) {
- network.links.push(lnk);
- lu[linkData.key] = lnk;
- }
- });
+ createHostLinks(data.allCps, d);
if (d.links.length) {
updateLinks();
@@ -213,15 +195,30 @@
}
}
+ function createHostLinks(cps, model) {
+ model.links = [];
+ cps.forEach(function (cp) {
+ var linkData = {
+ key: model.id + '/0-' + cp.device + '/' + cp.port,
+ dst: cp.device,
+ dstPort: cp.port,
+ };
+ model.links.push(linkData);
+
+ var lnk = tms.createHostLink(model.id, cp.device, cp.port);
+ if (lnk) {
+ network.links.push(lnk);
+ lu[linkData.key] = lnk;
+ }
+ });
+ }
+
function moveHost(data) {
var id = data.id,
- d = lu[id],
- lnk;
+ d = lu[id];
if (d) {
- // first remove the old host link
- // FIXME: what if the host has multiple links??????
- removeLinkElement(d.linkData);
+ removeAllLinkElements(d.links);
// merge new data
angular.extend(d, data);
@@ -229,19 +226,8 @@
sendUpdateMeta(d);
}
- // now create a new host link
- // TODO: verify this is the APPROPRIATE host link
- lnk = tms.createHostLink(id, data.cp.device, data.cp.port);
- if (lnk) {
- network.links.push(lnk);
- lu[lnk.key] = lnk;
-
- d.links.push({
- key: id + '/0-' + cp.device + '/' + cp.port,
- dst: data.cp.device,
- dstPort: data.cp.port,
- });
- }
+ // now create new host link(s)
+ createHostLinks(data.allCps, d);
updateNodes();
updateLinks();
@@ -391,6 +377,12 @@
}
}
+ function removeAllLinkElements(links) {
+ links.forEach(function (lnk) {
+ removeLinkElement(lnk);
+ });
+ }
+
function removeLinkElement(d) {
var idx = fs.find(d.key, network.links, 'key'),
removed;
@@ -405,12 +397,8 @@
}
function removeHostElement(d, upd) {
- // first, remove associated hostLink...
- removeLinkElement(d.linkData);
-
- // remove hostLink bindings
- delete lu[d.ingress];
- delete lu[d.egress];
+ // first, remove associated hostLink(s)...
+ removeAllLinkElements(d.links);
// remove from lookup cache
delete lu[d.id];
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css b/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
index 8ae6c2e..03138c1 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
@@ -258,7 +258,7 @@
#ov-topo2 svg .node.host text {
stroke: none;
- font: 9pt sans-serif;
+ font-size: 13px;
fill: #846;
}
@@ -266,6 +266,7 @@
stroke: #a3a596;
fill: #e0dfd6;
}
+
#ov-topo2 svg .node.host.selected .hostIcon > circle {
stroke-width: 2.0;
stroke: #009fdb;
@@ -275,6 +276,10 @@
fill: #3c3a3a;
}
+#ov-topo2 svg .node.host rect {
+ fill: #ffffff;
+}
+
/* --- Topo Links --- */
#ov-topo2 svg .link {
@@ -633,7 +638,7 @@
.dark #ov-topo2 svg .node.host text {
stroke: none;
- font: 9pt sans-serif;
+ font-size: 13px;
fill: #ad5781;
}
@@ -641,6 +646,11 @@
stroke: #a3a596;
fill: #8f8272;
}
+
+.dark #ov-topo2 svg .node.host rect {
+ fill: #525660;
+}
+
.dark #ov-topo2 svg .node.host.selected .hostIcon > circle {
stroke-width: 2.0;
stroke: #009fdb;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
index bd04c7b..4247f64 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
@@ -27,6 +27,7 @@
var hostIconDim = 15,
hostIconDimMin = 8,
hostIconDimMax = 15,
+ labelPadding = 20,
remappedDeviceTypes = {};
function createHostCollection(data, region) {
@@ -90,6 +91,20 @@
return labels[idx];
},
+ updateLabel: function () {
+ var node = this.el,
+ label = this.trimLabel(this.label()),
+ labelWidth;
+
+ node.select('text').text(label);
+ labelWidth = !_.isEmpty(this.label()) ? this.computeLabelWidth(node) + (labelPadding * 2) : 0;
+
+ node.select('rect')
+ .transition()
+ .attr({
+ width: labelWidth,
+ });
+ },
setScale: function () {
if (!this.el) return;
@@ -114,13 +129,21 @@
var node = d3.select(el),
icon = this.icon(),
textDy = 5,
- textDx = (hostIconDim * 2) + 20;
+ textDx = (hostIconDim * 2);
this.el = node;
var g = node.append('g')
.attr('class', 'svgIcon hostIcon');
+ // Add Label background to host
+
+ var rect = g.append('rect').attr({
+ width: 0,
+ height: hostIconDim * 2,
+ y: - hostIconDim,
+ });
+
g.append('circle').attr('r', hostIconDim);
var glyphSize = hostIconDim * 1.5;
@@ -138,11 +161,14 @@
.text(labelText)
.attr('dy', textDy)
.attr('dx', textDx)
- .attr('text-anchor', 'middle');
+ .attr('text-anchor', 'left');
this.setScale();
this.setUpEvents();
this.setVisibility();
+
+ var labelWidth = !_.isEmpty(this.label()) ? this.computeLabelWidth(node) + (labelPadding * 2) : 0;
+ rect.attr({ width: labelWidth });
},
});
diff --git a/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js b/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
index 73c1d6c..b600a10 100644
--- a/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
@@ -21,7 +21,7 @@
describe('factory: fw/svg/glyph.js', function() {
var $log, fs, gs, d3Elem, svg;
- var numBaseGlyphs = 105,
+ var numBaseGlyphs = 106,
vbBird = '352 224 113 112',
vbGlyph = '0 0 110 110',
vbBadge = '0 0 10 10',
diff --git a/web/gui/src/main/webapp/tests/package-lock.json b/web/gui/src/main/webapp/tests/package-lock.json
index b5f7f3c..4eb860d 100644
--- a/web/gui/src/main/webapp/tests/package-lock.json
+++ b/web/gui/src/main/webapp/tests/package-lock.json
@@ -9,6 +9,16 @@
"integrity": "sha1-Vdvr7wrZFVOC2enT5JfBNyNFtEo=",
"dev": true
},
+ "debug": {
+ "version": "2.6.8",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+ "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw="
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
"jasmine-core": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.6.4.tgz",
@@ -3514,6 +3524,16 @@
}
}
},
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "nan": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
+ "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY="
+ },
"phantomjs": {
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/phantomjs/-/phantomjs-2.1.7.tgz",
@@ -4261,6 +4281,21 @@
}
}
}
+ },
+ "typedarray-to-buffer": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz",
+ "integrity": "sha1-EBezLZhP9VbroQD1AViauhrOLgQ="
+ },
+ "websocket": {
+ "version": "1.0.24",
+ "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.24.tgz",
+ "integrity": "sha1-dJA+dfJUW2suHeFCW8HJBZF6GJA="
+ },
+ "yaeti": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
+ "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
}
}
}
diff --git a/web/gui/src/main/webapp/tests/package.json b/web/gui/src/main/webapp/tests/package.json
index 59b8102..c0ae7c3 100644
--- a/web/gui/src/main/webapp/tests/package.json
+++ b/web/gui/src/main/webapp/tests/package.json
@@ -26,6 +26,7 @@
"karma-mocha-reporter": "^1.1.3",
"karma-ng-html2js-preprocessor": "^0.2.0",
"karma-phantomjs-launcher": "^0.2.1",
- "phantomjs": "^2.1.3"
+ "phantomjs": "^2.1.3",
+ "websocket": "^1.0.24"
}
}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_10_removeHost.json b/web/gui/src/test/_karma/ev/dualHomed/ev_10_removeHost.json
new file mode 100644
index 0000000..9f7d6f5
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_10_removeHost.json
@@ -0,0 +1,35 @@
+{
+ "event": "removeHost",
+ "payload": {
+ "id": "3C:FD:FE:9E:6E:D0/None",
+ "type": "endstation",
+ "cp": {
+ "device": "of:0000000000000206",
+ "port": 54
+ },
+ "allCps": [
+ {
+ "device": "of:0000000000000206",
+ "port": 54
+ },
+ {
+ "device": "of:0000000000000205",
+ "port": 77
+ }
+ ],
+ "labels": [
+ "fe80::3efd:feff:fe9e:6ed0",
+ "fe80::3efd:feff:fe9e:6ed0",
+ "3C:FD:FE:9E:6E:D0"
+ ],
+ "props": {},
+ "metaUi": {
+ "x": 771.2614872361207,
+ "y": 870.9614439514203,
+ "equivLoc": {
+ "lng": -86.82423167773878,
+ "lat": 27.650078076056364
+ }
+ }
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_11_updateDevice.json b/web/gui/src/test/_karma/ev/dualHomed/ev_11_updateDevice.json
new file mode 100644
index 0000000..d5dd11b
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_11_updateDevice.json
@@ -0,0 +1,36 @@
+{
+ "event": "updateDevice",
+ "payload": {
+ "id": "of:0000000000000206",
+ "type": "switch",
+ "online": true,
+ "master": "10.128.0.217",
+ "labels": [
+ "",
+ "of:0000000000000206",
+ "of:0000000000000206"
+ ],
+ "props": {
+ "managementAddress": "10.128.0.206",
+ "protocol": "OF_13",
+ "latitude": "34.2403",
+ "name": "of:0000000000000206",
+ "locType": "geo",
+ "channelId": "10.128.0.206:52972",
+ "longitude": "-87.0413"
+ },
+ "location": {
+ "locType": "geo",
+ "latOrY": 34.2403,
+ "longOrX": -87.0413
+ },
+ "metaUi": {
+ "x": 764.7366062749779,
+ "y": 639.7691957045747,
+ "equivLoc": {
+ "lng": -87.0413,
+ "lat": 34.2403
+ }
+ }
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_12_addHost.json b/web/gui/src/test/_karma/ev/dualHomed/ev_12_addHost.json
new file mode 100644
index 0000000..4359989
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_12_addHost.json
@@ -0,0 +1,31 @@
+{
+ "event": "addHost",
+ "payload": {
+ "id": "3C:FD:FE:9E:6E:D0/None",
+ "type": "endstation",
+ "cp": {
+ "device": "of:0000000000000206",
+ "port": 54
+ },
+ "allCps": [
+ {
+ "device": "of:0000000000000206",
+ "port": 54
+ }
+ ],
+ "labels": [
+ "10.0.5.55",
+ "10.0.5.55",
+ "3C:FD:FE:9E:6E:D0"
+ ],
+ "props": {},
+ "metaUi": {
+ "x": 771.2614872361207,
+ "y": 870.9614439514203,
+ "equivLoc": {
+ "lng": -86.82423167773878,
+ "lat": 27.650078076056364
+ }
+ }
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_13_updateHost.json b/web/gui/src/test/_karma/ev/dualHomed/ev_13_updateHost.json
new file mode 100644
index 0000000..c6357d2
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_13_updateHost.json
@@ -0,0 +1,35 @@
+{
+ "event": "updateHost",
+ "payload": {
+ "id": "3C:FD:FE:9E:6E:D0/None",
+ "type": "endstation",
+ "cp": {
+ "device": "of:0000000000000206",
+ "port": 54
+ },
+ "prevCp": {
+ "device": "of:0000000000000206",
+ "port": 54
+ },
+ "allCps": [
+ {
+ "device": "of:0000000000000206",
+ "port": 54
+ }
+ ],
+ "labels": [
+ "fe80::3efd:feff:fe9e:6ed0",
+ "fe80::3efd:feff:fe9e:6ed0",
+ "3C:FD:FE:9E:6E:D0"
+ ],
+ "props": {},
+ "metaUi": {
+ "x": 771.2614872361207,
+ "y": 870.9614439514203,
+ "equivLoc": {
+ "lng": -86.82423167773878,
+ "lat": 27.650078076056364
+ }
+ }
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_1_addInstance.json b/web/gui/src/test/_karma/ev/dualHomed/ev_1_addInstance.json
new file mode 100644
index 0000000..daf074b
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_1_addInstance.json
@@ -0,0 +1,15 @@
+{
+ "event": "addInstance",
+ "payload": {
+ "id": "127.0.0.1",
+ "ip": "127.0.0.1",
+ "online": true,
+ "ready": true,
+ "uiAttached": true,
+ "switches": 2,
+ "labels": [
+ "127.0.0.1",
+ "127.0.0.1"
+ ]
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_2_addDevice_01.json b/web/gui/src/test/_karma/ev/dualHomed/ev_2_addDevice_01.json
new file mode 100644
index 0000000..e1aeffc
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_2_addDevice_01.json
@@ -0,0 +1,36 @@
+{
+ "event": "addDevice",
+ "payload": {
+ "id": "of:0000000000000205",
+ "type": "switch",
+ "online": true,
+ "master": "10.128.0.218",
+ "labels": [
+ "",
+ "of:0000000000000205",
+ "of:0000000000000205"
+ ],
+ "props": {
+ "managementAddress": "10.128.0.205",
+ "protocol": "OF_13",
+ "latitude": "34.1202",
+ "name": "of:0000000000000205",
+ "locType": "geo",
+ "channelId": "10.128.0.205:46174",
+ "longitude": "-90.4754"
+ },
+ "location": {
+ "locType": "geo",
+ "latOrY": 34.1202,
+ "longOrX": -90.4754
+ },
+ "metaUi": {
+ "x": 661.5106105366713,
+ "y": 644.1330453927671,
+ "equivLoc": {
+ "lng": -90.4754,
+ "lat": 34.12019999999999
+ }
+ }
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_3_addDevice_02.json b/web/gui/src/test/_karma/ev/dualHomed/ev_3_addDevice_02.json
new file mode 100644
index 0000000..3163d74
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_3_addDevice_02.json
@@ -0,0 +1,19 @@
+{
+ "event": "addDevice",
+ "payload": {
+ "id": "of:0000000000000206",
+ "type": "switch",
+ "online": true,
+ "master": "10.128.0.217",
+ "labels": [
+ "",
+ "of:0000000000000206",
+ "of:0000000000000206"
+ ],
+ "props": {
+ "managementAddress": "192.168.36.101",
+ "protocol": "OF_13",
+ "channelId": "192.168.36.101:55712"
+ }
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_4_addLink_1to2.json b/web/gui/src/test/_karma/ev/dualHomed/ev_4_addLink_1to2.json
new file mode 100644
index 0000000..e306ce1
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_4_addLink_1to2.json
@@ -0,0 +1,14 @@
+{
+ "event": "addLink",
+ "payload": {
+ "id": "of:0000000000000205/2-of:0000000000000206/2",
+ "type": "direct",
+ "expected": false,
+ "online": true,
+ "linkWidth": 1.2,
+ "src": "of:0000000000000205",
+ "srcPort": "2",
+ "dst": "of:0000000000000206",
+ "dstPort": "2"
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_5_addLink_2to1.json b/web/gui/src/test/_karma/ev/dualHomed/ev_5_addLink_2to1.json
new file mode 100644
index 0000000..69b8dc2
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_5_addLink_2to1.json
@@ -0,0 +1,14 @@
+{
+ "event": "addLink",
+ "payload": {
+ "id": "of:0000000000000206/2-of:0000000000000205/2",
+ "type": "direct",
+ "expected": false,
+ "online": true,
+ "linkWidth": 1.2,
+ "src": "of:0000000000000206",
+ "srcPort": "2",
+ "dst": "of:0000000000000205",
+ "dstPort": "2"
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_6_addHost_to1.json b/web/gui/src/test/_karma/ev/dualHomed/ev_6_addHost_to1.json
new file mode 100644
index 0000000..2e9a33d
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_6_addHost_to1.json
@@ -0,0 +1,35 @@
+{
+ "event": "addHost",
+ "payload": {
+ "id": "3C:FD:FE:9E:6E:D0/None",
+ "type": "endstation",
+ "cp": {
+ "device": "of:0000000000000206",
+ "port": 54
+ },
+ "prevCp": {
+ "device": "of:0000000000000205",
+ "port": 54
+ },
+ "allCps": [
+ {
+ "device": "of:0000000000000206",
+ "port": 54
+ }
+ ],
+ "labels": [
+ "fe80::3efd:feff:fe9e:6ed0",
+ "fe80::3efd:feff:fe9e:6ed0",
+ "3C:FD:FE:9E:6E:D0"
+ ],
+ "props": {},
+ "metaUi": {
+ "x": 771.2614872361207,
+ "y": 870.9614439514203,
+ "equivLoc": {
+ "lng": -86.82423167773878,
+ "lat": 27.650078076056364
+ }
+ }
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_7_updateDevice_01.json b/web/gui/src/test/_karma/ev/dualHomed/ev_7_updateDevice_01.json
new file mode 100644
index 0000000..6c61921
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_7_updateDevice_01.json
@@ -0,0 +1,28 @@
+{
+ "event": "updateDevice",
+ "payload": {
+ "id": "of:0000000000000205",
+ "type": "switch",
+ "online": true,
+ "master": "10.128.0.218",
+ "labels": [
+ "",
+ "SW-A",
+ "of:0000000000000001"
+ ],
+ "props": {
+ "managementAddress": "192.168.36.101",
+ "protocol": "OF_13",
+ "latitude": "43.37",
+ "name": "SW-A",
+ "locType": "geo",
+ "channelId": "192.168.36.101:55710",
+ "longitude": "-104.6572"
+ },
+ "location": {
+ "locType": "geo",
+ "latOrY": 43.37,
+ "longOrX": -104.6572
+ }
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_8_updateDevice_02.json b/web/gui/src/test/_karma/ev/dualHomed/ev_8_updateDevice_02.json
new file mode 100644
index 0000000..2801fe8
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_8_updateDevice_02.json
@@ -0,0 +1,36 @@
+{
+ "event": "updateDevice",
+ "payload": {
+ "id": "of:0000000000000206",
+ "type": "switch",
+ "online": true,
+ "master": "10.128.0.217",
+ "labels": [
+ "",
+ "SW-B",
+ "of:0000000000000206"
+ ],
+ "props": {
+ "managementAddress": "10.128.0.206",
+ "protocol": "OF_13",
+ "latitude": "34.2403",
+ "name": "of:0000000000000206",
+ "locType": "geo",
+ "channelId": "10.128.0.206:52972",
+ "longitude": "-87.0413"
+ },
+ "location": {
+ "locType": "geo",
+ "latOrY": 34.2403,
+ "longOrX": -87.0413
+ },
+ "metaUi": {
+ "x": 764.7366062749779,
+ "y": 639.7691957045747,
+ "equivLoc": {
+ "lng": -87.0413,
+ "lat": 34.2403
+ }
+ }
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_9_moveHost.json b/web/gui/src/test/_karma/ev/dualHomed/ev_9_moveHost.json
new file mode 100644
index 0000000..b70c0ff
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_9_moveHost.json
@@ -0,0 +1,39 @@
+{
+ "event": "moveHost",
+ "payload": {
+ "id": "3C:FD:FE:9E:6E:D0/None",
+ "type": "endstation",
+ "cp": {
+ "device": "of:0000000000000206",
+ "port": 54
+ },
+ "prevCp": {
+ "device": "of:0000000000000205",
+ "port": 54
+ },
+ "allCps": [
+ {
+ "device": "of:0000000000000206",
+ "port": 54
+ },
+ {
+ "device": "of:0000000000000205",
+ "port": 77
+ }
+ ],
+ "labels": [
+ "fe80::3efd:feff:fe9e:6ed0",
+ "fe80::3efd:feff:fe9e:6ed0",
+ "3C:FD:FE:9E:6E:D0"
+ ],
+ "props": {},
+ "metaUi": {
+ "x": 771.2614872361207,
+ "y": 870.9614439514203,
+ "equivLoc": {
+ "lng": -86.82423167773878,
+ "lat": 27.650078076056364
+ }
+ }
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/scenario.json b/web/gui/src/test/_karma/ev/dualHomed/scenario.json
new file mode 100644
index 0000000..4f86c5b
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/scenario.json
@@ -0,0 +1,26 @@
+{
+ "comments": [
+ "Single Host connected to two devices (Dual-Homed)."
+ ],
+ "title": "Dual-Homed Scenario",
+ "params": {
+ "lastAuto": 0
+ },
+ "description": [
+ "Simple sequence of events...",
+ "",
+ "1. add instance",
+ "2. add device [205]",
+ "3. add device [206]",
+ "4. add link [205] --> [206]",
+ "5. add link [206] --> [205]",
+ "6. add host (to [206])",
+ "7. update device [205]",
+ "8. update device [206]",
+ "9. move host (to include [205])",
+ "10. remove host",
+ "11. update device [206",
+ "12. add host (to [206])",
+ "13. update host"
+ ]
+}