[ONOS-5070] Adds mirroring functionality.

Changes:
- Adds mirroring behaviour;
- Adds mirroring description;
- Adds mirroring name;
- Implements for Ovsdb the mirroring;
- Adds OvsdbMirror entity;
- Adds constants related to Mirror table;
- Fix one issue related to Mirror table
- Extends OvsdbClientService introducing mirroring;
- Implements mirroring functionality in DefaulOvsdbClient;
- Support for different types of device id

Change-Id: Ie291f49b3c61b7998010f555ae11deb8c021063d
diff --git a/protocols/ovsdb/api/src/main/java/org/onosproject/ovsdb/controller/driver/DefaultOvsdbClient.java b/protocols/ovsdb/api/src/main/java/org/onosproject/ovsdb/controller/driver/DefaultOvsdbClient.java
index 26904d5..635f1a1 100644
--- a/protocols/ovsdb/api/src/main/java/org/onosproject/ovsdb/controller/driver/DefaultOvsdbClient.java
+++ b/protocols/ovsdb/api/src/main/java/org/onosproject/ovsdb/controller/driver/DefaultOvsdbClient.java
@@ -17,6 +17,8 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -30,10 +32,13 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.behaviour.BridgeDescription;
 import org.onosproject.net.behaviour.ControllerInfo;
+import org.onosproject.net.behaviour.MirroringStatistics;
+import org.onosproject.net.behaviour.MirroringName;
 import org.onosproject.ovsdb.controller.OvsdbBridge;
 import org.onosproject.ovsdb.controller.OvsdbClientService;
 import org.onosproject.ovsdb.controller.OvsdbInterface;
 import org.onosproject.ovsdb.controller.OvsdbInterface.Type;
+import org.onosproject.ovsdb.controller.OvsdbMirror;
 import org.onosproject.ovsdb.controller.OvsdbNodeId;
 import org.onosproject.ovsdb.controller.OvsdbPort;
 import org.onosproject.ovsdb.controller.OvsdbPortName;
@@ -41,6 +46,7 @@
 import org.onosproject.ovsdb.controller.OvsdbRowStore;
 import org.onosproject.ovsdb.controller.OvsdbStore;
 import org.onosproject.ovsdb.controller.OvsdbTableStore;
+
 import org.onosproject.ovsdb.rfc.jsonrpc.Callback;
 import org.onosproject.ovsdb.rfc.message.OperationResult;
 import org.onosproject.ovsdb.rfc.message.TableUpdates;
@@ -61,6 +67,7 @@
 import org.onosproject.ovsdb.rfc.table.Bridge;
 import org.onosproject.ovsdb.rfc.table.Controller;
 import org.onosproject.ovsdb.rfc.table.Interface;
+import org.onosproject.ovsdb.rfc.table.Mirror;
 import org.onosproject.ovsdb.rfc.table.OvsdbTable;
 import org.onosproject.ovsdb.rfc.table.Port;
 import org.onosproject.ovsdb.rfc.table.TableGenerator;
@@ -73,10 +80,12 @@
 
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
+
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentMap;
@@ -104,6 +113,7 @@
     private final Map<String, SettableFuture<? extends Object>> requestResult = Maps.newHashMap();
     private final Map<String, DatabaseSchema> schema = Maps.newHashMap();
 
