ONOS-7251 - Initial implementation of fabric.p4 L2 broadcast feature.

Thrift client cherry-picked from the commit dd5792ac9ee38a702c3128a34224852b5c284687

Change-Id: I989f2b2074485a892195889a7c976b518510da88
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2GroupProgrammable.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2GroupProgrammable.java
new file mode 100644
index 0000000..1ab518f
--- /dev/null
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2GroupProgrammable.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2018-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.drivers.bmv2;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableList;
+import org.onosproject.core.GroupId;
+import org.onosproject.drivers.bmv2.api.Bmv2DeviceAgent;
+import org.onosproject.drivers.bmv2.api.Bmv2PreController;
+import org.onosproject.drivers.bmv2.api.runtime.Bmv2PreGroup;
+import org.onosproject.drivers.bmv2.api.runtime.Bmv2PreGroupHandle;
+import org.onosproject.drivers.bmv2.api.runtime.Bmv2RuntimeException;
+import org.onosproject.drivers.bmv2.impl.Bmv2PreGroupTranslatorImpl;
+import org.onosproject.drivers.bmv2.mirror.Bmv2PreGroupMirror;
+import org.onosproject.drivers.p4runtime.P4RuntimeGroupProgrammable;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.group.GroupOperation;
+import org.onosproject.net.group.GroupOperations;
+import org.slf4j.Logger;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the group programmable behaviour for BMv2.
+ */
+public class Bmv2GroupProgrammable extends P4RuntimeGroupProgrammable {
+
+    private static final Logger log = getLogger(Bmv2GroupProgrammable.class);
+
+    private static final int PRE_GROUP_LOCK_EXPIRE_TIME_IN_MIN = 10;
+
+    // Needed to synchronize operations over the same group.
+    private static final LoadingCache<Bmv2PreGroupHandle, Lock> PRE_GROUP_LOCKS = CacheBuilder.newBuilder()
+            .expireAfterAccess(PRE_GROUP_LOCK_EXPIRE_TIME_IN_MIN, TimeUnit.MINUTES)
+            .build(new CacheLoader<Bmv2PreGroupHandle, Lock>() {
+                @Override
+                public Lock load(Bmv2PreGroupHandle bmv2PreGroupHandle) {
+                    return new ReentrantLock();
+                }
+            });
+
+    private Bmv2PreGroupMirror preGroupMirror;
+    private Bmv2PreController bmv2PreController;
+
+    @Override
+    protected boolean setupBehaviour() {
+        if (!super.setupBehaviour()) {
+            return false;
+        }
+
+        preGroupMirror = handler().get(Bmv2PreGroupMirror.class);
+        bmv2PreController = handler().get(Bmv2PreController.class);
+
+        return getBmv2DeviceAgent() != null
+                && preGroupMirror != null
+                && bmv2PreController != null;
+    }
+
+    @Override
+    public void performGroupOperation(DeviceId deviceId,
+                                      GroupOperations groupOps) {
+        if (!setupBehaviour()) {
+            return;
+        }
+        groupOps.operations().forEach(op -> processGroupOp(deviceId, op));
+    }
+
+
+    /**
+     * Fetches all groups of P4Runtime and BMv2 PRE in a device. Combines and returns them respectively.
+     *
+     * @return all the groups which are managed via both P4Runtime and BMv2 PRE
+     */
+    @Override
+    public Collection<Group> getGroups() {
+        //get groups managed via P4Runtime
+        Collection<Group> groups = getP4Groups();
+        //get groups managed via BMv2 Thrift
+        groups.addAll(getPreGroups());
+        return ImmutableList.copyOf(groups);
+    }
+
+    private Collection<Group> getP4Groups() {
+        return super.getGroups();
+    }
+
+    /**
+     * Returns BMv2 agent associated with a BMv2 device.
+     *
+     * @return BMv2 agent
+     */
+    private Bmv2DeviceAgent getBmv2DeviceAgent() {
+        return bmv2PreController.getPreClient(deviceId);
+    }
+
+    /**
+     * Retrieves groups of BMv2 PRE.
+     *
+     * @return collection of PRE groups
+     */
+    private Collection<Group> getPreGroups() {
+        if (!setupBehaviour()) {
+            return Collections.emptyList();
+        }
+        Bmv2DeviceAgent bmv2DeviceAgent = getBmv2DeviceAgent();
+
+        try {
+            return bmv2DeviceAgent.getPreGroups().stream()
+                    .map(preGroup -> groupStore.getGroup(deviceId, GroupId.valueOf(preGroup.groupId())))
+                    .collect(Collectors.toList());
+        } catch (Bmv2RuntimeException e) {
+            log.error("Exception while getting Bmv2 PRE groups of {}", deviceId, e);
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Checks whether specified group is a PRE group or not.
+     *
+     * @param group group
+     * @return Returns true iff this group is a PRE group; false otherwise.
+     */
+    private boolean isPreGroup(Group group) {
+        return group.type().equals(GroupDescription.Type.ALL);
+    }
+
+    /**
+     * Makes a decision between two methodologies over group type.
+     * A group of ALL type is evaluated by GroupProgrammable of BMv2;
+     * it is passed on to GroupProgrammable of P4Runtime otherwise.
+     *
+     * @param deviceId ID of the device on which the group is being accommodated.
+     * @param groupOp  group operation
+     */
+    private void processGroupOp(DeviceId deviceId, GroupOperation groupOp) {
+        final Group group = groupStore.getGroup(deviceId, groupOp.groupId());
+
+        if (isPreGroup(group)) {
+            processPreGroupOp(deviceId, groupOp);
+        } else {
+            //means the group is managed via P4Runtime.
+            super.performGroupOperation(deviceId,
+                                        new GroupOperations(Arrays.asList(new GroupOperation[]{groupOp})));
+        }
+    }
+
+    private void processPreGroupOp(DeviceId deviceId, GroupOperation groupOp) {
+        if (!setupBehaviour()) {
+            return;
+        }
+
+        final Group group = groupStore.getGroup(deviceId, groupOp.groupId());
+
+        Bmv2PreGroup preGroup = Bmv2PreGroupTranslatorImpl.translate(group);
+
+        final Bmv2PreGroupHandle handle = Bmv2PreGroupHandle.of(deviceId, preGroup);
+
+        final Bmv2PreGroup groupOnDevice = preGroupMirror.get(handle);
+
+        PRE_GROUP_LOCKS.getUnchecked(handle).lock();
+        try {
+            switch (groupOp.opType()) {
+                case ADD:
+                    onAdd(preGroup, handle);
+                    break;
+                case MODIFY:
+                    onModify(preGroup, groupOnDevice, handle);
+                    break;
+                case DELETE:
+                    onDelete(groupOnDevice, handle);
+                    break;
+                default:
+                    log.warn("PRE Group operation {} not supported", groupOp.opType());
+            }
+        } finally {
+            PRE_GROUP_LOCKS.getUnchecked(handle).unlock();
+        }
+    }
+
+    private void onAdd(Bmv2PreGroup preGroup, Bmv2PreGroupHandle handle) {
+        try {
+            writeGroup(preGroup, handle);
+        } catch (Bmv2RuntimeException e) {
+            log.error("Unable to create the PRE group with groupId={}. deviceId={}", preGroup.groupId(), deviceId, e);
+        }
+    }
+
+    private void onDelete(Bmv2PreGroup preGroupOnDevice, Bmv2PreGroupHandle handle) {
+        if (preGroupOnDevice == null) {
+            log.warn("Unable to delete the group. Nonexistent in the group mirror! deviceId={}", deviceId);
+            return;
+        }
+        try {
+            deleteGroup(preGroupOnDevice, handle);
+        } catch (Bmv2RuntimeException e) {
+            log.error("Unable to delete the group. deviceId={}", deviceId, e);
+        }
+    }
+
+    private void onModify(Bmv2PreGroup preGroup, Bmv2PreGroup preGroupOnDevice, Bmv2PreGroupHandle handle) {
+        if (preGroupOnDevice == null) {
+            log.warn("Unable to modify the group. Nonexistent in the group mirror! deviceId={}", deviceId);
+            return;
+        }
+        if (preGroup.equals(preGroupOnDevice)) {
+            return;
+        }
+        try {
+            deleteGroup(preGroupOnDevice, handle);
+            writeGroup(preGroup, handle);
+        } catch (Bmv2RuntimeException e) {
+            log.error("Unable to modify the group. deviceId={}, groupId={}", deviceId, preGroup.groupId(), e);
+        }
+    }
+
+    private void writeGroup(Bmv2PreGroup preGroup, Bmv2PreGroupHandle handle) throws Bmv2RuntimeException {
+        Bmv2DeviceAgent bmv2DeviceAgent = getBmv2DeviceAgent();
+        Bmv2PreGroup bmv2PreGroupCreated = bmv2DeviceAgent.writePreGroup(preGroup);
+        //put the created group into the mirror store
+        preGroupMirror.put(handle, bmv2PreGroupCreated);
+    }
+
+    private void deleteGroup(Bmv2PreGroup preGroupOnDevice, Bmv2PreGroupHandle handle) throws Bmv2RuntimeException {
+        Bmv2DeviceAgent bmv2DeviceAgent = getBmv2DeviceAgent();
+        bmv2DeviceAgent.deletePreGroup(preGroupOnDevice);
+        //remove the group from the mirror
+        preGroupMirror.remove(handle);
+    }
+
+}
\ No newline at end of file