Implement version 2 payload with more hw info

Bumping the payload version to two, and adding CPU, Memory, and Block
Device information to the inventory.

Added dependency on psutil for available memory and cpu_count.
Requirements line copied from global requirements

Change-Id: Ia39c85c91b1d60468667787a7978020084dc6c2a
This commit is contained in:
Jay Faulkner 2014-04-09 17:09:51 -07:00
parent bbe50749fc
commit 1384d79d2c
5 changed files with 206 additions and 48 deletions

@ -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)):

@ -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('/')

@ -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())

@ -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={

@ -9,3 +9,4 @@ oslo.config>=1.2.0
Babel>=1.3
iso8601>=0.1.9
oslotest==1.0
psutil>=1.1.1