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);