[ 3195 ] Remove duplicate method add_port

Update to the device operations as follows:
1) Add a few test scenarios to test the device state transitions
2) Check whether there is a callback before removing it from the queue
3) Fix a port reference issue when disabling an ONU
4) Update the CLI to handle exceptions from the grpc server

Change-Id: Ic7f41e80279f41d9a4575da5dd49de11294a22d5
diff --git a/voltha/core/adapter_agent.py b/voltha/core/adapter_agent.py
index d6e32af..e926d9d 100644
--- a/voltha/core/adapter_agent.py
+++ b/voltha/core/adapter_agent.py
@@ -221,9 +221,7 @@
     def update_adapter_pm_config(self, device, device_pm_config):
         self.adapter.update_pm_config(device, device_pm_config)
 
-    def add_port(self, device_id, port):
-        assert isinstance(port, Port)
-
+    def _add_peer_reference(self, device_id, port):
         # for referential integrity, add/augment references
         port.device_id = device_id
         me_as_peer = Port.PeerPort(device_id=device_id, port_no=port.port_no)
@@ -236,13 +234,30 @@
                 new.CopyFrom(me_as_peer)
             self.root_proxy.update(peer_port_path, peer_port)
 
+    def _del_peer_reference(self, device_id, port):
+        me_as_peer = Port.PeerPort(device_id=device_id, port_no=port.port_no)
+        for peer in port.peers:
+            peer_port_path = '/devices/{}/ports/{}'.format(
+                peer.device_id, peer.port_no)
+            peer_port = self.root_proxy.get(peer_port_path)
+            if me_as_peer in peer_port.peers:
+                peer_port.peers.remove(me_as_peer)
+            self.root_proxy.update(peer_port_path, peer_port)
+
+    def add_port(self, device_id, port):
+        assert isinstance(port, Port)
+
+        # for referential integrity, add/augment references
+        self._add_peer_reference(device_id, port)
+
+        # Add port
         self._make_up_to_date('/devices/{}/ports'.format(device_id),
                               port.port_no, port)
 
     def disable_all_ports(self, device_id):
         """
         Disable all ports on that device, i.e. change the admin status to
-        disable and operational status to UNKNOWN
+        disable and operational status to UNKNOWN.
         :param device_id: device id
         :return: None
         """
@@ -255,7 +270,8 @@
             self._make_up_to_date('/devices/{}/ports'.format(device_id),
                                   port.port_no, port)
 
-    def reenable_all_ports(self, device_id):
+
+    def enable_all_ports(self, device_id):
         """
         Re-enable all ports on that device, i.e. change the admin status to
         enabled and operational status to ACTIVE
@@ -293,16 +309,18 @@
         """
         assert isinstance(port, Port)
         self.log.info('delete-port-reference', device_id=device_id, port=port)
+        self._del_peer_reference(device_id, port)
 
-        # for referential integrity, remove references
-        me_as_peer = Port.PeerPort(device_id=device_id, port_no=port.port_no)
-        for peer in port.peers:
-            peer_port_path = '/devices/{}/ports/{}'.format(
-                peer.device_id, peer.port_no)
-            peer_port = self.root_proxy.get(peer_port_path)
-            if me_as_peer in peer_port.peers:
-                peer_port.peers.remove(me_as_peer)
-            self.root_proxy.update(peer_port_path, peer_port)
+    def add_port_reference_to_parent(self, device_id, port):
+        """
+        Add the port reference to the parent device
+        :param device_id: id of device containing the port
+        :param port: port to add
+        :return: None
+        """
+        assert isinstance(port, Port)
+        self.log.info('add-port-reference', device_id=device_id, port=port)
+        self._add_peer_reference(device_id, port)
 
     def _find_first_available_id(self):
         logical_devices = self.root_proxy.get('/logical_devices')
diff --git a/voltha/core/config/config_node.py b/voltha/core/config/config_node.py
index 56518bb..f1c8fc0 100644
--- a/voltha/core/config/config_node.py
+++ b/voltha/core/config/config_node.py
@@ -309,7 +309,7 @@
                     self._mk_event_bus().advertise,
                     change_type,
                     data,
-                    hash=rev.hash,
+                    hash=rev.hash
                 )
 
     # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ add operation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/voltha/core/config/config_proxy.py b/voltha/core/config/config_proxy.py
index fa6ec57..57d8150 100644
--- a/voltha/core/config/config_proxy.py
+++ b/voltha/core/config/config_proxy.py
@@ -135,7 +135,8 @@
 
     def unregister_callback(self, callback_type, callback, *args, **kw):
         lst = self._callbacks.setdefault(callback_type, [])
-        lst.remove((callback, args, kw))
+        if (callback, args, kw) in lst:
+            lst.remove((callback, args, kw))
 
     # ~~~~~~~~~~~~~~~~~~~~~ Callback dispatch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/voltha/core/local_handler.py b/voltha/core/local_handler.py
index 7360ca8..4a0fde8 100644
--- a/voltha/core/local_handler.py
+++ b/voltha/core/local_handler.py
@@ -262,7 +262,7 @@
                 'in admin state \'{}\''.format(device.admin_state)
 
         except AssertionError, e:
-            context.set_details(e.msg)
+            context.set_details(e.message)
             context.set_code(StatusCode.INVALID_ARGUMENT)
             return Device()
 
@@ -287,7 +287,7 @@
             context.set_details(
                 'Malformed device id \'{}\''.format(request.id))
             context.set_code(StatusCode.INVALID_ARGUMENT)
-            return Device()
+            return Empty()
 
         try:
             path = '/devices/{}'.format(request.id)
@@ -300,9 +300,8 @@
             self.root.update(path, device, strict=True)
 
         except AssertionError, e:
-            context.set_details(e.msg)
+            context.set_details(e.message)
             context.set_code(StatusCode.INVALID_ARGUMENT)
-            return Device()
 
         except KeyError:
             context.set_details(
@@ -319,8 +318,7 @@
             context.set_details(
                 'Malformed device id \'{}\''.format(request.id))
             context.set_code(StatusCode.INVALID_ARGUMENT)
-            return Device()
-
+            return Empty()
         try:
             path = '/devices/{}'.format(request.id)
             device = self.root.get(path)
@@ -331,9 +329,8 @@
             self.root.update(path, device, strict=True)
 
         except AssertionError, e:
-            context.set_details(e.msg)
+            context.set_details(e.message)
             context.set_code(StatusCode.INVALID_ARGUMENT)
-            return Device()
 
         except KeyError:
             context.set_details(
@@ -350,7 +347,7 @@
             context.set_details(
                 'Malformed device id \'{}\''.format(request.id))
             context.set_code(StatusCode.INVALID_ARGUMENT)
-            return Device()
+            return Empty()
 
         try:
             path = '/devices/{}'.format(request.id)
@@ -359,11 +356,6 @@
             agent = self.core.get_device_agent(device.id)
             agent.reboot_device(device)
 
-        except AssertionError, e:
-            context.set_details(e.msg)
-            context.set_code(StatusCode.INVALID_ARGUMENT)
-            return Device()
-
         except KeyError:
             context.set_details(
                 'Device \'{}\' not found'.format(request.id))
@@ -379,7 +371,7 @@
             context.set_details(
                 'Malformed device id \'{}\''.format(request.id))
             context.set_code(StatusCode.INVALID_ARGUMENT)
-            return Device()
+            return Empty()
 
         try:
             path = '/devices/{}'.format(request.id)
@@ -391,9 +383,8 @@
             self.root.remove(path)
 
         except AssertionError, e:
-            context.set_details(e.msg)
+            context.set_details(e.message)
             context.set_code(StatusCode.INVALID_ARGUMENT)
-            return Device()
 
         except KeyError:
             context.set_details(
@@ -432,9 +423,10 @@
             return PmConfigs()
 
         try:
-            pm_configs = self.root.get('/devices/{}/pm_configs'.format(request.id))
+            pm_configs = self.root.get(
+                '/devices/{}/pm_configs'.format(request.id))
             pm_configs.id = request.id
-            log.info('device-for-pms',pm_configs=pm_configs)
+            log.info('device-for-pms', pm_configs=pm_configs)
             return pm_configs
         except KeyError:
             context.set_details(
diff --git a/voltha/core/logical_device_agent.py b/voltha/core/logical_device_agent.py
index 1ecd032..dc5d1c0 100644
--- a/voltha/core/logical_device_agent.py
+++ b/voltha/core/logical_device_agent.py
@@ -85,17 +85,21 @@
 
     def stop(self):
         self.log.debug('stopping')
-        self.flows_proxy.unregister_callback(
-            CallbackType.POST_UPDATE, self._flow_table_updated)
-        self.groups_proxy.unregister_callback(
-            CallbackType.POST_UPDATE, self._group_table_updated)
-        self.self_proxy.unregister_callback(
-            CallbackType.POST_ADD, self._port_list_updated)
-        self.self_proxy.unregister_callback(
-            CallbackType.POST_REMOVE, self._port_list_updated)
+        try:
+            self.flows_proxy.unregister_callback(
+                CallbackType.POST_UPDATE, self._flow_table_updated)
+            self.groups_proxy.unregister_callback(
+                CallbackType.POST_UPDATE, self._group_table_updated)
+            self.self_proxy.unregister_callback(
+                CallbackType.POST_ADD, self._port_list_updated)
+            self.self_proxy.unregister_callback(
+                CallbackType.POST_REMOVE, self._port_list_updated)
 
-        # Remove subscription to the event bus
-        self.event_bus.unsubscribe(self.packet_in_subscription)
+            # Remove subscription to the event bus
+            self.event_bus.unsubscribe(self.packet_in_subscription)
+        except Exception, e:
+            self.log.info('stop-exception', e=e)
+
         self.log.info('stopped')
 
     def announce_flows_deleted(self, flows):
@@ -115,15 +119,6 @@
     def signal_group_mod_error(self, code, group_mod):
         pass  # TODO
 
-    def delete_all_flows(self):
-        self.update_flow_table(mk_simple_flow_mod(
-            command=ofp.OFPFC_DELETE,
-            out_port=ofp.OFPP_ANY,
-            out_group=ofp.OFPG_ANY,
-            match_fields=[],
-            actions=[]
-        ))
-
     def update_flow_table(self, flow_mod):
 
         command = flow_mod.command