+
     /**
      * Creates an OvsdbClient.
      *
@@ -234,6 +244,107 @@
         ovsdbStore.createOrUpdateOvsdbStore(dbName, tableStore);
     }
 
+    /**
+     * Gets the Mirror uuid.
+     *
+     * @param mirrorName mirror name
+     * @return mirror uuid, empty if no uuid is found
+     */
+    @Override
+    public String getMirrorUuid(String mirrorName) {
+        DatabaseSchema dbSchema = schema.get(DATABASENAME);
+        OvsdbRowStore rowStore = getRowStore(DATABASENAME, MIRROR);
+        if (rowStore == null) {
+            log.warn("The mirror uuid is null");
+            return null;
+        }
+
+        ConcurrentMap<String, Row> mirrorTableRows = rowStore.getRowStore();
+        if (mirrorTableRows == null) {
+            log.warn("The mirror uuid is null");
+            return null;
+        }
+
+        for (String uuid : mirrorTableRows.keySet()) {
+            Mirror mirror = (Mirror) TableGenerator
+                    .getTable(dbSchema, mirrorTableRows.get(uuid), OvsdbTable.MIRROR);
+            String name = mirror.getName();
+            if (name.contains(mirrorName)) {
+                return uuid;
+            }
+        }
+        log.warn("Mirroring not found");
+        return null;
+    }
+
+    /**
+     * Gets mirrors of the device.
+     *
+     * @param deviceId target device id
+     * @return set of mirroring; empty if no mirror is found
+     */
+    @Override
+    public Set<MirroringStatistics> getMirroringStatistics(DeviceId deviceId) {
+        Uuid bridgeUuid = getBridgeUuid(deviceId);
+        if (bridgeUuid == null) {
+            log.warn("Couldn't find bridge {} in {}", deviceId, nodeId.getIpAddress());
+            return null;
+        }
+
+        List<MirroringStatistics> mirrorings = getMirrorings(bridgeUuid);
+        if (mirrorings == null) {
+            log.warn("Couldn't find mirrors in {}", nodeId.getIpAddress());
+            return null;
+        }
+        return ImmutableSet.copyOf(mirrorings);
+    }
+
+    /**
+     * Helper method which retrieves mirrorings statistics using bridge uuid.
+     *
+     * @param bridgeUuid the uuid of the bridge
+     * @return the list of the mirrorings statistics.
+     */
+    private List<MirroringStatistics> getMirrorings(Uuid bridgeUuid) {
+        DatabaseSchema dbSchema = schema.get(DATABASENAME);
+        if (dbSchema == null) {
+            log.warn("Unable to retrieve dbSchema {}", DATABASENAME);
+            return null;
+        }
+        OvsdbRowStore rowStore = getRowStore(DATABASENAME, BRIDGE);
+        if (rowStore == null) {
+            log.warn("Unable to retrieve rowStore {} of {}", BRIDGE, DATABASENAME);
+            return null;
+        }
+
+        Row bridgeRow = rowStore.getRow(bridgeUuid.value());
+        Bridge bridge = (Bridge) TableGenerator.
+                getTable(dbSchema, bridgeRow, OvsdbTable.BRIDGE);
+
+        Set<Uuid> mirroringsUuids = (Set<Uuid>) ((OvsdbSet) bridge
+                .getMirrorsColumn().data()).set();
+
+        OvsdbRowStore mirrorRowStore = getRowStore(DATABASENAME, MIRROR);
+        if (mirrorRowStore == null) {
+            log.warn("Unable to retrieve rowStore {} of {}", MIRROR, DATABASENAME);
+            return null;
+        }
+
+        List<MirroringStatistics> mirroringStatistics = new ArrayList<>();
+        ConcurrentMap<String, Row> mirrorTableRows = mirrorRowStore.getRowStore();
+        mirrorTableRows.forEach((key, row) -> {
+            if (!mirroringsUuids.contains(Uuid.uuid(key))) {
+                return;
+            }
+            Mirror mirror = (Mirror) TableGenerator
+                    .getTable(dbSchema, row, OvsdbTable.MIRROR);
+            mirroringStatistics.add(MirroringStatistics.mirroringStatistics(mirror.getName(),
+                                                                      (Map<String, Integer>) ((OvsdbMap) mirror
+                                                                   .getStatisticsColumn().data()).map()));
+        });
+        return ImmutableList.copyOf(mirroringStatistics);
+    }
+
     @Override
     public String getPortUuid(String portName, String bridgeUuid) {
         DatabaseSchema dbSchema = schema.get(DATABASENAME);
@@ -502,6 +613,142 @@
         deleteConfig(BRIDGE, UUID, bridgeUuid, DATABASENAME, BRIDGES);
     }
 
