Fixed issue with 'moveHost' event handling in the topo view, to
handle multi-homed hosts.

Change-Id: If605388b00aef70ffc89639b60bb88b22f0469d6
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index b4073a0..a629dfc 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -174,26 +174,8 @@
         lu[id] = d;
         updateNodes();
 
-        function mkLinkKey(devId, devPort) {
-            return id + '/0-' + devId + '/' + devPort;
-        }
-
         // need to handle possible multiple links (multi-homed host)
-        d.links = [];
-        data.allCps.forEach(function (cp) {
-            var linkData = {
-                key: mkLinkKey(cp.device, cp.port),
-                dst: cp.device,
-                dstPort: cp.port,
-            };
-            d.links.push(linkData);
-
-            var lnk = tms.createHostLink(id, cp.device, cp.port);
-            if (lnk) {
-                network.links.push(lnk);
-                lu[linkData.key] = lnk;
-            }
-        });
+        createHostLinks(data.allCps, d);
 
         if (d.links.length) {
             updateLinks();
@@ -213,15 +195,30 @@
         }
     }
 
+    function createHostLinks(cps, model) {
+        model.links = [];
+        cps.forEach(function (cp) {
+            var linkData = {
+                key: model.id + '/0-' + cp.device + '/' + cp.port,
+                dst: cp.device,
+                dstPort: cp.port,
+            };
+            model.links.push(linkData);
+
+            var lnk = tms.createHostLink(model.id, cp.device, cp.port);
+            if (lnk) {
+                network.links.push(lnk);
+                lu[linkData.key] = lnk;
+            }
+        });
+    }
+
     function moveHost(data) {
         var id = data.id,
-            d = lu[id],
-            lnk;
+            d = lu[id];
 
         if (d) {
-            // first remove the old host link
-            // FIXME: what if the host has multiple links??????
-            removeLinkElement(d.linkData);
+            removeAllLinkElements(d.links);
 
             // merge new data
             angular.extend(d, data);
@@ -229,19 +226,8 @@
                 sendUpdateMeta(d);
             }
 
-            // now create a new host link
-            // TODO: verify this is the APPROPRIATE host link
-            lnk = tms.createHostLink(id, data.cp.device, data.cp.port);
-            if (lnk) {
-                network.links.push(lnk);
-                lu[lnk.key] = lnk;
-
-                d.links.push({
-                    key: id + '/0-' + cp.device + '/' + cp.port,
-                    dst: data.cp.device,
-                    dstPort: data.cp.port,
-                });
-            }
+            // now create new host link(s)
+            createHostLinks(data.allCps, d);
 
             updateNodes();
             updateLinks();
@@ -391,6 +377,12 @@
         }
     }
 
+    function removeAllLinkElements(links) {
+        links.forEach(function (lnk) {
+            removeLinkElement(lnk);
+        });
+    }
+
     function removeLinkElement(d) {
         var idx = fs.find(d.key, network.links, 'key'),
             removed;
diff --git a/web/gui/src/main/webapp/tests/package-lock.json b/web/gui/src/main/webapp/tests/package-lock.json
index b5f7f3c..4eb860d 100644
--- a/web/gui/src/main/webapp/tests/package-lock.json
+++ b/web/gui/src/main/webapp/tests/package-lock.json
@@ -9,6 +9,16 @@
       "integrity": "sha1-Vdvr7wrZFVOC2enT5JfBNyNFtEo=",
       "dev": true
     },
+    "debug": {
+      "version": "2.6.8",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
+      "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw="
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+    },
     "jasmine-core": {
       "version": "2.6.4",
       "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.6.4.tgz",
@@ -3514,6 +3524,16 @@
         }
       }
     },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "nan": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
+      "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY="
+    },
     "phantomjs": {
       "version": "2.1.7",
       "resolved": "https://registry.npmjs.org/phantomjs/-/phantomjs-2.1.7.tgz",
@@ -4261,6 +4281,21 @@
           }
         }
       }
+    },
+    "typedarray-to-buffer": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz",
+      "integrity": "sha1-EBezLZhP9VbroQD1AViauhrOLgQ="
+    },
+    "websocket": {
+      "version": "1.0.24",
+      "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.24.tgz",
+      "integrity": "sha1-dJA+dfJUW2suHeFCW8HJBZF6GJA="
+    },
+    "yaeti": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
+      "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
     }
   }
 }
