https://jira.opencord.org/browse/VOL-167
  Device Management: Retrieve Software Version on the device

  Provides a framework in voltha to retrieve the software
  details on the device.

  Addressed Review Comments

Change-Id: I2938e6e1a57f95d41cc8f548a5ebad9c952a23db
diff --git a/cli/device.py b/cli/device.py
index 690d381..c1e9ba6 100644
--- a/cli/device.py
+++ b/cli/device.py
@@ -336,3 +336,10 @@
             groups=device['flow_groups']['items']
         )
 
+    def do_images(self, line):
+        """Show software images on the device"""
+        device = self.get_device(depth=-1)
+        omit_fields = {}
+        print_pb_list_as_table('Software Images:', device.images.image,
+                               omit_fields, self.poutput, show_nulls=True)
+
diff --git a/cli/main.py b/cli/main.py
index 3f3157e..0adee54 100755
--- a/cli/main.py
+++ b/cli/main.py
@@ -174,7 +174,7 @@
             'vendor',
             'model',
             'hardware_version',
-            'software_version',
+            'images',
             'firmware_version',
             'serial_number'
         }
diff --git a/tests/itests/voltha/test_cold_activation_sequence.py b/tests/itests/voltha/test_cold_activation_sequence.py
index 64c405c..5a0d7ad 100644
--- a/tests/itests/voltha/test_cold_activation_sequence.py
+++ b/tests/itests/voltha/test_cold_activation_sequence.py
@@ -82,7 +82,11 @@
             lambda: self.get(path)['oper_status'] == 'ACTIVE',
             timeout=0.5)
         device = self.get(path)
-        self.assertNotEqual(device['software_version'], '')
+        images = device['images']
+        image = images['image']
+        image_1 = image[0]
+        version = image_1['version']
+        self.assertNotEqual(version, '')
         self.assertEqual(device['connect_status'], 'REACHABLE')
 
         ports = self.get(path + '/ports')['items']
diff --git a/tests/itests/voltha/test_voltha_retrieve_software_info.py b/tests/itests/voltha/test_voltha_retrieve_software_info.py
new file mode 100644
index 0000000..ddb5909
--- /dev/null
+++ b/tests/itests/voltha/test_voltha_retrieve_software_info.py
@@ -0,0 +1,133 @@
+from unittest import main
+from time import time, sleep
+from tests.itests.voltha.rest_base import RestBase
+from google.protobuf.json_format import MessageToDict
+from voltha.protos.device_pb2 import Device
+import simplejson, jsonschema
+
+# ~~~~~~~ Common variables ~~~~~~~
+
+IMAGES_SCHEMA = {
+    "properties": {
+        "image": {
+            "items": {
+                "properties": {
+                    "hash": {
+                        "type": "string"
+                    },
+                    "install_datetime": {
+                        "type": "string"
+                    },
+                    "is_active": {
+                        "type": "boolean"
+                    },
+                    "is_committed": {
+                        "type": "boolean"
+                    },
+                    "is_valid": {
+                        "type": "boolean"
+                    },
+                    "name": {
+                        "type": "string"
+                    },
+                    "version": {
+                        "type": "string"
+                    }
+                },
+                "type": "object"
+            },
+            "type": "array"
+        }
+    },
+    "type": "object"
+}
+
+
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+######################################################
+# Requirements for the test:                         #
+# Ensure voltha and chameleon are running fine and   #
+# chameleon is available on port 8881 to listen for  #
+# any REST requests                                  #
+######################################################
+
+
+class VolthaDeviceManagementRetrieveSoftwareInfo(RestBase):
+    # Retrieve details on the REST entry point
+    rest_endpoint = "127.0.0.1:8881"
+
+    # Construct the base_url
+    base_url = 'http://' + rest_endpoint
+
+    def wait_till(self, msg, predicate, interval=0.1, timeout=5.0):
+        deadline = time() + timeout
+        while time() < deadline:
+            if predicate():
+                return
+            sleep(interval)
+        self.fail('Timed out while waiting for condition: {}'.format(msg))
+
+    # ~~~~~~~~~~~~ Tests ~~~~~~~~~~~~
+    def test_01_voltha_device_management_retrieve_images(self):
+        # Make sure the Voltha REST interface is available
+        self.verify_rest()
+
+        # Create a new device
+        device = self.add_device()
+
+        # Activate the new device
+        self.activate_device(device['id'])
+
+        # wait till device moves to ACTIVE state
+        self.wait_till(
+            'admin state moves from ACTIVATING to ACTIVE',
+            lambda: self.get('/api/v1/devices/{}'.format(device['id']))['oper_status'] in ('ACTIVE'),
+            timeout=5.0)
+
+        # Give some time before ONUs are detected
+        sleep(2.0)
+
+        # Retrieve the images for the device
+        images = self.get_images(device['id'])
+
+        # Validate the schema for the software info
+        self.validate_images_schema(images)
+
+    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    def verify_rest(self):
+        self.get('/api/v1')
+
+    # Create a new simulated device
+    def add_device(self):
+        device = Device(
+            type='simulated_olt',
+        )
+        device = self.post('/api/v1/local/devices', MessageToDict(device),
+                           expected_code=200)
+        return device
+
+    # Active the simulated device.
+    def activate_device(self, device_id):
+        path = '/api/v1/local/devices/{}'.format(device_id)
+        self.post(path + '/enable', expected_code=200)
+        device = self.get(path)
+        self.assertEqual(device['admin_state'], 'ENABLED')
+
+    # Retrieve software info on the device
+    def get_images(self, device_id):
+        path = '/api/v1/local/devices/{}/images'.format(device_id)
+        images = self.get(path)
+        return images
+
+    def validate_images_schema(self, images):
+        try:
+            jsonschema.validate(images, IMAGES_SCHEMA)
+        except Exception as e:
+            self.assertTrue(
+                False, 'Validation failed for images: {}'.format(e.message))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index ad53b0d..fe8370a 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -30,6 +30,7 @@
 from voltha.protos.common_pb2 import OperStatus, AdminState, ConnectStatus
 from voltha.protos.events_pb2 import AlarmEventType, \
     AlarmEventSeverity, AlarmEventState, AlarmEventCategory
