ONOS-5457 OFAgent - handle GROUP_MOD and METER_MOD controller to switch command message
Change-Id: Ib34f22581d8f4a89fcf2fbfc8ab66306d87137a5
diff --git a/apps/ofagent/BUCK b/apps/ofagent/BUCK
index 0d28fca..23e7ca2 100644
--- a/apps/ofagent/BUCK
+++ b/apps/ofagent/BUCK
@@ -16,11 +16,13 @@
'//lib:javax.ws.rs-api',
'//utils/rest:onlab-rest',
'//providers/openflow/flow:onos-providers-openflow-flow',
+ '//protocols/openflow/api:onos-protocols-openflow-api',
]
BUNDLES = [
'//apps/ofagent:onos-apps-ofagent',
'//providers/openflow/flow:onos-providers-openflow-flow',
+ '//protocols/openflow/api:onos-protocols-openflow-api',
]
TEST_DEPS = [
diff --git a/apps/ofagent/pom.xml b/apps/ofagent/pom.xml
index 76fb852..1b0cc01 100644
--- a/apps/ofagent/pom.xml
+++ b/apps/ofagent/pom.xml
@@ -179,6 +179,13 @@
<version>${project.version}</version>
</dependency>
+ <!-- GroupBucket building dependencies -->
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-protocols-openflow-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
</dependencies>
<build>
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchService.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchService.java
index cbed3d9..9a172a2 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchService.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/api/OFSwitchService.java
@@ -15,6 +15,7 @@
*/
package org.onosproject.ofagent.api;
+import org.onosproject.core.ApplicationId;
import org.onosproject.incubator.net.virtual.NetworkId;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
@@ -111,4 +112,11 @@
* @return connect point; null if none exists
*/
ConnectPoint neighbour(NetworkId networkId, DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Returns application id.
+ *
+ * @return application id
+ */
+ ApplicationId appId();
}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java
index f06446a..95c5908 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/DefaultOFSwitch.java
@@ -19,6 +19,7 @@
import com.google.common.collect.Lists;
import io.netty.channel.Channel;
import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.core.ApplicationId;
import org.onosproject.incubator.net.virtual.NetworkId;
import org.onosproject.incubator.net.virtual.VirtualNetworkService;
import org.onosproject.net.ConnectPoint;
@@ -31,11 +32,25 @@
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TableStatisticsEntry;
+import org.onosproject.net.group.DefaultGroupDescription;
+import org.onosproject.net.group.DefaultGroupKey;
import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.group.GroupService;
+import org.onosproject.net.meter.Band;
+import org.onosproject.net.meter.DefaultBand;
+import org.onosproject.net.meter.DefaultMeterRequest;
+import org.onosproject.net.meter.Meter;
+import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.meter.MeterRequest;
+import org.onosproject.net.meter.MeterService;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.ofagent.api.OFSwitch;
import org.onosproject.ofagent.api.OFSwitchCapabilities;
import org.onosproject.ofagent.api.OFSwitchService;
+import org.onosproject.openflow.controller.Dpid;
import org.projectfloodlight.openflow.protocol.OFActionType;
import org.projectfloodlight.openflow.protocol.OFBadRequestCode;
import org.projectfloodlight.openflow.protocol.OFBarrierReply;
@@ -50,12 +65,17 @@
import org.projectfloodlight.openflow.protocol.OFFlowMod;
import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry;
import org.projectfloodlight.openflow.protocol.OFGetConfigReply;
+import org.projectfloodlight.openflow.protocol.OFGroupAdd;
import org.projectfloodlight.openflow.protocol.OFGroupDescStatsEntry;
+import org.projectfloodlight.openflow.protocol.OFGroupMod;
+import org.projectfloodlight.openflow.protocol.OFGroupModify;
import org.projectfloodlight.openflow.protocol.OFGroupStatsEntry;
import org.projectfloodlight.openflow.protocol.OFGroupType;
import org.projectfloodlight.openflow.protocol.OFHello;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFMeterFeatures;
+import org.projectfloodlight.openflow.protocol.OFMeterMod;
+import org.projectfloodlight.openflow.protocol.OFMeterModFailedCode;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPacketInReason;
import org.projectfloodlight.openflow.protocol.OFPacketOut;
@@ -76,9 +96,14 @@
import org.projectfloodlight.openflow.protocol.action.OFAction;
import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
+import org.projectfloodlight.openflow.protocol.errormsg.OFMeterModFailedErrorMsg;
import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
import org.projectfloodlight.openflow.protocol.match.Match;
import org.projectfloodlight.openflow.protocol.match.MatchField;
+import org.projectfloodlight.openflow.protocol.meterband.OFMeterBand;
+import org.projectfloodlight.openflow.protocol.meterband.OFMeterBandDrop;
+import org.projectfloodlight.openflow.protocol.meterband.OFMeterBandDscpRemark;
+import org.projectfloodlight.openflow.protocol.meterband.OFMeterBandExperimenter;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.OFGroup;
import org.projectfloodlight.openflow.types.OFPort;
@@ -88,10 +113,12 @@
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -112,6 +139,8 @@
private final OFSwitchService ofSwitchService;
private final FlowRuleService flowRuleService;
private final DriverService driverService;
+ private final GroupService groupService;
+ private final MeterService meterService;
private final DatapathId dpId;
private final OFSwitchCapabilities capabilities;
@@ -139,6 +168,8 @@
this.driverService = serviceDirectory.get(DriverService.class);
VirtualNetworkService virtualNetworkService = serviceDirectory.get(VirtualNetworkService.class);
this.flowRuleService = virtualNetworkService.get(networkId, FlowRuleService.class);
+ this.groupService = virtualNetworkService.get(networkId, GroupService.class);
+ this.meterService = virtualNetworkService.get(networkId, MeterService.class);
log = LoggerFactory.getLogger(getClass().getName() + " : " + dpid);
}
@@ -249,6 +280,140 @@
flowRuleService.applyFlowRules(flowEntry);
}
+ // Methods that support GROUP_MOD
+
+ private GroupDescription.Type getGroupType(OFGroupType type) {
+ switch (type) {
+ case ALL:
+ return GroupDescription.Type.ALL;
+ case INDIRECT:
+ return GroupDescription.Type.INDIRECT;
+ case SELECT:
+ return GroupDescription.Type.SELECT;
+ case FF:
+ return GroupDescription.Type.FAILOVER;
+ default:
+ log.error("Unsupported OF group type : {}", type);
+ break;
+ }
+ return null;
+ }
+
+ private void processGroupMod(OFGroupMod groupMod) {
+ log.debug("processing GROUP_MOD {} message", groupMod.getCommand());
+
+ ApplicationId appId = ofSwitchService.appId();
+ GroupKey appCookie = new DefaultGroupKey(networkId.toString().getBytes());
+ switch (groupMod.getCommand()) {
+ case ADD:
+ // TODO return OFGroupModFailedCode.GROUP_EXISTS if group already exists
+ int groupId = groupMod.getGroup().getGroupNumber();
+ OFGroupAdd groupAdd = (OFGroupAdd) groupMod;
+ GroupBuckets groupAddBuckets = new OFAgentVirtualGroupBucketEntryBuilder(
+ Dpid.dpid(Dpid.uri(dpid().getLong())),
+ groupAdd.getBuckets(), groupAdd.getGroupType(), driverService)
+ .build();
+ GroupDescription groupDescription = new DefaultGroupDescription(
+ deviceId, getGroupType(groupAdd.getGroupType()), groupAddBuckets,
+ appCookie, groupId, appId);
+ groupService.addGroup(groupDescription);
+ break;
+ case MODIFY:
+ // TODO return OFGroupModFailedCode.INVALID_GROUP if group does not exist
+ OFGroupModify groupModify = (OFGroupModify) groupMod;
+ GroupBuckets groupModifyBuckets = new OFAgentVirtualGroupBucketEntryBuilder(
+ Dpid.dpid(Dpid.uri(dpid().getLong())),
+ groupModify.getBuckets(), groupModify.getGroupType(), driverService)
+ .build();
+ groupService.setBucketsForGroup(deviceId, appCookie, groupModifyBuckets,
+ appCookie, appId);
+ break;
+ case DELETE:
+ groupService.removeGroup(deviceId, appCookie, appId);
+ break;
+ default:
+ // INSERT_BUCKET, REMOVE_BUCKET are effective OF 1.5. OFAgent supports 1.3.
+ log.warn("Unsupported GROUP_MOD {} message received for switch {}",
+ groupMod.getCommand(), this);
+ }
+ }
+
+ // Methods that spport METER_MOD.
+
+ private Band band(OFMeterBand ofMeterBand) {
+ DefaultBand.Builder builder = DefaultBand.builder();
+ if (ofMeterBand instanceof OFMeterBandDrop) {
+ OFMeterBandDrop ofMeterBandDrop = (OFMeterBandDrop) ofMeterBand;
+ builder.ofType(Band.Type.DROP)
+ .burstSize(ofMeterBandDrop.getBurstSize())
+ .withRate(ofMeterBandDrop.getRate());
+ } else if (ofMeterBand instanceof OFMeterBandDscpRemark) {
+ OFMeterBandDscpRemark ofMeterBandDscpRemark = (OFMeterBandDscpRemark) ofMeterBand;
+ builder.ofType(Band.Type.REMARK)
+ .burstSize(ofMeterBandDscpRemark.getBurstSize())
+ .withRate(ofMeterBandDscpRemark.getRate())
+ .dropPrecedence(ofMeterBandDscpRemark.getPrecLevel());
+ } else if (ofMeterBand instanceof OFMeterBandExperimenter) {
+ OFMeterBandExperimenter ofMeterBandExperimenter = (OFMeterBandExperimenter) ofMeterBand;
+ builder.ofType(Band.Type.EXPERIMENTAL)
+ .burstSize(ofMeterBandExperimenter.getBurstSize())
+ .withRate(ofMeterBandExperimenter.getRate());
+ }
+ return builder.build();
+ }
+
+ private MeterRequest.Builder meterRequestBuilder(OFMeterMod meterMod) {
+ Collection<Band> bands = meterMod.getBands().stream()
+ .map(ofMeterBand -> band(ofMeterBand)).collect(Collectors.toList());
+ return DefaultMeterRequest.builder().forDevice(deviceId)
+ .withBands(bands).fromApp(ofSwitchService.appId());
+ }
+
+ private void meterModError(OFMeterMod meterMod, OFMeterModFailedCode code,
+ Channel channel) {
+ OFMeterModFailedErrorMsg errorMsg = FACTORY.errorMsgs()
+ .buildMeterModFailedErrorMsg()
+ .setXid(meterMod.getXid())
+ .setCode(code)
+ .build();
+ channel.writeAndFlush(Collections.singletonList(errorMsg));
+ log.debug("Sent meterMod error {}", code);
+ }
+
+ private void processMeterMod(OFMeterMod meterMod, Channel channel) {
+ log.debug("processing METER_MOD {} message", meterMod.getCommand());
+
+ long meterModId = meterMod.getMeterId();
+ Meter existingMeter = meterService.getMeter(deviceId, MeterId.meterId(meterModId));
+ MeterRequest meterRequest = null;
+ switch (meterMod.getCommand()) {
+ case ADD:
+ if (existingMeter != null) {
+ meterModError(meterMod, OFMeterModFailedCode.METER_EXISTS, channel);
+ return;
+ }
+ meterRequest = meterRequestBuilder(meterMod).add();
+ break;
+ case MODIFY:
+ if (existingMeter == null) {
+ meterModError(meterMod, OFMeterModFailedCode.UNKNOWN_METER, channel);
+ return;
+ }
+ meterRequest = meterRequestBuilder(meterMod).add();
+ break;
+ case DELETE:
+ // non-existing meter id will not result in OFMeterModFailedErrorMsg
+ // being sent to the controller
+ meterRequest = meterRequestBuilder(meterMod).remove();
+ break;
+ default:
+ log.warn("Unexpected message {} received for switch {}",
+ meterMod.getCommand(), this);
+ return;
+ }
+ meterService.submit(meterRequest);
+ }
+
@Override
public void processControllerCommand(Channel channel, OFMessage msg) {
@@ -273,7 +438,13 @@
processFlowMod(flowMod);
break;
case GROUP_MOD:
+ OFGroupMod groupMod = (OFGroupMod) msg;
+ processGroupMod(groupMod);
+ break;
case METER_MOD:
+ OFMeterMod meterMod = (OFMeterMod) msg;
+ processMeterMod(meterMod, channel);
+ break;
case TABLE_MOD:
log.debug("processControllerCommand: {} not yet supported for {}",
msg.getType(), msg);
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFAgentVirtualGroupBucketEntryBuilder.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFAgentVirtualGroupBucketEntryBuilder.java
new file mode 100644
index 0000000..2ec7aca
--- /dev/null
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFAgentVirtualGroupBucketEntryBuilder.java
@@ -0,0 +1,150 @@
+/*
+ * 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.ofagent.impl;
+
+import com.google.common.collect.Lists;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.driver.DefaultDriverData;
+import org.onosproject.net.driver.DefaultDriverHandler;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.openflow.controller.Dpid;
+import org.onosproject.provider.of.flow.util.FlowEntryBuilder;
+import org.projectfloodlight.openflow.protocol.OFBucket;
+import org.projectfloodlight.openflow.protocol.OFGroupType;
+import org.projectfloodlight.openflow.protocol.action.OFAction;
+import org.slf4j.Logger;
+
+import java.util.List;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Group Bucket builder customized for OFAgent.
+ * <p>
+ * This builder is used to build GroupBucketEntry objects for virtual devices encountered by
+ * OFAgent. Its code is based on org.onosproject.provider.of.group.impl.GroupBucketEntryBuilder,
+ * except that the driver has been hardcoded to "ovs".
+ */
+public class OFAgentVirtualGroupBucketEntryBuilder {
+
+ private static final String DRIVER_NAME = "ovs";
+
+ private Dpid dpid;
+ private List<OFBucket> ofBuckets;
+ private OFGroupType type;
+ private DriverService driverService;
+
+ private final Logger log = getLogger(getClass());
+
+
+ /**
+ * Creates a builder.
+ *
+ * @param dpid dpid
+ * @param ofBuckets list of OFBucket
+ * @param type Group type
+ * @param driverService driver service
+ */
+ public OFAgentVirtualGroupBucketEntryBuilder(Dpid dpid, List<OFBucket> ofBuckets, OFGroupType type,
+ DriverService driverService) {
+ this.dpid = dpid;
+ this.ofBuckets = ofBuckets;
+ this.type = type;
+ this.driverService = driverService;
+ }
+
+ /**
+ * Builds a GroupBuckets.
+ *
+ * @return GroupBuckets object, a list of GroupBuckets
+ */
+ public GroupBuckets build() {
+ List<GroupBucket> bucketList = Lists.newArrayList();
+
+ for (OFBucket bucket: ofBuckets) {
+ TrafficTreatment treatment = buildTreatment(bucket.getActions());
+ // TODO: Use GroupBucketEntry
+ GroupBucket groupBucket = null;
+ switch (type) {
+ case INDIRECT:
+ groupBucket =
+ DefaultGroupBucket.createIndirectGroupBucket(treatment);
+ break;
+ case SELECT:
+ groupBucket =
+ DefaultGroupBucket.createSelectGroupBucket(treatment, (short) bucket.getWeight());
+ break;
+ case FF:
+ PortNumber port =
+ PortNumber.portNumber(bucket.getWatchPort().getPortNumber());
+ GroupId groupId =
+ new GroupId(bucket.getWatchGroup().getGroupNumber());
+ groupBucket =
+ DefaultGroupBucket.createFailoverGroupBucket(treatment,
+ port, groupId);
+ break;
+ case ALL:
+ groupBucket =
+ DefaultGroupBucket.createAllGroupBucket(treatment);
+ break;
+ default:
+ log.error("Unsupported Group type : {}", type);
+ }
+ if (groupBucket != null) {
+ bucketList.add(groupBucket);
+ }
+ }
+ return new GroupBuckets(bucketList);
+ }
+
+ private TrafficTreatment buildTreatment(List<OFAction> actions) {
+ DriverHandler driverHandler = getDriver(dpid);
+ TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
+
+ // If this is a drop rule
+ if (actions.isEmpty()) {
+ builder.drop();
+ return builder.build();
+ }
+
+ return FlowEntryBuilder.configureTreatmentBuilder(actions, builder,
+ driverHandler, DeviceId.deviceId(Dpid.uri(dpid))).build();
+ }
+
+ /**
+ * Retrieves the driver handler for the specified device.
+ *
+ * @param dpid datapath identifier
+ * @return driver handler
+ */
+ protected DriverHandler getDriver(Dpid dpid) {
+ DeviceId devId = DeviceId.deviceId(Dpid.uri(dpid));
+ log.debug("running getDriver for {}", devId);
+ Driver driver = driverService.getDriver(DRIVER_NAME);
+ DriverHandler handler = new DefaultDriverHandler(new DefaultDriverData(driver, devId));
+ return handler;
+ }
+
+}
diff --git a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java
index 69ea60a..e98c920 100644
--- a/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java
+++ b/apps/ofagent/src/main/java/org/onosproject/ofagent/impl/OFSwitchManager.java
@@ -205,6 +205,11 @@
}
@Override
+ public ApplicationId appId() {
+ return appId;
+ }
+
+ @Override
public List<FlowEntry> getFlowEntries(NetworkId networkId, DeviceId deviceId) {
FlowRuleService flowRuleService = virtualNetService.get(networkId, FlowRuleService.class);
Iterable<FlowEntry> entries = flowRuleService.getFlowEntries(deviceId);