+    /**
+     * Creates a mirror port. Mirrors the traffic
+     * that goes to selectDstPort or comes from
+     * selectSrcPort or packets containing selectVlan
+     * to mirrorPort or to all ports that trunk mirrorVlan.
+     *
+     * @param mirror the OVSDB mirror description
+     * @return true if mirror creation is successful, false otherwise
+     */
+    @Override
+    public boolean createMirror(String bridgeName, OvsdbMirror mirror) {
+
+        /**
+         * Retrieves bridge's uuid. It is necessary to update
+         * Bridge table.
+         */
+        String bridgeUuid  = getBridgeUuid(bridgeName);
+        if (bridgeUuid == null) {
+            log.warn("Couldn't find bridge {} in {}", bridgeName, nodeId.getIpAddress());
+            return false;
+        }
+
+        OvsdbMirror.Builder mirrorBuilder = OvsdbMirror.builder();
+
+        mirrorBuilder.mirroringName(mirror.mirroringName());
+        mirrorBuilder.selectAll(mirror.selectAll());
+
+        /**
+         * Retrieves the uuid of the monitored dst ports.
+         */
+        mirrorBuilder.monitorDstPorts(mirror.monitorDstPorts().parallelStream()
+                                              .map(dstPort -> {
+                                                  String dstPortUuid = getPortUuid(dstPort.value(), bridgeUuid);
+                                                  if (dstPortUuid != null) {
+                                                      return Uuid.uuid(dstPortUuid);
+                                                  }
+                                                  log.warn("Couldn't find port {} in {}",
+                                                           dstPort.value(), nodeId.getIpAddress());
+                                                  return null;
+                                              })
+                                              .filter(Objects::nonNull)
+                                              .collect(Collectors.toSet())
+        );
+
+        /**
+         * Retrieves the uuid of the monitored src ports.
+         */
+        mirrorBuilder.monitorSrcPorts(mirror.monitorSrcPorts().parallelStream()
+                                              .map(srcPort -> {
+                                                  String srcPortUuid = getPortUuid(srcPort.value(), bridgeUuid);
+                                                  if (srcPortUuid != null) {
+                                                      return Uuid.uuid(srcPortUuid);
+                                                  }
+                                                  log.warn("Couldn't find port {} in {}",
+                                                           srcPort.value(), nodeId.getIpAddress());
+                                                  return null;
+                                              }).filter(Objects::nonNull)
+                                              .collect(Collectors.toSet())
+        );
+
+        mirrorBuilder.monitorVlans(mirror.monitorVlans());
+        mirrorBuilder.mirrorPort(mirror.mirrorPort());
+        mirrorBuilder.mirrorVlan(mirror.mirrorVlan());
+        mirrorBuilder.externalIds(mirror.externalIds());
+        mirror = mirrorBuilder.build();
+
+        if (mirror.monitorDstPorts().size() == 0 &&
+                mirror.monitorSrcPorts().size() == 0 &&
+                mirror.monitorVlans().size() == 0) {
+            log.warn("Invalid monitoring data");
+            return false;
+        }
+
+        DatabaseSchema dbSchema = schema.get(DATABASENAME);
+
+        Mirror mirrorEntry = (Mirror) TableGenerator.createTable(dbSchema, OvsdbTable.MIRROR);
+        mirrorEntry.setName(mirror.mirroringName());
+        mirrorEntry.setSelectDstPort(mirror.monitorDstPorts());
+        mirrorEntry.setSelectSrcPort(mirror.monitorSrcPorts());
+        mirrorEntry.setSelectVlan(mirror.monitorVlans());
+        mirrorEntry.setExternalIds(mirror.externalIds());
+
+        /**
+         * If mirror port, retrieves the uuid of the mirror port.
+         */
+        if (mirror.mirrorPort() != null) {
+
+            String outputPortUuid = getPortUuid(mirror.mirrorPort().value(), bridgeUuid);
+            if (outputPortUuid == null) {
+                log.warn("Couldn't find port {} in {}", mirror.mirrorPort().value(), nodeId.getIpAddress());
+                return false;
+            }
+
+            mirrorEntry.setOutputPort(Uuid.uuid(outputPortUuid));
+
+        } else if (mirror.mirrorVlan() != null) {
+
+            mirrorEntry.setOutputVlan(mirror.mirrorVlan());
+
+        } else {
+            log.warn("Invalid mirror, no mirror port and no mirror vlan");
+            return false;
+        }
+
+        ArrayList<Operation> operations = Lists.newArrayList();
+        Insert mirrorInsert = new Insert(dbSchema.getTableSchema("Mirror"), "Mirror", mirrorEntry.getRow());
+        operations.add(mirrorInsert);
+
+        // update the bridge table
+        Condition condition = ConditionUtil.isEqual(UUID, Uuid.uuid(bridgeUuid));
+        Mutation mutation = MutationUtil.insert(MIRRORS, Uuid.uuid("Mirror"));
+        List<Condition> conditions = Lists.newArrayList(condition);
+        List<Mutation> mutations = Lists.newArrayList(mutation);
+        operations.add(new Mutate(dbSchema.getTableSchema("Bridge"), conditions, mutations));
+
+        transactConfig(DATABASENAME, operations);
+        log.info("Created mirror {}", mirror.mirroringName());
+        return true;
+    }
+
+    /**
+     * Drops the configuration for mirror.
+     *
+     * @param mirroringName
+     */
+    @Override
+    public void dropMirror(MirroringName mirroringName) {
+        String mirrorUuid = getMirrorUuid(mirroringName.name());
+        if (mirrorUuid != null) {
+            log.info("Deleted mirror {}", mirroringName.name());
+            deleteConfig(MIRROR, UUID, mirrorUuid, BRIDGE, MIRRORS);
+        }
+        log.warn("Unable to delete {}", mirroringName.name());
+        return;
+    }
+
     @Deprecated
     @Override
     public boolean createTunnel(String bridgeName, String ifaceName, String tunnelType,