diff --git a/web/gui/src/main/webapp/tests/package.json b/web/gui/src/main/webapp/tests/package.json
index 59b8102..c0ae7c3 100644
--- a/web/gui/src/main/webapp/tests/package.json
+++ b/web/gui/src/main/webapp/tests/package.json
@@ -26,6 +26,7 @@
     "karma-mocha-reporter": "^1.1.3",
     "karma-ng-html2js-preprocessor": "^0.2.0",
     "karma-phantomjs-launcher": "^0.2.1",
-    "phantomjs": "^2.1.3"
+    "phantomjs": "^2.1.3",
+    "websocket": "^1.0.24"
   }
 }
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_1_addInstance.json b/web/gui/src/test/_karma/ev/dualHomed/ev_1_addInstance.json
new file mode 100644
index 0000000..daf074b
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_1_addInstance.json
@@ -0,0 +1,15 @@
+{
+  "event": "addInstance",
+  "payload": {
+    "id": "127.0.0.1",
+    "ip": "127.0.0.1",
+    "online": true,
+    "ready": true,
+    "uiAttached": true,
+    "switches": 2,
+    "labels": [
+      "127.0.0.1",
+      "127.0.0.1"
+    ]
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_2_addDevice_01.json b/web/gui/src/test/_karma/ev/dualHomed/ev_2_addDevice_01.json
new file mode 100644
index 0000000..c30ea6b
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_2_addDevice_01.json
@@ -0,0 +1,19 @@
+{
+  "event": "addDevice",
+  "payload": {
+    "id": "of:0000000000000001",
+    "type": "switch",
+    "online": true,
+    "master": "127.0.0.1",
+    "labels": [
+      "",
+      "of:0000000000000001",
+      "of:0000000000000001"
+    ],
+    "props": {
+      "managementAddress": "192.168.36.101",
+      "protocol": "OF_13",
+      "channelId": "192.168.36.101:55710"
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_3_addDevice_02.json b/web/gui/src/test/_karma/ev/dualHomed/ev_3_addDevice_02.json
new file mode 100644
index 0000000..2fde4a6
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_3_addDevice_02.json
@@ -0,0 +1,19 @@
+{
+  "event": "addDevice",
+  "payload": {
+    "id": "of:0000000000000002",
+    "type": "switch",
+    "online": true,
+    "master": "127.0.0.1",
+    "labels": [
+      "",
+      "of:0000000000000002",
+      "of:0000000000000002"
+    ],
+    "props": {
+      "managementAddress": "192.168.36.101",
+      "protocol": "OF_13",
+      "channelId": "192.168.36.101:55712"
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_4_addLink_1to2.json b/web/gui/src/test/_karma/ev/dualHomed/ev_4_addLink_1to2.json
new file mode 100644
index 0000000..3ce5e3a
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_4_addLink_1to2.json
@@ -0,0 +1,14 @@
+{
+  "event": "addLink",
+  "payload": {
+    "id": "of:0000000000000001/2-of:0000000000000002/2",
+    "type": "direct",
+    "expected": false,
+    "online": true,
+    "linkWidth": 1.2,
+    "src": "of:0000000000000001",
+    "srcPort": "2",
+    "dst": "of:0000000000000002",
+    "dstPort": "2"
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_5_addLink_2to1.json b/web/gui/src/test/_karma/ev/dualHomed/ev_5_addLink_2to1.json
new file mode 100644
index 0000000..ad4562e
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_5_addLink_2to1.json
@@ -0,0 +1,14 @@
+{
+  "event": "addLink",
+  "payload": {
+    "id": "of:0000000000000002/2-of:0000000000000001/2",
+    "type": "direct",
+    "expected": false,
+    "online": true,
+    "linkWidth": 1.2,
+    "src": "of:0000000000000002",
+    "srcPort": "2",
+    "dst": "of:0000000000000001",
+    "dstPort": "2"
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_6_addHost_to1.json b/web/gui/src/test/_karma/ev/dualHomed/ev_6_addHost_to1.json
new file mode 100644
index 0000000..6a234a6
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_6_addHost_to1.json
@@ -0,0 +1,23 @@
+{
+  "event": "addHost",
+  "payload": {
+    "id": "00:00:00:00:00:01/None",
+    "type": "endstation",
+    "cp": {
+      "device": "of:0000000000000001",
+      "port": 1
+    },
+    "allCps": [
+      {
+        "device": "of:0000000000000001",
+        "port": 1
+      }
+    ],
+    "labels": [
+      "10.0.0.1",
+      "10.0.0.1",
+      "00:00:00:00:00:01"
+    ],
+    "props": {}
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_7_updateDevice_01.json b/web/gui/src/test/_karma/ev/dualHomed/ev_7_updateDevice_01.json
new file mode 100644
index 0000000..214d10b
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_7_updateDevice_01.json
@@ -0,0 +1,28 @@
+{
+  "event": "updateDevice",
+  "payload": {
+    "id": "of:0000000000000001",
+    "type": "switch",
+    "online": true,
+    "master": "127.0.0.1",
+    "labels": [
+      "",
+      "SW-A",
+      "of:0000000000000001"
+    ],
+    "props": {
+      "managementAddress": "192.168.36.101",
+      "protocol": "OF_13",
+      "latitude": "43.37",
+      "name": "SW-A",
+      "locType": "geo",
+      "channelId": "192.168.36.101:55710",
+      "longitude": "-104.6572"
+    },
+    "location": {
+      "locType": "geo",
+      "latOrY": 43.37,
+      "longOrX": -104.6572
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_8_updateDevice_02.json b/web/gui/src/test/_karma/ev/dualHomed/ev_8_updateDevice_02.json
new file mode 100644
index 0000000..3fbb1d4
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_8_updateDevice_02.json
@@ -0,0 +1,28 @@
+{
+  "event": "updateDevice",
+  "payload": {
+    "id": "of:0000000000000002",
+    "type": "switch",
+    "online": true,
+    "master": "127.0.0.1",
+    "labels": [
+      "",
+      "SW-B",
+      "of:0000000000000002"
+    ],
+    "props": {
+      "managementAddress": "192.168.36.101",
+      "protocol": "OF_13",
+      "latitude": "38.5189",
+      "name": "SW-B",
+      "locType": "geo",
+      "channelId": "192.168.36.101:55712",
+      "longitude": "-109.2781"
+    },
+    "location": {
+      "locType": "geo",
+      "latOrY": 38.5189,
+      "longOrX": -109.2781
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/ev_9_moveHost.json b/web/gui/src/test/_karma/ev/dualHomed/ev_9_moveHost.json
new file mode 100644
index 0000000..195208c
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/ev_9_moveHost.json
@@ -0,0 +1,41 @@
+{
+  "event": "moveHost",
+  "payload": {
+    "id": "00:00:00:00:00:01/None",
+    "type": "endstation",
+    "cp": {
+      "device": "of:0000000000000001",
+      "port": 1
+    },
+    "prevCp": {
+      "device": "of:0000000000000001",
+      "port": 1
+    },
+    "allCps": [
+      {
+        "device": "of:0000000000000001",
+        "port": 1
+      },
+      {
+        "device": "of:0000000000000002",
+        "port": 1
+      }
+    ],
+    "labels": [
+      "Host-X",
+      "10.0.0.1",
+      "00:00:00:00:00:01"
+    ],
+    "props": {
+      "name": "Host-X",
+      "locType": "geo",
+      "latitude": "39.9444",
+      "longitude": "-91.7822"
+    },
+    "location": {
+      "locType": "geo",
+      "latOrY": 39.9444,
+      "longOrX": -91.7822
+    }
+  }
+}
diff --git a/web/gui/src/test/_karma/ev/dualHomed/scenario.json b/web/gui/src/test/_karma/ev/dualHomed/scenario.json
new file mode 100644
index 0000000..a640d38
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/dualHomed/scenario.json
@@ -0,0 +1,22 @@
+{
+  "comments": [
+    "Single Host connected to two devices (Dual-Homed)."
+  ],
+  "title": "Dual-Homed Scenario",
+  "params": {
+    "lastAuto": 0
+  },
+  "description": [
+    "Simple sequence of events...",
+    "",
+    "1. add instance",
+    "2. add device [1]",
+    "3. add device [2]",
+    "4. add link [1] --> [2]",
+    "5. add link [2] --> [1]",
+    "6. add host (to [1])",
+    "7. update device [1]",
+    "8. update device [2]",
+    "9. move host (to include [2])"
+  ]
+}