diff --git a/ironic_python_agent/hardware.py b/ironic_python_agent/hardware.py
index 5de626613..65c9421a9 100644
--- a/ironic_python_agent/hardware.py
+++ b/ironic_python_agent/hardware.py
@@ -18,6 +18,7 @@ import abc
 import functools
 import os
 
+import psutil
 import six
 import stevedore
 
@@ -45,26 +46,19 @@ class HardwareType(object):
     MAC_ADDRESS = 'mac_address'
 
 
-class HardwareInfo(encoding.Serializable):
-    def __init__(self, type, id):
-        self.type = type
-        self.id = id
+class BlockDevice(encoding.Serializable):
+    def __init__(self, name, size):
+        self.name = name
+        self.size = size
 
     def serialize(self):
         return utils.get_ordereddict([
-            ('type', self.type),
-            ('id', self.id),
+            ('name', self.name),
+            ('size', self.size),
         ])
 
 
-class BlockDevice(object):
-    def __init__(self, name, size, start_sector):
-        self.name = name
-        self.size = size
-        self.start_sector = start_sector
-
-
-class NetworkInterface(object):
+class NetworkInterface(encoding.Serializable):
     def __init__(self, name, mac_addr):
         self.name = name
         self.mac_address = mac_addr
@@ -72,6 +66,38 @@ class NetworkInterface(object):
         self.switch_port_descr = None
         self.switch_chassis_descr = None
 
+    def serialize(self):
+        return utils.get_ordereddict([
+            ('name', self.name),
+            ('mac_address', self.mac_address),
+            ('switch_port_descr', self.switch_port_descr),
+            ('switch_chassis_descr', self.switch_port_descr),
+        ])
+
+
+class CPU(encoding.Serializable):
+    def __init__(self, model_name, frequency, count):
+        self.model_name = model_name
+        self.frequency = frequency
+        self.count = count
+
+    def serialize(self):
+        return utils.get_ordereddict([
+            ('model_name', self.model_name),
+            ('frequency', self.frequency),
+            ('count', self.count),
+        ])
+
+
+class Memory(encoding.Serializable):
+    def __init__(self, total):
+        self.total = total
+
+    def serialize(self):
+        return utils.get_ordereddict([
+            ('total', self.total),
+        ])
+
 
 class HardwareManager(object):
     @abc.abstractmethod
@@ -87,10 +113,11 @@ class HardwareManager(object):
         pass
 
     def list_hardware_info(self):
-        hardware_info = []
-        for interface in self.list_network_interfaces():
-            hardware_info.append(HardwareInfo(HardwareType.MAC_ADDRESS,
-                                              interface.mac_address))
+        hardware_info = {}
+        hardware_info['interfaces'] = self.list_network_interfaces()
+        hardware_info['cpu'] = self.get_cpus()
+        hardware_info['disks'] = self.list_block_devices()
+        hardware_info['memory'] = self.get_memory()
         return hardware_info
 
 
@@ -123,7 +150,26 @@ class GenericHardwareManager(HardwareManager):
                 for name in iface_names
                 if self._is_device(name)]
 
-    def _list_block_devices(self):
+    def get_cpus(self):
+        model = None
+        freq = None
+        with open('/proc/cpuinfo') as f:
+            lines = f.read()
+            for line in lines.split('\n'):
+                if model and freq:
+                    break
+                if not model and line.startswith('model name'):
+                    model = line.split(':')[1].strip()
+                if not freq and line.startswith('cpu MHz'):
+                    freq = line.split(':')[1].strip()
+
+        return CPU(model, freq, psutil.cpu_count())
+
+    def get_memory(self):
+        # psutil returns a long, force it to an int
+        return Memory(int(psutil.phymem_usage().total))
+
+    def list_block_devices(self):
         report = utils.execute('blockdev', '--report',
                                check_exit_code=[0])[0]
         lines = report.split('\n')
@@ -131,20 +177,17 @@ class GenericHardwareManager(HardwareManager):
         startsec_idx = lines[0].index('StartSec')
         device_idx = lines[0].index('Device')
         size_idx = lines[0].index('Size')
+        # If a device doesn't start at sector 0, assume it is a partition
         return [BlockDevice(line[device_idx],
-                            int(line[size_idx]),
-                            int(line[startsec_idx]))
+                            int(line[size_idx]))
                 for line
-                in lines[1:]]
+                in lines[1:] if int(line[startsec_idx]) == 0]
 
     def get_os_install_device(self):
-        # Assume anything with a start sector other than 0, is a partition
-        block_devices = [device for device in self._list_block_devices()
-                         if device.start_sector == 0]
-
         # Find the first device larger than 4GB, assume it is the OS disk
         # TODO(russellhaering): This isn't a valid assumption in all cases,
         #                       is there a more reasonable default behavior?
+        block_devices = self.list_block_devices()
         block_devices.sort(key=lambda device: device.size)
         for device in block_devices:
             if device.size >= (4 * pow(1024, 3)):