+from voltha.protos.device_pb2 import Image
 from voltha.protos.logical_device_pb2 import LogicalDevice
 from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_switch_features, OFPC_PORT_STATS, \
     OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
@@ -175,7 +176,9 @@
             device.model = 'TODO: Adtran PizzaBox, YUM'
             device.hardware_version = 'TODO: H/W Version'
             device.firmware_version = 'TODO: S/W Version'
-            device.software_version = 'TODO: S/W Version'
+            device.images.image.extend([
+                                         Image(version="TODO: S/W Version")
+                                       ])
             device.serial_number = 'TODO: Serial Number'
 
             device.root = True
@@ -219,11 +222,16 @@
             # Complete activation by setting up logical device for this OLT and saving
             # off the devices parent_id
 
+            # There could be multiple software version on the device,
+            # active, standby etc. Choose the active or running software
+            # below. See simulated_olt for example implementation
+            version = device.images.image[0].version
+
             ld = LogicalDevice(
                 # NOTE: not setting id and datapath_id will let the adapter agent pick id
                 desc=ofp_desc(mfr_desc=device.vendor,
                               hw_desc=device.hardware_version,
-                              sw_desc=device.software_version,
+                              sw_desc=version,
                               serial_num=device.serial_number,
                               dp_desc='n/a'),
                 switch_features=ofp_switch_features(n_buffers=256,  # TODO fake for now
diff --git a/voltha/adapters/broadcom_onu/broadcom_onu.py b/voltha/adapters/broadcom_onu/broadcom_onu.py
index 8adb0e8..5531978 100644
--- a/voltha/adapters/broadcom_onu/broadcom_onu.py
+++ b/voltha/adapters/broadcom_onu/broadcom_onu.py
@@ -32,7 +32,7 @@
 from voltha.protos.adapter_pb2 import AdapterConfig
 from voltha.protos.common_pb2 import LogLevel, OperStatus, ConnectStatus, \
     AdminState
-from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Port
+from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Port, Image
 from voltha.protos.health_pb2 import HealthStatus
 from voltha.protos.logical_device_pb2 import LogicalPort
 from voltha.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER, OFPPF_1GB_FD
@@ -244,7 +244,9 @@
         device.model = 'n/a'
         device.hardware_version = 'to be filled'
         device.firmware_version = 'to be filled'
-        device.software_version = 'to be filled'
+        device.images.image.extend([
+                                        Image(version="to be filled")
+                                       ])
         device.connect_status = ConnectStatus.REACHABLE
         self.adapter_agent.update_device(device)
 
diff --git a/voltha/adapters/dpoe_onu/dpoe_onu.py b/voltha/adapters/dpoe_onu/dpoe_onu.py
index 4bf13f2..94bceb5 100644
--- a/voltha/adapters/dpoe_onu/dpoe_onu.py
+++ b/voltha/adapters/dpoe_onu/dpoe_onu.py
@@ -37,7 +37,7 @@
 
 from voltha.adapters.interface import IAdapterInterface
 from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
-from voltha.protos.device_pb2 import Port
+from voltha.protos.device_pb2 import Port, Image
 from voltha.protos.device_pb2 import DeviceType, DeviceTypes
 from voltha.protos.health_pb2 import HealthStatus
 from voltha.protos.common_pb2 import LogLevel, ConnectStatus
@@ -131,7 +131,12 @@
         device.model = '10G EPON ONU'
         device.hardware_version = 'fa161020'
         device.firmware_version = '16.12.02'
-        device.software_version = '1.0'
+
+        # There could be multiple software versions on the device (one active, other
+        # standby etc.). Look for simulated_olt for example implementation.
+        device.images.image.extend([
+                                     Image(version="1.0")
+                                   ])
         device.serial_number = uuid4().hex
         device.connect_status = ConnectStatus.REACHABLE
         self.adapter_agent.update_device(device)
diff --git a/voltha/adapters/microsemi_olt/DeviceManager.py b/voltha/adapters/microsemi_olt/DeviceManager.py
index 3a33275..24c3e7a 100644
--- a/voltha/adapters/microsemi_olt/DeviceManager.py
+++ b/voltha/adapters/microsemi_olt/DeviceManager.py
@@ -18,7 +18,7 @@
 
 from voltha.adapters.microsemi_olt.PAS5211 import CHANNELS
 from voltha.protos.common_pb2 import ConnectStatus, OperStatus, AdminState
-from voltha.protos.device_pb2 import Device, Port
+from voltha.protos.device_pb2 import Device, Port, Image
 from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
 from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_switch_features, OFPC_FLOW_STATS, OFPC_TABLE_STATS, \
     OFPC_PORT_STATS, OFPC_GROUP_STATS, ofp_port, OFPPS_LIVE, OFPPF_10GB_FD, OFPPF_FIBER
@@ -50,7 +50,13 @@
         self.device.firmware_version = '{}.{}.{}'.format(pkt.major_firmware_version,
                                                          pkt.minor_firmware_version,
                                                          pkt.build_firmware_version)
-        self.device.software_version = '0.0.1'
+
+        # There could be multiple software version on the device,
+        # active, standby etc. Choose the active or running software
+        # below. See simulated_olt for example implementation
+        self.device.images.image.extend([
+                                          Image(version="0.0.1")
+                                        ])
         self.device.serial_number = self.device.mac_address
         self.device.oper_status = ConnectStatus.REACHABLE
         self.adapter_agent.update_device(self.device)
diff --git a/voltha/adapters/pmcs_onu/pmcs_onu.py b/voltha/adapters/pmcs_onu/pmcs_onu.py
index da5a5be..c1c8437 100644
--- a/voltha/adapters/pmcs_onu/pmcs_onu.py
+++ b/voltha/adapters/pmcs_onu/pmcs_onu.py
@@ -32,7 +32,7 @@
 from voltha.protos.adapter_pb2 import Adapter
 from voltha.protos.adapter_pb2 import AdapterConfig
 from voltha.protos.common_pb2 import LogLevel, ConnectStatus, AdminState, OperStatus
-from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Port
+from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Port, Image
 from voltha.protos.health_pb2 import HealthStatus
 from voltha.protos.logical_device_pb2 import LogicalPort
 from voltha.protos.openflow_13_pb2 import OFPPF_1GB_FD, OFPPF_FIBER, ofp_port, OFPPS_LIVE
@@ -164,7 +164,9 @@
         device.model = 'GPON ONU'
         device.hardware_version = 'tbd'
         device.firmware_version = 'tbd'
-        device.software_version = 'tbd'
+        device.images.image.extend([
+                                     Image(version="tbd")
+                                   ])
 
         device.connect_status = ConnectStatus.REACHABLE
 
@@ -696,4 +698,4 @@
 
         if OmciCreateResponse not in response:
             log.error("Failed to set gem info for {}".format(device.proxy_address))
-            return
\ No newline at end of file
+            return
diff --git a/voltha/adapters/simulated_olt/simulated_olt.py b/voltha/adapters/simulated_olt/simulated_olt.py
index f973381..0759d9c 100644
--- a/voltha/adapters/simulated_olt/simulated_olt.py
+++ b/voltha/adapters/simulated_olt/simulated_olt.py
@@ -21,6 +21,7 @@
 
 import arrow
 import structlog
+import datetime
 from klein import Klein
 from scapy.layers.l2 import Ether, EAPOL, Padding
 from twisted.internet import endpoints
@@ -36,7 +37,7 @@
 from voltha.core.logical_device_agent import mac_str_to_tuple
 from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
 from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Device, Port, \
-PmConfigs, PmConfig, PmGroupConfig
+PmConfigs, PmConfig, PmGroupConfig, Image
 from voltha.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs
 from voltha.protos.health_pb2 import HealthStatus
 from voltha.protos.common_pb2 import LogLevel, OperStatus, ConnectStatus, \
@@ -382,10 +383,27 @@
         device.model = 'n/a'
         device.hardware_version = 'n/a'
         device.firmware_version = 'n/a'
-        device.software_version = '1.0'
         device.serial_number = uuid4().hex
         device.connect_status = ConnectStatus.REACHABLE
 
+        image1 = Image(name="olt_candidate1",
+                       version="1.0",
+                       hash="",
+                       install_datetime=datetime.datetime.utcnow().isoformat(),
+                       is_active=True,
+                       is_committed=True,
+                       is_valid=True)
+
+        image2 = Image(name="olt_candidate2",
+                       version="1.0",
+                       hash="",
+                       install_datetime=datetime.datetime.utcnow().isoformat(),
+                       is_active=False,
+                       is_committed=False,
+                       is_valid=True)
+
+        device.images.image.extend([image1, image2])
+
         self.adapter_agent.update_device(device)
 
         # Now set the initial PM configuration for this device
diff --git a/voltha/adapters/simulated_onu/simulated_onu.py b/voltha/adapters/simulated_onu/simulated_onu.py
index b53fd11..4509852 100644
--- a/voltha/adapters/simulated_onu/simulated_onu.py
+++ b/voltha/adapters/simulated_onu/simulated_onu.py
@@ -20,6 +20,7 @@
 from uuid import uuid4
 
 import structlog
+import datetime
 from twisted.internet import reactor
 from twisted.internet.defer import inlineCallbacks, DeferredQueue
 from zope.interface import implementer
@@ -29,7 +30,8 @@
 from voltha.core.flow_decomposer import *
 from voltha.core.logical_device_agent import mac_str_to_tuple
 from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
-from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Device, Port
+from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Device, Port, \
+     Image
 from voltha.protos.health_pb2 import HealthStatus
 from voltha.protos.common_pb2 import LogLevel, OperStatus, ConnectStatus, \
     AdminState
@@ -128,9 +130,26 @@
         device.model = 'n/a'
         device.hardware_version = 'n/a'
         device.firmware_version = 'n/a'
-        device.software_version = '1.0'
         device.serial_number = uuid4().hex
         device.connect_status = ConnectStatus.REACHABLE
+
+        image1 = Image(name="onu_candidate1",
+                       version="1.0",
+                       hash="1234567892",
+                       install_datetime=datetime.datetime.utcnow().isoformat(),
+                       is_active=True,
+                       is_committed=True,
+                       is_valid=True)
+        image2 = Image(name="onu_candidate2",
+                       version="1.0",
+                       hash="1234567893",
+                       install_datetime=datetime.datetime.utcnow().isoformat(),
+                       is_active=False,
+                       is_committed=False,
+                       is_valid=True)
+
+        device.images.image.extend([image1, image2])
+
         self.adapter_agent.update_device(device)
 
         # then shortly after we create some ports for the device
diff --git a/voltha/adapters/tibit_olt/tibit_olt.py b/voltha/adapters/tibit_olt/tibit_olt.py
index 7e26d2c..c9e9e4e 100644
--- a/voltha/adapters/tibit_olt/tibit_olt.py
+++ b/voltha/adapters/tibit_olt/tibit_olt.py
@@ -91,7 +91,7 @@
 from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
 from voltha.protos.common_pb2 import LogLevel, ConnectStatus
 from voltha.protos.common_pb2 import OperStatus, AdminState
-from voltha.protos.device_pb2 import Device, Port
+from voltha.protos.device_pb2 import Device, Port, Image
 from voltha.protos.device_pb2 import DeviceType, DeviceTypes
 from voltha.protos.events_pb2 import KpiEvent, MetricValuePairs
 from voltha.protos.events_pb2 import KpiEventType
@@ -260,11 +260,17 @@
             log.info('create-logical-device')
             # then shortly after we create the logical device with one port
             # that will correspond to the NNI port
+
+            # There could be multiple software version on the device,
+            # active, standby etc. Choose the active or running software
+            # below. See simulated_olt for example implementation
+            version = device.images.image[0].version
+
             ld = LogicalDevice(
                 desc=ofp_desc(
                     mfr_desc=device.vendor,
                     hw_desc=device.hardware_version,
-                    sw_desc=device.software_version,
+                    sw_desc=version,
                     serial_num=uuid4().hex,
                     dp_desc='n/a'
                 ),
@@ -616,11 +622,16 @@
         if manufacturer[rc]:
             manu_value = manufacturer.pop()
             device.firmware_version = re.search('\Firmware: (.+?) ', manu_value).group(1)
-            device.software_version = re.search('\Build: (.+?) ', manu_value).group(1)
+            # There could be multiple software versions on the device (one active, other
+            # standby etc.). Look for simulated_olt for example implementation.
+            image_1 = Image(version = \
+                                  re.search('\Build: (.+?) ', manu_value).group(1))
+            device.images.image.extend([ image_1 ])
             device.serial_number = re.search('\Serial #: (.+?) ', manu_value).group(1)
         else:
             device.firmware_version = "UNKNOWN"
-            device.software_version = "UNKNOWN"
+            image_1 = Image(version="UNKNOWN")
+            device.images.image.extend([ image_1 ])
             device.serial_number = "UNKNOWN"
         device.root = True
         device.connect_status = ConnectStatus.REACHABLE
diff --git a/voltha/adapters/tibit_onu/tibit_onu.py b/voltha/adapters/tibit_onu/tibit_onu.py
index 8b5f754..62148e1 100644
--- a/voltha/adapters/tibit_onu/tibit_onu.py
+++ b/voltha/adapters/tibit_onu/tibit_onu.py
@@ -40,7 +40,7 @@
 from common.frameio.frameio import BpfProgramFilter, hexify
 from voltha.adapters.interface import IAdapterInterface
 from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
-from voltha.protos.device_pb2 import Port
+from voltha.protos.device_pb2 import Port, Image
 from voltha.protos.device_pb2 import DeviceType, DeviceTypes
 from voltha.protos.events_pb2 import KpiEventType
 from voltha.protos.events_pb2 import MetricValuePairs, KpiEvent
@@ -802,11 +802,14 @@
         if manufacturer[rc]:
             manu_value = manufacturer.pop()
             device.firmware_version = re.search('\Firmware: (.+?) ', manu_value).group(1)
-            device.software_version = re.search('\Build: (.+?) ', manu_value).group(1)
+            image_1 = Image(version = \
+                                    re.search('\Build: (.+?) ', manu_value).group(1))
+            device.images.image.extend([ image_1 ])
             device.serial_number = re.search('\Serial #: (.+?) ', manu_value).group(1)
         else:
             device.firmware_version = "UNKNOWN"
-            device.software_version = "UNKNOWN"
+            image_1 = Image(version="UNKNOWN")
+            device.images.image.extend([ image_1 ])
             device.serial_number = "UNKNOWN"
 
         device.connect_status = ConnectStatus.REACHABLE
diff --git a/voltha/core/global_handler.py b/voltha/core/global_handler.py
index 7df3f29..63e2c60 100644
--- a/voltha/core/global_handler.py
+++ b/voltha/core/global_handler.py
@@ -19,7 +19,7 @@
 
 from common.utils.grpc_utils import twisted_async
 from voltha.core.config.config_root import ConfigRoot
-from voltha.protos.device_pb2 import PmConfigs
+from voltha.protos.device_pb2 import PmConfigs, Images
 from voltha.protos.voltha_pb2 import \
     add_VolthaGlobalServiceServicer_to_server, VolthaLocalServiceStub, \
     VolthaGlobalServiceServicer, Voltha, VolthaInstances, VolthaInstance, \
@@ -557,3 +557,22 @@
             'ListAlarmFilters',
             Empty(),
             context)
+
+    @twisted_async
+    def GetImages(self, request, context):
+        log.info('grpc-request', request=request)
+
+        try:
+            instance_id = self.dispatcher.instance_id_by_device_id(request.id)
+        except KeyError:
+            context.set_details(
+                'Device \'{}\' not found'.format(request.id))
+            context.set_code(StatusCode.NOT_FOUND)
+            return Images()
+
+        return self.dispatcher.dispatch(
+            instance_id,
+            VolthaLocalServiceStub,
+            'GetImages',
+            request,
+            context)
diff --git a/voltha/core/local_handler.py b/voltha/core/local_handler.py
index 824bc85..60a519f 100644
--- a/voltha/core/local_handler.py
+++ b/voltha/core/local_handler.py
@@ -30,7 +30,7 @@
     LogicalPorts, Devices, Device, DeviceType, \
     DeviceTypes, DeviceGroups, DeviceGroup, AdminState, OperStatus, ChangeEvent, \
     AlarmFilter, AlarmFilters
-from voltha.protos.device_pb2 import PmConfigs
+from voltha.protos.device_pb2 import PmConfigs, Images
 from voltha.registry import registry
 
 log = structlog.get_logger()
@@ -688,3 +688,23 @@
                 'Alarm filter \'{}\' not found'.format(request.id))
             context.set_code(StatusCode.NOT_FOUND)
             return AlarmFilter()
+
+    @twisted_async
+    def GetImages(self, request, context):
+        log.info('grpc-request', request=request)
+
+        if '/' in request.id:
+            context.set_details(
+                'Malformed device id \'{}\''.format(request.id))
+            context.set_code(StatusCode.INVALID_ARGUMENT)
+            return Images()
+
+        try:
+            device = self.root.get('/devices/' + request.id)
+            return device.images
+
+        except KeyError:
+            context.set_details(
+                'Device \'{}\' not found'.format(request.id))
+            context.set_code(StatusCode.NOT_FOUND)
+            return Images()
diff --git a/voltha/protos/device.proto b/voltha/protos/device.proto
index a4174f5..b71063b 100644
--- a/voltha/protos/device.proto
+++ b/voltha/protos/device.proto
@@ -59,6 +59,37 @@
     repeated PmConfig metrics = 6; // The metrics themselves if grouped is false.
 }
 
