[CORD-3227] Support for dual-stack ips

Change-Id: Ia01abab7a9818743bce8dcd0f153ba3d9972cb3a
diff --git a/Dockerfile.synchronizer b/Dockerfile.synchronizer
index 067bf7a..e9aedf0 100644
--- a/Dockerfile.synchronizer
+++ b/Dockerfile.synchronizer
@@ -12,7 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# xosproject/vsg-synchronizer
+# xosproject/vsg-hw-synchronizer
+#
+# docker build -t xosproject/vsg-hw-synchronizer:candidate -f Dockerfile.synchronizer .
 
 FROM xosproject/xos-synchronizer-base:2.1.22
 
diff --git a/VERSION b/VERSION
index 6d7de6e..336c367 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.2
+1.1.0-dev
diff --git a/xos/synchronizer/model_policies/model_policy_vsghwserviceinstance.py b/xos/synchronizer/model_policies/model_policy_vsghwserviceinstance.py
index 18475c9..648f509 100644
--- a/xos/synchronizer/model_policies/model_policy_vsghwserviceinstance.py
+++ b/xos/synchronizer/model_policies/model_policy_vsghwserviceinstance.py
@@ -41,5 +41,3 @@
 
     def handle_delete(self, service_instance):
         log.info("Handle_delete VSG-HW Service Instance", service_instance=service_instance)
-
-
diff --git a/xos/synchronizer/models/vsg-hw.xproto b/xos/synchronizer/models/vsg-hw.xproto
index c96e82d..dc18e14 100644
--- a/xos/synchronizer/models/vsg-hw.xproto
+++ b/xos/synchronizer/models/vsg-hw.xproto
@@ -6,7 +6,6 @@
     option description="HW VSG implementation";
 }
 
-
 message VSGHWServiceInstance (ServiceInstance){
     option verbose_name = "vSGHW Service Instance";
     option owner_class_name="VSGHWService";
diff --git a/xos/synchronizer/steps/sync_vsg_hw_service_instance.py b/xos/synchronizer/steps/sync_vsg_hw_service_instance.py
index 871d68b..b30987f 100644
--- a/xos/synchronizer/steps/sync_vsg_hw_service_instance.py
+++ b/xos/synchronizer/steps/sync_vsg_hw_service_instance.py
@@ -21,6 +21,7 @@
 import requests
 from requests.auth import HTTPBasicAuth
 
+log = create_logger(Config().get('logging'))
 
 class SyncVSGHWServiceInstance(SyncStep):
     provides = [VSGHWServiceInstance]
@@ -56,15 +57,14 @@
         }
 
     def sync_record(self, o):
-        self.log.info("Sync'ing VSG-HW Service Instance", service_instance=o)
-
+        log.info("Sync'ing VSG-HW Service Instance", service_instance=o)
 
         onos = SyncVSGHWServiceInstance.get_fabric_onos_info(o)
 
         si = ServiceInstance.objects.get(id=o.id)
 
         mac_address = si.get_westbound_service_instance_properties("mac_address")
-        ip = si.get_westbound_service_instance_properties("ip_address")
+        ips = si.get_westbound_service_instance_properties("ips")
         s_tag = si.get_westbound_service_instance_properties("s_tag")
         c_tag = si.get_westbound_service_instance_properties("c_tag")
         dpid = si.get_westbound_service_instance_properties("switch_datapath_id")
@@ -81,8 +81,8 @@
         try:
             if not mac_address:
                 raise ValueError("mac_address")
-            if not ip:
-                raise ValueError("ip_address")
+            if len(ips.all()) == 0:
+                raise ValueError("ips")
             if not s_tag:
                 raise ValueError("s_tag")
             if not c_tag:
@@ -92,13 +92,14 @@
             if not port:
                 raise ValueError("switch_port")
         except ValueError as e:
+            log.info("Skipping synchronization for VSG-HW Service Instance with id %s as westbound value %s is not available" % (o.id, e.message))
             raise Exception("Skipping synchronization for VSG-HW Service Instance with id %s as westbound value %s is not available" % (o.id, e.message))
 
         data = {
             'hosts': {
                 mac_address + "/" + str(s_tag): {
                     "basic": {
-                        "ips": [ip],
+                        "ips": [ i.ip for i in ips.all() ],
                         "locations": ["%s/%s" % (dpid, port)],
                         "innerVlan": str(c_tag),
                     }
@@ -113,17 +114,17 @@
 
         url = onos['url'] + '/onos/v1/network/configuration'
 
-        self.log.info("Sending requests to ONOS", url=url, body=data)
+        log.info("Sending requests to ONOS", url=url, body=data)
 
         r = requests.post(url, json=data, auth=HTTPBasicAuth(onos['user'], onos['pass']))
 
         if r.status_code != 200:
             raise Exception("Failed to terminate subscriber in ONOS: %s" % r.text)
 
-        self.log.info("ONOS response", res=r.text)
+        log.info("ONOS response", res=r.text)
 
     def delete_record(self, o):
-        self.log.info("Deleting VSG-HW Service Instance", service_instance=o)
+        log.info("Deleting VSG-HW Service Instance", service_instance=o)
         if o.enacted:
             onos = SyncVSGHWServiceInstance.get_fabric_onos_info(o)
 
@@ -142,5 +143,5 @@
             if r.status_code != 204:
                 raise Exception("Failed to remove subscriber termination in ONOS: %s" % r.text)
 
-            self.log.info("ONOS response", res=r.text)
+            log.info("ONOS response", res=r.text)
         pass
\ No newline at end of file
diff --git a/xos/synchronizer/steps/test_sync_vsg_hw_service_instance.py b/xos/synchronizer/steps/test_sync_vsg_hw_service_instance.py
index e05f597..7cea075 100644
--- a/xos/synchronizer/steps/test_sync_vsg_hw_service_instance.py
+++ b/xos/synchronizer/steps/test_sync_vsg_hw_service_instance.py
@@ -47,6 +47,17 @@
 def mock_get_westbound_service_instance_properties(prop):
     if prop == "status":
         return "enabled"
+    if prop == "ips":
+
+        ip1 = Mock()
+        ip1.ip = "ip_address1"
+
+        ip2 = Mock()
+        ip2.ip = "ip_address2"
+
+        ips = Mock()
+        ips.all.return_value = [ip1, ip2]
+        return ips
     return prop
 
 def match_json(desired, req):
@@ -126,13 +137,19 @@
         """
         If we don't have all the data we need, do not synchronize
         """
-
-        for i in ["mac_address", "ip_address", "s_tag", "c_tag", "switch_datapath_id", "switch_port"]:
+        for i in ["mac_address", "ips", "s_tag", "c_tag", "switch_datapath_id", "switch_port"]:
 
             def wb_si_prop(prop):
                 # fake prop not present
                 if prop == "status":
                     return "enabled"
+                if prop == "ips":
+                    ips = Mock()
+                    if i == "ips":
+                        ips.all.return_value = []
+                    else:
+                        ips.all.return_value = ["foo"]
+                    return ips
                 if prop != i:
                     return prop
                 return ""
@@ -157,6 +174,10 @@
                 # fake prop not present
                 if prop == "status":
                     return "suspended"
+                if prop == "ips":
+                    ips = Mock()
+                    ips.all.return_value = []
+                    return ips
                 return prop
 
             self.si.get_westbound_service_instance_properties = wb_si_prop
@@ -175,7 +196,7 @@
             'hosts': {
                 "mac_address/s_tag": {
                     "basic": {
-                        "ips": ["ip_address"],
+                        "ips": ["ip_address1", "ip_address2"],
                         "locations": ["switch_datapath_id/switch_port"],
                         "innerVlan": "c_tag",
                     }
@@ -193,6 +214,16 @@
                 return None
             if prop == "status":
                 return "enabled"
+            if prop == "ips":
+                ip1 = Mock()
+                ip1.ip = "ip_address1"
+
+                ip2 = Mock()
+                ip2.ip = "ip_address2"
+
+                ips = Mock()
+                ips.all.return_value = [ip1, ip2]
+                return ips
             return prop
 
         self.si.get_westbound_service_instance_properties = wb_si_prop
@@ -210,7 +241,7 @@
             'hosts': {
                 "mac_address/s_tag": {
                     "basic": {
-                        "ips": ["ip_address"],
+                        "ips": ["ip_address1", "ip_address2"],
                         "locations": ["switch_datapath_id/switch_port"],
                         "innerVlan": "c_tag",
                         "outerTpid": "outer_tpid"
@@ -258,6 +289,16 @@
                 # fake prop not present
                 if prop == "status":
                     return "suspended"
+                if prop == "ips":
+                    ip1 = Mock()
+                    ip1.ip = "ip_address1"
+
+                    ip2 = Mock()
+                    ip2.ip = "ip_address2"
+
+                    ips = Mock()
+                    ips.all.return_value = [ip1, ip2]
+                    return ips
                 return prop
 
             self.si.get_westbound_service_instance_properties = wb_si_prop