diff --git a/ironic_python_agent/ironic_api_client.py b/ironic_python_agent/ironic_api_client.py
index 52a860210..e73c31f90 100644
--- a/ironic_python_agent/ironic_api_client.py
+++ b/ironic_python_agent/ironic_api_client.py
@@ -30,7 +30,7 @@ LOG = log.getLogger(__name__)
 
 class APIClient(object):
     api_version = 'v1'
-    payload_version = '1'
+    payload_version = '2'
 
     def __init__(self, api_url):
         self.api_url = api_url.rstrip('/')
diff --git a/ironic_python_agent/tests/hardware.py b/ironic_python_agent/tests/hardware.py
index 82540199c..43caf496e 100644
--- a/ironic_python_agent/tests/hardware.py
+++ b/ironic_python_agent/tests/hardware.py
@@ -65,16 +65,100 @@ class TestGenericHardwareManager(test_base.BaseTestCase):
                                                '--report',
                                                check_exit_code=[0])
 
-    def test_list_hardwre_info(self):
+    @mock.patch('psutil.cpu_count')
+    @mock.patch(OPEN_FUNCTION_NAME)
+    def test_get_cpus(self, mocked_open, mocked_cpucount):
+        mocked_open.return_value.__enter__ = lambda s: s
+        mocked_open.return_value.__exit__ = mock.Mock()
+        read_mock = mocked_open.return_value.read
+        read_mock.return_value = (
+            'processor       : 0\n'
+            'vendor_id       : GenuineIntel\n'
+            'cpu family      : 6\n'
+            'model           : 58\n'
+            'model name      : Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz\n'
+            'stepping        : 9\n'
+            'microcode       : 0x15\n'
+            'cpu MHz         : 2594.685\n'
+            'cache size      : 6144 KB\n'
+            'fpu             : yes\n'
+            'fpu_exception   : yes\n'
+            'cpuid level     : 13\n'
+            'wp              : yes\n'
+            'flags           : fpu vme de pse tsc msr pae mce cx8 apic sep '
+            'mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss '
+            'syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts nopl '
+            'xtopology tsc_reliable nonstop_tsc aperfmperf eagerfpu pni '
+            'pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt aes xsave '
+            'avx f16c rdrand hypervisor lahf_lm ida arat epb xsaveopt pln pts '
+            'dtherm fsgsbase smep\n'
+            'bogomips        : 5189.37\n'
+            'clflush size    : 64\n'
+            'cache_alignment : 64\n'
+            'address sizes   : 40 bits physical, 48 bits virtual\n'
+            'power management:\n'
+            '\n'
+            'processor       : 1\n'
+            'vendor_id       : GenuineIntel\n'
+            'cpu family      : 6\n'
+            'model           : 58\n'
+            'model name      : Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz\n'
+            'stepping        : 9\n'
+            'microcode       : 0x15\n'
+            'cpu MHz         : 2594.685\n'
+            'cache size      : 6144 KB\n'
+            'fpu             : yes\n'
+            'fpu_exception   : yes\n'
+            'cpuid level     : 13\n'
+            'wp              : yes\n'
+            'flags           : fpu vme de pse tsc msr pae mce cx8 apic sep '
+            'mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss '
+            'syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts nopl '
+            'xtopology tsc_reliable nonstop_tsc aperfmperf eagerfpu pni '
+            'pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt aes xsave '
+            'avx f16c rdrand hypervisor lahf_lm ida arat epb xsaveopt pln pts '
+            'dtherm fsgsbase smep\n'
+            'bogomips        : 5189.37\n'
+            'clflush size    : 64\n'
+            'cache_alignment : 64\n'
+            'address sizes   : 40 bits physical, 48 bits virtual\n'
+            'power management:\n'
+        )
+
+        mocked_cpucount.return_value = 2
+
+        cpus = self.hardware.get_cpus()
+        self.assertEqual(cpus.model_name,
+                         'Intel(R) Core(TM) i7-3720QM CPU @ 2.60GHz')
+        self.assertEqual(cpus.frequency, '2594.685')
+        self.assertEqual(cpus.count, 2)
+
+    def test_list_hardware_info(self):
         self.hardware.list_network_interfaces = mock.Mock()
         self.hardware.list_network_interfaces.return_value = [
             hardware.NetworkInterface('eth0', '00:0c:29:8c:11:b1'),
             hardware.NetworkInterface('eth1', '00:0c:29:8c:11:b2'),
         ]
 
+        self.hardware.get_cpus = mock.Mock()
+        self.hardware.get_cpus.return_value = hardware.CPU(
+            'Awesome CPU x14 9001',
+            9001,
+            14)
+
+        self.hardware.get_memory = mock.Mock()
+        self.hardware.get_memory.return_value = hardware.Memory(1017012)
+
+        self.hardware.list_block_devices = mock.Mock()
+        self.hardware.list_block_devices.return_value = [
+            hardware.BlockDevice('/dev/sdj', 1073741824),
+            hardware.BlockDevice('/dev/hdaa', 65535),
+        ]
+
         hardware_info = self.hardware.list_hardware_info()
