Converted the storage model for pm_configs to a child node of the
device rather then being embedded in the device. Finalized all API
calls and callbacks. The simulated adapter and ponsim will be addressed
in a subsequent commit.

Amendment: Created a more generic variable to pass additional data to
the callbacks registerd for data model updates as suggested by the code
review.

Change-Id: Idc5e2a175a17be791f5f7696bd2f0a02e2a0b965
diff --git a/voltha/adapters/simulated_olt/simulated_olt.py b/voltha/adapters/simulated_olt/simulated_olt.py
index 5a05fc9..1850afd 100644
--- a/voltha/adapters/simulated_olt/simulated_olt.py
+++ b/voltha/adapters/simulated_olt/simulated_olt.py
@@ -170,7 +170,8 @@
         raise NotImplementedError()
 
     def update_pm_config(self, device, pm_configs):
-        raise NotImplementedError()
+        #raise NotImplementedError()
+        log.info("adapter-update-pm-config", device=device, pm_configs=pm_configs)
 
     def _tmp_populate_stuff(self):
         """
@@ -315,53 +316,60 @@
         device.software_version = '1.0'
         device.serial_number = uuid4().hex
         device.connect_status = ConnectStatus.REACHABLE
-        device.pm_configs.default_freq=150
-        device.pm_configs.grouped = False
-        device.pm_configs.freq_override = False
-        device.pm_configs.metrics.extend([PmConfig(name='tx_64',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='tx_65_127',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='tx_128_255',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='tx_256_511',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='tx_512_1023',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='tx_1024_1518',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='tx_1519_9k',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='rx_64',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='rx_65_127',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='rx_128_255',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='rx_256_511',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='rx_512_1023',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='rx_1024_1518',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
-        device.pm_configs.metrics.extend([PmConfig(name='rx_1519_9k',
-                                                   type=PmConfig.COUNTER,
-                                                   enabled=True)])
+
         self.adapter_agent.update_device(device)
 
+        # Now set the initial PM configuration for this device
+        pm_configs = PmConfigs(
+            id=device.id,
+            default_freq=150,
+            grouped = False,
+            freq_override = False)
+
+        pm_configs.metrics.extend([PmConfig(name='tx_64',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='tx_65_127',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='tx_128_255',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='tx_256_511',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='tx_512_1023',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='tx_1024_1518',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='tx_1519_9k',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='rx_64',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='rx_65_127',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='rx_128_255',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='rx_256_511',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='rx_512_1023',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='rx_1024_1518',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+        pm_configs.metrics.extend([PmConfig(name='rx_1519_9k',
+                                            type=PmConfig.COUNTER,
+                                            enabled=True)])
+
+        self.adapter_agent.update_device_pm_config(pm_configs,init=True)
         # then shortly after we create some ports for the device
         yield asleep(0.05)
         nni_port = Port(
diff --git a/voltha/core/adapter_agent.py b/voltha/core/adapter_agent.py
index 7ba382b..d6e32af 100644
--- a/voltha/core/adapter_agent.py
+++ b/voltha/core/adapter_agent.py
@@ -210,6 +210,17 @@
         device_agent = self.core.get_device_agent(device.id)
         device_agent.update_device(device)
 
+    def update_device_pm_config(self, device_pm_config, init=False):
+        assert isinstance(device_pm_config, PmConfigs)
+
+        # we run the update through the device_agent so that the change
+        # does not loop back to the adapter unnecessarily
+        device_agent = self.core.get_device_agent(device_pm_config.id)
+        device_agent.update_device_pm_config(device_pm_config,init)
+
+    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)
 
diff --git a/voltha/core/device_agent.py b/voltha/core/device_agent.py
index 2cb56ec..ca402f6 100644
--- a/voltha/core/device_agent.py
+++ b/voltha/core/device_agent.py
@@ -37,6 +37,7 @@
         self.core = core
         self._tmp_initial_data = initial_data
         self.last_data = None
+        self.calback_data = None
 
         self.proxy = core.get_proxy('/devices/{}'.format(initial_data.id))
         self.flows_proxy = core.get_proxy(
@@ -44,6 +45,9 @@
         self.groups_proxy = core.get_proxy(
             '/devices/{}/flow_groups'.format(initial_data.id))
 
+        self.pm_config_proxy = core.get_proxy(
+            '/devices/{}/pm_configs'.format(initial_data.id))
+
         self.proxy.register_callback(
             CallbackType.PRE_UPDATE, self._validate_update)
         self.proxy.register_callback(
@@ -54,6 +58,9 @@
         self.groups_proxy.register_callback(
             CallbackType.POST_UPDATE, self._group_table_updated)
 
+        self.pm_config_proxy.register_callback(
+            CallbackType.POST_UPDATE, self._pm_config_updated)
+
         # to know device capabilities
         self.device_type = core.get_proxy(
             '/device_types/{}'.format(initial_data.type)).get()
@@ -161,6 +168,10 @@
         self.last_data = device  # so that we don't propagate back
         self.proxy.update('/', device)
 
+    def update_device_pm_config(self, device_pm_config, init=False):
+        self.callback_data = init# so that we don't push init data
+        self.pm_config_proxy.update('/', device_pm_config)
+
     def _propagate_change(self, device, dry_run=False):
         self.log.info('propagate-change', device=device, dry_run=dry_run)
         if device != self.last_data:
@@ -211,6 +222,16 @@
 
     }
 
+    ## <======================= PM CONFIG UPDATE HANDLING ====================
+
+    #@inlineCallbacks
+    def _pm_config_updated(self, pm_configs):
+        self.log.debug('pm-config-updated', pm_configs=pm_configs,
+                       callback_data=self.callback_data)
+        if not self.callback_data:
+            self.adapter_agent.update_adapter_pm_config(self.proxy.get('/'), pm_configs)
+        self.callback_data = None
+
     ## <======================= FLOW TABLE UPDATE HANDLING ====================
 
     @inlineCallbacks
diff --git a/voltha/core/local_handler.py b/voltha/core/local_handler.py
index 8466c4e..7360ca8 100644
--- a/voltha/core/local_handler.py
+++ b/voltha/core/local_handler.py
@@ -432,19 +432,36 @@
             return PmConfigs()
 
         try:
-            device = self.root.get('/devices/{}'.format(request.id))
-            log.info('device-for-pms',device=device)
-            return device.pm_configs
+            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)
+            return pm_configs
         except KeyError:
             context.set_details(
                 'Device \'{}\' not found'.format(request.id))
             context.set_code(StatusCode.NOT_FOUND)
             return PmConfigs()
 
-    #TODO: create the local PM config update function.
     @twisted_async
     def UpdateDevicePmConfigs(self, request, context):
-        raise NotImplementedError('Method not implemented!')
+        log.info('grpc-request', request=request)
+
+        if '/' in request.id:
+            context.set_details(
+                'Malformed logical device id \'{}\''.format(request.id))
+            context.set_code(StatusCode.INVALID_ARGUMENT)
+            return Empty()
+
+        try:
+            device = self.root.get('/devices/{}'.format(request.id))
+            agent = self.core.get_device_agent(request.id)
+            agent.update_device_pm_config(request)
+            return Empty()
+        except KeyError:
+            context.set_details(
+                'Device \'{}\' not found'.format(request.id))
+            context.set_code(StatusCode.NOT_FOUND)
+            return Empty()
 
     @twisted_async
     def ListDeviceFlows(self, request, context):
diff --git a/voltha/protos/device.proto b/voltha/protos/device.proto
index d13b812..47deacd 100644
--- a/voltha/protos/device.proto
+++ b/voltha/protos/device.proto
@@ -49,13 +49,14 @@
 }
 
 message PmConfigs {
-    uint32 default_freq = 1; // Default sample rate
+    string id = 1; // To work around a chameleon POST bug
+    uint32 default_freq = 2; // Default sample rate
     // Forces group names and group semantics
-    bool grouped = 2 [(access) = READ_ONLY];
+    bool grouped = 3 [(access) = READ_ONLY];
     // Allows Pm to set an individual sample frequency
-    bool freq_override = 3 [(access) = READ_ONLY];
-    repeated PmGroupConfig groups = 4; // The groups if grouped is true
-    repeated PmConfig metrics = 5; // The metrics themselves if grouped is false.
+    bool freq_override = 4 [(access) = READ_ONLY];
+    repeated PmGroupConfig groups = 5; // The groups if grouped is true
+    repeated PmConfig metrics = 6; // The metrics themselves if grouped is false.
 }
 
 message Port {
@@ -166,7 +167,7 @@
     openflow_13.FlowGroups flow_groups = 130 [(child_node) = {}];
     // PmConfigs will eventually converted to a child node of the
     // device to falicitata callbacks and to simplify manipulation.
-    PmConfigs pm_configs = 131;
+    PmConfigs pm_configs = 131 [(child_node) = {}];
 
 }
 
diff --git a/voltha/protos/voltha.proto b/voltha/protos/voltha.proto
index 89d379e..703f62b 100644
--- a/voltha/protos/voltha.proto
+++ b/voltha/protos/voltha.proto
@@ -248,7 +248,7 @@
     }
 
     // Update the pm config of a device
-    rpc UpdateDevicePmConfigs(ID) returns(PmConfigs) {
+    rpc UpdateDevicePmConfigs(voltha.PmConfigs) returns(google.protobuf.Empty) {
         option (google.api.http) = {
             post: "/api/v1/devices/{id}/pm_configs"
             body: "*"
@@ -464,7 +464,7 @@
     }
 
     // Update the pm config of a device
-    rpc UpdateDevicePmConfigs(ID) returns(PmConfigs) {
+    rpc UpdateDevicePmConfigs(voltha.PmConfigs) returns(google.protobuf.Empty) {
         option (google.api.http) = {
             post: "/api/v1/local/devices/{id}/pm_configs"
             body: "*"