Improve fingerprint computation for pipeconf

Do not read extensions in memory and consider pipeline model.

Change-Id: I3e077fbcd9ed0a2dba78b4e1c87e95ecb7287be6
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java b/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java
index cff8248..464bdd7 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/DefaultPiPipeconf.java
@@ -17,7 +17,8 @@
 package org.onosproject.net.pi.model;
 
 import com.google.common.collect.ImmutableMap;
-import org.apache.commons.io.IOUtils;
+import com.google.common.hash.Hashing;
+import com.google.common.hash.HashingInputStream;
 import org.onosproject.net.driver.Behaviour;
 
 import java.io.IOException;
@@ -188,21 +189,32 @@
             checkNotNull(pipelineModel);
 
             Map<ExtensionType, URL> extensions = extensionMapBuilder.build();
-            return new DefaultPiPipeconf(id, pipelineModel, generateFingerprint(extensions),
-                                         behaviourMapBuilder.build(), extensions);
+            Map<Class<? extends Behaviour>, Class<? extends Behaviour>> behaviours =
+                    behaviourMapBuilder.build();
+            return new DefaultPiPipeconf(
+                    id, pipelineModel, generateFingerprint(extensions.values()),
+                    behaviours, extensions);
         }
 
-        private long generateFingerprint(Map<ExtensionType, URL> extensions) {
-            Collection<Integer> hashArray = new ArrayList<>();
-            for (Map.Entry<ExtensionType, URL> pair : extensions.entrySet()) {
+        private long generateFingerprint(Collection<URL> extensions) {
+            Collection<Integer> hashes = new ArrayList<>();
+            for (URL extUrl : extensions) {
                 try {
-                    hashArray.add(Arrays.hashCode(ByteBuffer.wrap(IOUtils.toByteArray(
-                            pair.getValue().openStream())).array()));
+                    HashingInputStream hin = new HashingInputStream(
+                            Hashing.crc32(), extUrl.openStream());
+                    //noinspection StatementWithEmptyBody
+                    while (hin.read() != -1) {
+                        // Do nothing. Reading all input stream to update hash.
+                    }
+                    hashes.add(hin.hash().asInt());
                 } catch (IOException e) {
                     throw new IllegalArgumentException(e);
                 }
             }
-            return Arrays.hashCode(hashArray.toArray());
+            //  FIXME: how to include behaviours in the hash?
+            int low = Arrays.hashCode(hashes.toArray());
+            int high = pipelineModel.hashCode();
+            return ByteBuffer.allocate(8).putInt(high).putInt(low).getLong(0);
         }
     }
 }
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java
index 29deb4f..3070d66 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiPipeconfManager.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Striped;
+import org.onlab.util.HexString;
 import org.onlab.util.ItemNotFoundException;
 import org.onlab.util.SharedExecutors;
 import org.onosproject.net.DeviceId;
@@ -56,6 +57,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
 
+import static com.google.common.base.Preconditions.checkNotNull;
 import static java.lang.String.format;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -123,24 +125,28 @@
 
     @Override
     public void register(PiPipeconf pipeconf) throws IllegalStateException {
+        checkNotNull(pipeconf);
         if (pipeconfs.containsKey(pipeconf.id())) {
             throw new IllegalStateException(format("Pipeconf %s is already registered", pipeconf.id()));
         }
         pipeconfs.put(pipeconf.id(), pipeconf);
-        log.info("New pipeconf registered: {}", pipeconf.id());
+        log.info("New pipeconf registered: {} (fingerprint={})",
+                 pipeconf.id(), HexString.toHexString(pipeconf.fingerprint()));
         executor.execute(() -> attemptMergeAll(pipeconf.id()));
     }
 
     @Override
     public void remove(PiPipeconfId pipeconfId) throws IllegalStateException {
+        checkNotNull(pipeconfId);
         // TODO add mechanism to remove from device.
         if (!pipeconfs.containsKey(pipeconfId)) {
             throw new IllegalStateException(format("Pipeconf %s is not registered", pipeconfId));
         }
         // TODO remove the binding from the distributed Store when the lifecycle of a pipeconf is defined.
         // pipeconfMappingStore.removeBindings(pipeconfId);
-        log.info("Removing pipeconf {}", pipeconfId);
-        pipeconfs.remove(pipeconfId);
+        final PiPipeconf pipeconf = pipeconfs.remove(pipeconfId);
+        log.info("Unregistered pipeconf: {} (fingerprint={})",
+                 pipeconfId, HexString.toHexString(pipeconf.fingerprint()));
     }
 
     @Override