-        self.assertEqual(len(hardware_info), 2)
-        self.assertEqual(hardware_info[0].type, 'mac_address')
-        self.assertEqual(hardware_info[1].type, 'mac_address')
-        self.assertEqual(hardware_info[0].id, '00:0c:29:8c:11:b1')
-        self.assertEqual(hardware_info[1].id, '00:0c:29:8c:11:b2')
+        self.assertEqual(hardware_info['memory'], self.hardware.get_memory())
+        self.assertEqual(hardware_info['cpu'], self.hardware.get_cpus())
+        self.assertEqual(hardware_info['disks'],
+                         self.hardware.list_block_devices())
+        self.assertEqual(hardware_info['interfaces'],
+                         self.hardware.list_network_interfaces())
diff --git a/ironic_python_agent/tests/ironic_api_client.py b/ironic_python_agent/tests/ironic_api_client.py
index b0dd2b2df..eeca401e3 100644
--- a/ironic_python_agent/tests/ironic_api_client.py
+++ b/ironic_python_agent/tests/ironic_api_client.py
@@ -41,12 +41,18 @@ class TestBaseIronicPythonAgent(test_base.BaseTestCase):
     def setUp(self):
         super(TestBaseIronicPythonAgent, self).setUp()
         self.api_client = ironic_api_client.APIClient(API_URL)
-        self.hardware_info = [
-            hardware.HardwareInfo(hardware.HardwareType.MAC_ADDRESS,
-                                  'aa:bb:cc:dd:ee:ff'),
-            hardware.HardwareInfo(hardware.HardwareType.MAC_ADDRESS,
-                                  'ff:ee:dd:cc:bb:aa'),
-        ]
+        self.hardware_info = {
+            'interfaces': [
+                hardware.NetworkInterface('eth0', '00:0c:29:8c:11:b1'),
+                hardware.NetworkInterface('eth1', '00:0c:29:8c:11:b2'),
+            ],
+            'cpu': hardware.CPU('Awesome Jay CPU x10 9001', '9001', '10'),
+            'disks': [
+                hardware.BlockDevice('/dev/sdj', '9001'),
+                hardware.BlockDevice('/dev/hdj', '9002'),
+            ],
+            'memory': hardware.Memory('8675309'),
+        }
 
     def test_successful_heartbeat(self):
         expected_heartbeat_before = time.time() + 120
@@ -65,7 +71,7 @@ class TestBaseIronicPythonAgent(test_base.BaseTestCase):
         self.assertEqual(heartbeat_before, expected_heartbeat_before)
 
         heartbeat_path = 'v1/nodes/deadbeef-dabb-ad00-b105-f00d00bab10c/' \
-                       'vendor_passthru/heartbeat'
+                         'vendor_passthru/heartbeat'
         request_args = self.api_client.session.request.call_args[0]
         self.assertEqual(request_args[0], 'POST')
         self.assertEqual(request_args[1], API_URL + heartbeat_path)
@@ -162,16 +168,40 @@ class TestBaseIronicPythonAgent(test_base.BaseTestCase):
         data = self.api_client.session.request.call_args[1]['data']
         content = json.loads(data)
         self.assertEqual(content['version'], self.api_client.payload_version)
-        self.assertEqual(content['inventory'], [
-            {
-                'type': 'mac_address',
-                'id': 'aa:bb:cc:dd:ee:ff',
+        self.assertEqual(content['inventory'], {
+            u'interfaces': [
+                {
+                    u'mac_address': u'00:0c:29:8c:11:b1',
+                    u'name': u'eth0',
+                    u'switch_chassis_descr': None,
+                    u'switch_port_descr': None
+                },
+                {
+                    u'mac_address': u'00:0c:29:8c:11:b2',
+                    u'name': u'eth1',
+                    u'switch_chassis_descr': None,
+                    'switch_port_descr': None
+                }
+            ],
+            u'cpu': {
+                u'model_name': u'Awesome Jay CPU x10 9001',
+                u'frequency': u'9001',
+                u'count': u'10',
             },
-            {
-                'type': 'mac_address',
-                'id': 'ff:ee:dd:cc:bb:aa',
+            u'disks': [
+                {
+                    u'name': u'/dev/sdj',
+                    u'size': u'9001',
+                },
+                {
+                    u'name': u'/dev/hdj',
+                    u'size': u'9002',
+                },
+            ],
+            u'memory': {
+                u'total': u'8675309',
             },
-        ])
+        })
 
     def test_do_lookup_bad_response_code(self):
         response = FakeResponse(status_code=400, content={
diff --git a/requirements.txt b/requirements.txt
index 53281e62a..1b1fb88c3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,3 +9,4 @@ oslo.config>=1.2.0
 Babel>=1.3
 iso8601>=0.1.9
 oslotest==1.0
+psutil>=1.1.1