+// Describes instance of software image on the device
+message Image {
+    string name = 1;                // software patch name
+    string version = 2;             // version of software
+    string hash = 3;                // md5 hash
+    string install_datetime = 4;    // combined date and time expressed in UTC.
+                                    // use ISO 8601 format for date and time
+
+    // The active software image is one that is currently loaded and executing
+    // in the ONU or circuit pack. Under normal operation, one software image
+    // is always active while the other is inactive. Under no circumstances are
+    // both software images allowed to be active at the same time
+    bool is_active = 5;             // True if the image is active
+
+    // The committed software image is loaded and executed upon reboot of the
+    // ONU and/or circuit pack. During normal operation, one software image is
+    // always committed, while the other is uncommitted.
+    bool is_committed = 6;          // True if the image is committed
+
+    // A software image is valid if it has been verified to be an executable
+    // code image. The verification mechanism is not subject to standardization;
+    // however, it should include at least a data integrity (e.g., CRC) check of
+    // the entire code image.
+    bool is_valid = 7;              // True if the image is valid
+}
+
+// List of software on the device
+message Images {
+    repeated Image image = 1;
+}
+
 message Port {
     option (voltha.yang_child_rule) = MOVE_TO_PARENT_LEVEL;
 
@@ -118,7 +149,8 @@
     string model = 6 [(access) = READ_ONLY];
     string hardware_version = 7 [(access) = READ_ONLY];
     string firmware_version = 8 [(access) = READ_ONLY];
-    string software_version = 9 [(access) = READ_ONLY];
+    // List of software on the device
+    Images images = 9 [(access) = READ_ONLY];
     string serial_number = 10 [(access) = READ_ONLY];
 
     // Addapter that takes care of device
diff --git a/voltha/protos/voltha.proto b/voltha/protos/voltha.proto
index 8dcdba1..3315d94 100644
--- a/voltha/protos/voltha.proto
+++ b/voltha/protos/voltha.proto
@@ -364,6 +364,12 @@
             get: "/api/v1/alarm_filters"
         };
     }
+
+    rpc GetImages(ID) returns(Images) {
+        option (google.api.http) = {
+            get: "/api/v1/devices/{id}/images"
+        };
+    }
 }
 
 /*
@@ -628,4 +634,10 @@
             get: "/api/v1/local/alarm_filters"
         };
     }
+
+    rpc GetImages(ID) returns(Images) {
+        option (google.api.http) = {
+            get: "/api/v1/local/devices/{id}/images"
+        };
+    }
 }