ONOS-1443: Group bucket statistics support and group CLI formatting
Change-Id: Iaa6d8ae1f9222eb9c29d14bf1615a7449e50c4d3
diff --git a/cli/src/main/java/org/onosproject/cli/Comparators.java b/cli/src/main/java/org/onosproject/cli/Comparators.java
index 6efb139..cbb22a5 100644
--- a/cli/src/main/java/org/onosproject/cli/Comparators.java
+++ b/cli/src/main/java/org/onosproject/cli/Comparators.java
@@ -25,6 +25,7 @@
import org.onosproject.net.ElementId;
import org.onosproject.net.Port;
import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.group.Group;
import org.onosproject.net.host.PortAddresses;
import org.onosproject.net.topology.TopologyCluster;
@@ -72,6 +73,13 @@
}
};
+ public static final Comparator<Group> GROUP_COMPARATOR = new Comparator<Group>() {
+ @Override
+ public int compare(Group g1, Group g2) {
+ return Long.valueOf(g1.id().id()).compareTo(Long.valueOf(g2.id().id()));
+ }
+ };
+
public static final Comparator<Port> PORT_COMPARATOR = new Comparator<Port>() {
@Override
public int compare(Port p1, Port p2) {
diff --git a/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java b/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java
index cb5ba42..70fa2a7 100644
--- a/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/GroupsListCommand.java
@@ -15,11 +15,23 @@
*/
package org.onosproject.cli.net;
+import static com.google.common.collect.Lists.newArrayList;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.Comparators;
+import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.group.Group;
+import org.onosproject.net.group.Group.GroupState;
+import org.onosproject.net.group.GroupBucket;
import org.onosproject.net.group.GroupService;
/**
@@ -30,23 +42,75 @@
public class GroupsListCommand extends AbstractShellCommand {
private static final String FORMAT =
- " key=%s, id=%s, state=%s, bytes=%s, packets=%s, appId=%s, buckets=%s";
+ " id=%s, state=%s, bytes=%s, packets=%s, appId=%s";
+ private static final String BUCKET_FORMAT =
+ " id=%s, bucket=%s, bytes=%s, packets=%s, actions=%s";
+
+ @Argument(index = 1, name = "uri", description = "Device ID",
+ required = false, multiValued = false)
+ String uri = null;
+
+ @Argument(index = 0, name = "state", description = "Group state",
+ required = false, multiValued = false)
+ String state;
@Override
protected void execute() {
DeviceService deviceService = get(DeviceService.class);
GroupService groupService = get(GroupService.class);
+ SortedMap<Device, List<Group>> sortedGroups =
+ getSortedGroups(deviceService, groupService);
- deviceService.getDevices().forEach(d ->
- printGroups(d.id(), groupService.getGroups(d.id()))
- );
+ sortedGroups.forEach((device, groups) -> printGroups(device.id(), groups));
}
- private void printGroups(DeviceId deviceId, Iterable<Group> groups) {
+ /**
+ * Returns the list of devices sorted using the device ID URIs.
+ *
+ * @param deviceService device service
+ * @param groupService group service
+ * @return sorted device list
+ */
+ protected SortedMap<Device, List<Group>>
+ getSortedGroups(DeviceService deviceService,
+ GroupService groupService) {
+ SortedMap<Device, List<Group>> sortedGroups =
+ new TreeMap<>(Comparators.ELEMENT_COMPARATOR);
+ List<Group> groups;
+ GroupState s = null;
+ if (state != null && !state.equals("any")) {
+ s = GroupState.valueOf(state.toUpperCase());
+ }
+ Iterable<Device> devices = (uri == null) ? deviceService.getDevices() :
+ Collections.singletonList(deviceService.getDevice(DeviceId.deviceId(uri)));
+ for (Device d : devices) {
+ if (s == null) {
+ groups = newArrayList(groupService.getGroups(d.id()));
+ } else {
+ groups = newArrayList();
+ for (Group g : groupService.getGroups(d.id())) {
+ if (g.state().equals(s)) {
+ groups.add(g);
+ }
+ }
+ }
+ groups.sort(Comparators.GROUP_COMPARATOR);
+ sortedGroups.put(d, groups);
+ }
+ return sortedGroups;
+ }
+
+ private void printGroups(DeviceId deviceId, List<Group> groups) {
print("deviceId=%s", deviceId);
for (Group group : groups) {
- print(FORMAT, group.appCookie(), group.id(), group.state(),
- group.bytes(), group.packets(), group.appId(), group.buckets());
+ print(FORMAT, group.id().id(), group.state(),
+ group.bytes(), group.packets(), group.appId().name());
+ int i = 0;
+ for (GroupBucket bucket:group.buckets().buckets()) {
+ print(BUCKET_FORMAT, group.id().id(), ++i,
+ bucket.bytes(), bucket.packets(),
+ bucket.treatment().allInstructions());
+ }
}
}
}
diff --git a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java
index e4910d5..6efd3e7 100644
--- a/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java
+++ b/core/api/src/main/java/org/onosproject/net/group/DefaultGroupBucket.java
@@ -38,12 +38,14 @@
* in the group. A failover group bucket is associated with a
* specific port or group that controls its liveness.
*/
-public final class DefaultGroupBucket implements GroupBucket {
+public final class DefaultGroupBucket implements GroupBucket, StoredGroupBucketEntry {
private final GroupDescription.Type type;
private final TrafficTreatment treatment;
private final short weight;
private final PortNumber watchPort;
private final GroupId watchGroup;
+ private long packets;
+ private long bytes;
/**
* Group bucket constructor with the parameters.
@@ -223,6 +225,28 @@
return toStringHelper(this)
.add("type", type)
.add("treatment", treatment)
+ .add("packets", packets)
+ .add("bytes", bytes)
.toString();
}
+
+ @Override
+ public long packets() {
+ return packets;
+ }
+
+ @Override
+ public long bytes() {
+ return bytes;
+ }
+
+ @Override
+ public void setPackets(long packets) {
+ this.packets = packets;
+ }
+
+ @Override
+ public void setBytes(long bytes) {
+ this.bytes = bytes;
+ }
}
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java b/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java
index 2bf8882..401c769 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java
+++ b/core/api/src/main/java/org/onosproject/net/group/GroupBucket.java
@@ -64,4 +64,17 @@
*/
public GroupId watchGroup();
+ /**
+ * Returns the number of packets processed by this group bucket.
+ *
+ * @return number of packets
+ */
+ long packets();
+
+ /**
+ * Returns the number of bytes processed by this group bucket.
+ *
+ * @return number of bytes
+ */
+ long bytes();
}
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java b/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java
index 26cd5f6..c0b5e5c 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java
+++ b/core/api/src/main/java/org/onosproject/net/group/GroupBuckets.java
@@ -70,7 +70,7 @@
@Override
public String toString() {
return toStringHelper(this)
- .add("buckets", buckets)
+ .add("buckets", buckets.toString())
.toString();
}
}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/net/group/GroupBucketEntry.java b/core/api/src/main/java/org/onosproject/net/group/StoredGroupBucketEntry.java
similarity index 72%
rename from core/api/src/main/java/org/onosproject/net/group/GroupBucketEntry.java
rename to core/api/src/main/java/org/onosproject/net/group/StoredGroupBucketEntry.java
index f7a6617..131875b 100644
--- a/core/api/src/main/java/org/onosproject/net/group/GroupBucketEntry.java
+++ b/core/api/src/main/java/org/onosproject/net/group/StoredGroupBucketEntry.java
@@ -20,18 +20,18 @@
* group object. A group bucket entry provides additional info of
* group bucket like statistics...etc
*/
-public interface GroupBucketEntry extends GroupBucket {
+public interface StoredGroupBucketEntry extends GroupBucket {
/**
- * Returns Number of packets processed by bucket.
+ * Sets number of packets processed by this group bucket entry.
*
- * @return long
+ * @param packets a long value
*/
- public long packets();
+ void setPackets(long packets);
/**
- * Returns Number of bytes processed by bucket.
+ * Sets number of bytes processed by this group bucket entry.
*
- * @return long
+ * @param bytes a long value
*/
- public long bytes();
+ void setBytes(long bytes);
}
diff --git a/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java b/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
index 1930a47..b447732 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/group/impl/DistributedGroupStore.java
@@ -15,8 +15,10 @@
*/
package org.onosproject.store.group.impl;
+import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
+
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@@ -54,6 +56,7 @@
import org.onosproject.net.group.GroupOperation;
import org.onosproject.net.group.GroupStore;
import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
import org.onosproject.net.group.StoredGroupEntry;
import org.onosproject.store.AbstractStore;
import org.onosproject.store.Timestamp;
@@ -624,6 +627,22 @@
group.id(),
group.deviceId());
synchronized (existing) {
+ for (GroupBucket bucket:group.buckets().buckets()) {
+ java.util.Optional<GroupBucket> matchingBucket =
+ existing.buckets().buckets()
+ .stream()
+ .filter((existingBucket)->(existingBucket.equals(bucket)))
+ .findFirst();
+ if (matchingBucket.isPresent()) {
+ ((StoredGroupBucketEntry) matchingBucket.
+ get()).setPackets(bucket.packets());
+ ((StoredGroupBucketEntry) matchingBucket.
+ get()).setBytes(bucket.bytes());
+ } else {
+ log.warn("addOrUpdateGroupEntry: No matching "
+ + "buckets to update stats");
+ }
+ }
existing.setLife(group.life());
existing.setPackets(group.packets());
existing.setBytes(group.bytes());
diff --git a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java
index d35a5c0..7c4f8c31 100644
--- a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java
+++ b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleGroupStore.java
@@ -46,6 +46,7 @@
import org.onosproject.net.group.GroupOperation;
import org.onosproject.net.group.GroupStore;
import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
import org.onosproject.net.group.StoredGroupEntry;
import org.onosproject.store.AbstractStore;
import org.slf4j.Logger;
@@ -416,6 +417,22 @@
if (existing != null) {
synchronized (existing) {
+ for (GroupBucket bucket:group.buckets().buckets()) {
+ java.util.Optional<GroupBucket> matchingBucket =
+ existing.buckets().buckets()
+ .stream()
+ .filter((existingBucket)->(existingBucket.equals(bucket)))
+ .findFirst();
+ if (matchingBucket.isPresent()) {
+ ((StoredGroupBucketEntry) matchingBucket.
+ get()).setPackets(bucket.packets());
+ ((StoredGroupBucketEntry) matchingBucket.
+ get()).setBytes(bucket.bytes());
+ } else {
+ log.warn("addOrUpdateGroupEntry: No matching "
+ + "buckets to update stats");
+ }
+ }
existing.setLife(group.life());
existing.setPackets(group.packets());
existing.setBytes(group.bytes());
@@ -424,7 +441,7 @@
event = new GroupEvent(Type.GROUP_ADDED, existing);
} else {
if (existing.state() == GroupState.PENDING_UPDATE) {
- existing.setState(GroupState.PENDING_UPDATE);
+ existing.setState(GroupState.ADDED);
}
event = new GroupEvent(Type.GROUP_UPDATED, existing);
}
diff --git a/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java
index 92ae5d7..61d60b1 100644
--- a/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java
+++ b/core/store/trivial/src/test/java/org/onosproject/store/trivial/impl/SimpleGroupStoreTest.java
@@ -34,6 +34,7 @@
import org.onosproject.net.PortNumber;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.DefaultGroup;
import org.onosproject.net.group.DefaultGroupBucket;
import org.onosproject.net.group.DefaultGroupDescription;
import org.onosproject.net.group.DefaultGroupKey;
@@ -46,6 +47,8 @@
import org.onosproject.net.group.GroupOperation;
import org.onosproject.net.group.GroupStore.UpdateType;
import org.onosproject.net.group.GroupStoreDelegate;
+import org.onosproject.net.group.StoredGroupBucketEntry;
+import org.onosproject.net.group.StoredGroupEntry;
import com.google.common.collect.Iterables;
@@ -100,6 +103,26 @@
createdGroupId = event.subject().id();
assertEquals(Group.GroupState.ADDED,
event.subject().state());
+ } else if (expectedEvent == GroupEvent.Type.GROUP_UPDATED) {
+ createdGroupId = event.subject().id();
+ assertEquals(true,
+ event.subject().buckets().
+ buckets().containsAll(createdBuckets.buckets()));
+ assertEquals(true,
+ createdBuckets.buckets().
+ containsAll(event.subject().buckets().buckets()));
+ for (GroupBucket bucket:event.subject().buckets().buckets()) {
+ java.util.Optional<GroupBucket> matched = createdBuckets.buckets()
+ .stream()
+ .filter((expected) -> expected.equals(bucket))
+ .findFirst();
+ assertEquals(matched.get().packets(),
+ bucket.packets());
+ assertEquals(matched.get().bytes(),
+ bucket.bytes());
+ }
+ assertEquals(Group.GroupState.ADDED,
+ event.subject().state());
} else if (expectedEvent == GroupEvent.Type.GROUP_UPDATE_REQUESTED) {
assertEquals(Group.GroupState.PENDING_UPDATE,
event.subject().state());
@@ -243,13 +266,33 @@
// Testing addOrUpdateGroupEntry operation from southbound
private void testUpdateGroupEntryFromSB(GroupKey currKey) {
Group existingGroup = simpleGroupStore.getGroup(D1, currKey);
+ int totalPkts = 0;
+ int totalBytes = 0;
+ List<GroupBucket> newBucketList = new ArrayList<GroupBucket>();
+ for (GroupBucket bucket:existingGroup.buckets().buckets()) {
+ StoredGroupBucketEntry newBucket =
+ (StoredGroupBucketEntry)
+ DefaultGroupBucket.createSelectGroupBucket(bucket.treatment());
+ newBucket.setPackets(10);
+ newBucket.setBytes(10 * 256 * 8);
+ totalPkts += 10;
+ totalBytes += 10 * 256 * 8;
+ newBucketList.add(newBucket);
+ }
+ GroupBuckets updatedBuckets = new GroupBuckets(newBucketList);
+ Group updatedGroup = new DefaultGroup(existingGroup.id(),
+ existingGroup.deviceId(),
+ existingGroup.type(),
+ updatedBuckets);
+ ((StoredGroupEntry) updatedGroup).setPackets(totalPkts);
+ ((StoredGroupEntry) updatedGroup).setBytes(totalBytes);
InternalGroupStoreDelegate updateGroupEntryDelegate =
new InternalGroupStoreDelegate(currKey,
- existingGroup.buckets(),
+ updatedBuckets,
GroupEvent.Type.GROUP_UPDATED);
simpleGroupStore.setDelegate(updateGroupEntryDelegate);
- simpleGroupStore.addOrUpdateGroupEntry(existingGroup);
+ simpleGroupStore.addOrUpdateGroupEntry(updatedGroup);
simpleGroupStore.unsetDelegate(updateGroupEntryDelegate);
}
diff --git a/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java b/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java
index cae79eb..78650fe 100644
--- a/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java
+++ b/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java
@@ -17,6 +17,7 @@
package org.onosproject.provider.of.group.impl;
import com.google.common.collect.Maps;
+
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@@ -34,6 +35,7 @@
import org.onosproject.net.group.GroupProvider;
import org.onosproject.net.group.GroupProviderRegistry;
import org.onosproject.net.group.GroupProviderService;
+import org.onosproject.net.group.StoredGroupBucketEntry;
import org.onosproject.net.provider.AbstractProvider;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.openflow.controller.Dpid;
@@ -42,6 +44,7 @@
import org.onosproject.openflow.controller.OpenFlowSwitch;
import org.onosproject.openflow.controller.OpenFlowSwitchListener;
import org.onosproject.openflow.controller.RoleState;
+import org.projectfloodlight.openflow.protocol.OFBucketCounter;
import org.projectfloodlight.openflow.protocol.OFErrorMsg;
import org.projectfloodlight.openflow.protocol.OFErrorType;
import org.projectfloodlight.openflow.protocol.OFGroupDescStatsEntry;
@@ -208,6 +211,7 @@
Map<Integer, Group> groups = Maps.newHashMap();
+
for (OFGroupDescStatsEntry entry: groupDescStatsReply.getEntries()) {
int id = entry.getGroup().getGroupNumber();
GroupId groupId = new DefaultGroupId(id);
@@ -226,6 +230,19 @@
group.setLife(entry.getDurationSec());
group.setPackets(entry.getPacketCount().getValue());
group.setReferenceCount(entry.getRefCount());
+ int bucketIndex = 0;
+ for (OFBucketCounter bucketStats:entry.getBucketStats()) {
+ ((StoredGroupBucketEntry) group.buckets().buckets()
+ .get(bucketIndex))
+ .setPackets(bucketStats
+ .getPacketCount().getValue());
+ ((StoredGroupBucketEntry) group.buckets().buckets()
+ .get(bucketIndex))
+ .setBytes(entry.getBucketStats()
+ .get(bucketIndex)
+ .getByteCount().getValue());
+ bucketIndex++;
+ }
}
}