Merge "NUMA-topology collector"
This commit is contained in:
commit
db76c899b5
@ -293,6 +293,12 @@ class ISCSIError(RESTError):
|
|||||||
super(ISCSIError, self).__init__(details)
|
super(ISCSIError, self).__init__(details)
|
||||||
|
|
||||||
|
|
||||||
|
class IncompatibleNumaFormatError(RESTError):
|
||||||
|
"""Error raised when unexpected format data in NUMA node."""
|
||||||
|
|
||||||
|
message = 'Error in NUMA node data format'
|
||||||
|
|
||||||
|
|
||||||
class ISCSICommandError(ISCSIError):
|
class ISCSICommandError(ISCSIError):
|
||||||
"""Error executing TGT command."""
|
"""Error executing TGT command."""
|
||||||
|
|
||||||
|
255
ironic_python_agent/numa_inspector.py
Normal file
255
ironic_python_agent/numa_inspector.py
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
# Copyright 2017 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
|
import pint
|
||||||
|
|
||||||
|
from ironic_python_agent import errors
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
UNIT_CONVERTER = pint.UnitRegistry(filename=None)
|
||||||
|
UNIT_CONVERTER.define('kB = []')
|
||||||
|
UNIT_CONVERTER.define('KB = []')
|
||||||
|
UNIT_CONVERTER.define('MB = 1024 KB')
|
||||||
|
UNIT_CONVERTER.define('GB = 1048576 KB')
|
||||||
|
|
||||||
|
|
||||||
|
def get_numa_node_id(numa_node_dir):
|
||||||
|
"""Provides the NUMA node id from NUMA node directory
|
||||||
|
|
||||||
|
:param numa_node_dir: NUMA node directory
|
||||||
|
:raises: IncompatibleNumaFormatError: when unexpected format data
|
||||||
|
in NUMA node dir
|
||||||
|
|
||||||
|
:return: NUMA node id
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return int(os.path.basename(numa_node_dir)[4:])
|
||||||
|
except (IOError, ValueError, IndexError) as exc:
|
||||||
|
msg = ('Failed to get NUMA node id for %(node)s: '
|
||||||
|
'%(error)s' % {'node': numa_node_dir, 'error': exc})
|
||||||
|
raise errors.IncompatibleNumaFormatError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def get_nodes_memory_info(numa_node_dirs):
|
||||||
|
"""Collect the NUMA nodes memory information.
|
||||||
|
|
||||||
|
"ram": [{"numa_node": <numa_node_id>, "size_kb": <memory_in_kb>}, ...]
|
||||||
|
|
||||||
|
:param numa_node_dirs: A list of NUMA node directories
|
||||||
|
:raises: IncompatibleNumaFormatError: when unexpected format data
|
||||||
|
in NUMA node
|
||||||
|
|
||||||
|
:return: A list of memory information with NUMA node id
|
||||||
|
"""
|
||||||
|
ram = []
|
||||||
|
for numa_node_dir in numa_node_dirs:
|
||||||
|
numa_node_memory = {}
|
||||||
|
numa_node_id = get_numa_node_id(numa_node_dir)
|
||||||
|
try:
|
||||||
|
with open(os.path.join(numa_node_dir,
|
||||||
|
'meminfo')) as meminfo_file:
|
||||||
|
for line in meminfo_file:
|
||||||
|
if 'MemTotal' in line:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
msg = ('Memory information is not available for '
|
||||||
|
'%(node)s' % {'node': numa_node_dir})
|
||||||
|
raise errors.IncompatibleNumaFormatError(msg)
|
||||||
|
except IOError as exc:
|
||||||
|
msg = ('Failed to get memory information '
|
||||||
|
'for %(node)s: %(error)s' %
|
||||||
|
{'node': numa_node_dir, 'error': exc})
|
||||||
|
raise errors.IncompatibleNumaFormatError(msg)
|
||||||
|
try:
|
||||||
|
# To get memory size with unit from memory info line
|
||||||
|
# Memory info sample line format 'Node 0 MemTotal: 1560000 kB'
|
||||||
|
value = line.split(":")[1].strip()
|
||||||
|
memory_kb = int(UNIT_CONVERTER(value).to_base_units())
|
||||||
|
except (ValueError, IndexError, pint.errors.UndefinedUnitError) as exc:
|
||||||
|
msg = ('Failed to get memory information for %(node)s: '
|
||||||
|
'%(error)s' % {'node': numa_node_dir, 'error': exc})
|
||||||
|
raise errors.IncompatibleNumaFormatError(msg)
|
||||||
|
numa_node_memory['numa_node'] = numa_node_id
|
||||||
|
numa_node_memory['size_kb'] = memory_kb
|
||||||
|
LOG.debug('Found memory available %d KB in NUMA node %d',
|
||||||
|
memory_kb, numa_node_id)
|
||||||
|
ram.append(numa_node_memory)
|
||||||
|
return ram
|
||||||
|
|
||||||
|
|
||||||
|
def get_nodes_cores_info(numa_node_dirs):
|
||||||
|
"""Collect the NUMA nodes cpu's and thread's information.
|
||||||
|
|
||||||
|
"cpus": [
|
||||||
|
{
|
||||||
|
"cpu": <cpu_id>, "numa_node": <numa_node_id>,
|
||||||
|
"thread_siblings": [<list of sibling threads>]
|
||||||
|
},
|
||||||
|
...,
|
||||||
|
]
|
||||||
|
NUMA nodes path: /sys/devices/system/node/node<node_id>
|
||||||
|
|
||||||
|
Thread dirs path: /sys/devices/system/node/node<node_id>/cpu<thread_id>
|
||||||
|
|
||||||
|
CPU id file path: /sys/devices/system/node/node<node_id>/cpu<thread_id>/
|
||||||
|
topology/core_id
|
||||||
|
|
||||||
|
:param numa_node_dirs: A list of NUMA node directories
|
||||||
|
:raises: IncompatibleNumaFormatError: when unexpected format data
|
||||||
|
in NUMA node
|
||||||
|
|
||||||
|
:return: A list of cpu information with NUMA node id and thread siblings
|
||||||
|
"""
|
||||||
|
dict_cpus = {}
|
||||||
|
for numa_node_dir in numa_node_dirs:
|
||||||
|
numa_node_id = get_numa_node_id(numa_node_dir)
|
||||||
|
try:
|
||||||
|
thread_dirs = os.listdir(numa_node_dir)
|
||||||
|
except OSError as exc:
|
||||||
|
msg = ('Failed to get list of threads for %(node)s: '
|
||||||
|
'%(error)s' % {'node': numa_node_dir, 'error': exc})
|
||||||
|
raise errors.IncompatibleNumaFormatError(msg)
|
||||||
|
for thread_dir in thread_dirs:
|
||||||
|
if (not os.path.isdir(os.path.join(numa_node_dir, thread_dir))
|
||||||
|
or not thread_dir.startswith("cpu")):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
thread_id = int(thread_dir[3:])
|
||||||
|
except (ValueError, IndexError) as exc:
|
||||||
|
msg = ('Failed to get cores information for '
|
||||||
|
'%(node)s: %(error)s' %
|
||||||
|
{'node': numa_node_dir, 'error': exc})
|
||||||
|
raise errors.IncompatibleNumaFormatError(msg)
|
||||||
|
try:
|
||||||
|
with open(os.path.join(numa_node_dir, thread_dir, 'topology',
|
||||||
|
'core_id')) as core_id_file:
|
||||||
|
cpu_id = int(core_id_file.read().strip())
|
||||||
|
except (IOError, ValueError) as exc:
|
||||||
|
msg = ('Failed to gather cpu_id for thread'
|
||||||
|
'%(thread)s NUMA node %(node)s: %(error)s' %
|
||||||
|
{'thread': thread_dir, 'node': numa_node_dir,
|
||||||
|
'error': exc})
|
||||||
|
raise errors.IncompatibleNumaFormatError(msg)
|
||||||
|
# CPU and NUMA node together forms a unique value, as cpu_id is
|
||||||
|
# specific to a NUMA node
|
||||||
|
# NUMA node id and cpu id tuple is used for unique key
|
||||||
|
dict_key = numa_node_id, cpu_id
|
||||||
|
if dict_key in dict_cpus:
|
||||||
|
if thread_id not in dict_cpus[dict_key]['thread_siblings']:
|
||||||
|
dict_cpus[dict_key]['thread_siblings'].append(thread_id)
|
||||||
|
else:
|
||||||
|
cpu_item = {}
|
||||||
|
cpu_item['thread_siblings'] = [thread_id]
|
||||||
|
cpu_item['cpu'] = cpu_id
|
||||||
|
cpu_item['numa_node'] = numa_node_id
|
||||||
|
dict_cpus[dict_key] = cpu_item
|
||||||
|
LOG.debug('Found a thread sibling %d for CPU %d in NUMA node %d',
|
||||||
|
thread_id, cpu_id, numa_node_id)
|
||||||
|
return list(dict_cpus.values())
|
||||||
|
|
||||||
|
|
||||||
|
def get_nodes_nics_info(nic_device_path):
|
||||||
|
"""Collect the NUMA nodes nics information.
|
||||||
|
|
||||||
|
"nics": [
|
||||||
|
{"name": "<network interface name>", "numa_node": <numa_node_id>},
|
||||||
|
...,
|
||||||
|
]
|
||||||
|
|
||||||
|
:param nic_device_path: nic device directory path
|
||||||
|
:raises: IncompatibleNumaFormatError: when unexpected format data
|
||||||
|
in NUMA node
|
||||||
|
|
||||||
|
:return: A list of nics information with NUMA node id
|
||||||
|
"""
|
||||||
|
nics = []
|
||||||
|
if not os.path.isdir(nic_device_path):
|
||||||
|
msg = ('Failed to get list of NIC\'s, NIC device path '
|
||||||
|
'does not exist: %(nic_device_path)s' %
|
||||||
|
{'nic_device_path': nic_device_path})
|
||||||
|
raise errors.IncompatibleNumaFormatError(msg)
|
||||||
|
for nic_dir in os.listdir(nic_device_path):
|
||||||
|
if not os.path.isdir(os.path.join(nic_device_path, nic_dir, 'device')):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
with open(os.path.join(nic_device_path, nic_dir, 'device',
|
||||||
|
'numa_node')) as nicsinfo_file:
|
||||||
|
numa_node_id = int(nicsinfo_file.read().strip())
|
||||||
|
except (IOError, ValueError) as exc:
|
||||||
|
msg = ('Failed to gather NIC\'s for NUMA node %(node)s: '
|
||||||
|
'%(error)s' % {'node': nic_dir, 'error': exc})
|
||||||
|
raise errors.IncompatibleNumaFormatError(msg)
|
||||||
|
numa_node_nics = {}
|
||||||
|
numa_node_nics['name'] = nic_dir
|
||||||
|
numa_node_nics['numa_node'] = numa_node_id
|
||||||
|
LOG.debug('Found a NIC %s in NUMA node %d', nic_dir,
|
||||||
|
numa_node_id)
|
||||||
|
nics.append(numa_node_nics)
|
||||||
|
return nics
|
||||||
|
|
||||||
|
|
||||||
|
def collect_numa_topology_info(data, failures):
|
||||||
|
"""Collect the NUMA topology information.
|
||||||
|
|
||||||
|
{
|
||||||
|
"numa_topology": {
|
||||||
|
"ram": [{"numa_node": <numa_node_id>, "size_kb": <memory_in_kb>}, ...],
|
||||||
|
"cpus": [
|
||||||
|
{
|
||||||
|
"cpu": <cpu_id>, "numa_node": <numa_node_id>,
|
||||||
|
"thread_siblings": [<list of sibling threads>]
|
||||||
|
},
|
||||||
|
...,
|
||||||
|
],
|
||||||
|
"nics": [
|
||||||
|
{"name": "<network interface name>", "numa_node": <numa_node_id>},
|
||||||
|
...,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The data is gathered from /sys/devices/system/node/node<X> and
|
||||||
|
/sys/class/net/ directories.
|
||||||
|
|
||||||
|
:param data: mutable data that we'll send to inspector
|
||||||
|
:param failures: AccumulatedFailures object
|
||||||
|
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
numa_node_path = '/sys/devices/system/node/'
|
||||||
|
nic_device_path = '/sys/class/net/'
|
||||||
|
numa_info = {}
|
||||||
|
numa_node_dirs = []
|
||||||
|
if not os.path.isdir(numa_node_path):
|
||||||
|
LOG.warning('Failed to get list of NUMA nodes, NUMA node path '
|
||||||
|
'does not exist: %s', numa_node_path)
|
||||||
|
return
|
||||||
|
for numa_node_dir in os.listdir(numa_node_path):
|
||||||
|
numa_node_dir_path = os.path.join(numa_node_path, numa_node_dir)
|
||||||
|
if (os.path.isdir(numa_node_dir_path)
|
||||||
|
and numa_node_dir.startswith("node")):
|
||||||
|
numa_node_dirs.append(numa_node_dir_path)
|
||||||
|
try:
|
||||||
|
numa_info['ram'] = get_nodes_memory_info(numa_node_dirs)
|
||||||
|
numa_info['cpus'] = get_nodes_cores_info(numa_node_dirs)
|
||||||
|
numa_info['nics'] = get_nodes_nics_info(nic_device_path)
|
||||||
|
except errors.IncompatibleNumaFormatError as exc:
|
||||||
|
LOG.warning('Failed to get some NUMA information (%s)', exc)
|
||||||
|
return
|
||||||
|
data['numa_topology'] = numa_info
|
350
ironic_python_agent/tests/unit/test_numa_inspector.py
Normal file
350
ironic_python_agent/tests/unit/test_numa_inspector.py
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
# Copyright 2017 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslotest import base as test_base
|
||||||
|
|
||||||
|
from ironic_python_agent import errors
|
||||||
|
from ironic_python_agent import numa_inspector as numa_insp
|
||||||
|
from ironic_python_agent import utils
|
||||||
|
|
||||||
|
|
||||||
|
class TestCollectNumaTopologyInfo(test_base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCollectNumaTopologyInfo, self).setUp()
|
||||||
|
self.data = {}
|
||||||
|
self.failures = utils.AccumulatedFailures()
|
||||||
|
|
||||||
|
@mock.patch.object(numa_insp, 'get_nodes_nics_info', autospec=True)
|
||||||
|
@mock.patch.object(numa_insp, 'get_nodes_cores_info', autospec=True)
|
||||||
|
@mock.patch.object(numa_insp, 'get_nodes_memory_info', autospec=True)
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
@mock.patch.object(os, 'listdir', autospec=True)
|
||||||
|
def test_collect_success(self, mock_listdir, mock_isdir, mock_memory_info,
|
||||||
|
mock_cores_info, mock_nics_info):
|
||||||
|
numa_node_dirs = ['node0', 'node1']
|
||||||
|
mock_listdir.return_value = numa_node_dirs
|
||||||
|
mock_isdir.return_value = True
|
||||||
|
mock_memory_info.return_value = [{'numa_node': 0, 'size_kb': 1560000},
|
||||||
|
{'numa_node': 1, 'size_kb': 1200000}]
|
||||||
|
mock_cores_info.return_value = [{'cpu': 0, 'numa_node': 0,
|
||||||
|
'thread_siblings': [0, 1, 2, 3]},
|
||||||
|
{'cpu': 1, 'numa_node': 0,
|
||||||
|
'thread_siblings': [4, 5, 6]},
|
||||||
|
{'cpu': 0, 'numa_node': 1,
|
||||||
|
'thread_siblings': [16, 17]},
|
||||||
|
{'cpu': 1, 'numa_node': 1,
|
||||||
|
'thread_siblings': [18, 19]}]
|
||||||
|
mock_nics_info.return_value = [{'name': 'enp0s01', 'numa_node': 0},
|
||||||
|
{'name': 'enp0s02', 'numa_node': 1}]
|
||||||
|
expected_numa_info = {"ram": [{'numa_node': 0, 'size_kb': 1560000},
|
||||||
|
{'numa_node': 1, 'size_kb': 1200000}],
|
||||||
|
"cpus": [{'cpu': 0, 'numa_node': 0,
|
||||||
|
'thread_siblings': [0, 1, 2, 3]},
|
||||||
|
{'cpu': 1, 'numa_node': 0,
|
||||||
|
'thread_siblings': [4, 5, 6]},
|
||||||
|
{'cpu': 0, 'numa_node': 1,
|
||||||
|
'thread_siblings': [16, 17]},
|
||||||
|
{'cpu': 1, 'numa_node': 1,
|
||||||
|
'thread_siblings': [18, 19]}],
|
||||||
|
"nics": [{'name': 'enp0s01', 'numa_node': 0},
|
||||||
|
{'name': 'enp0s02', 'numa_node': 1}]}
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
numa_insp.collect_numa_topology_info(self.data, self.failures)
|
||||||
|
self.assertEqual(expected_numa_info, self.data["numa_topology"])
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
def test_collect_no_numa_dirs(self, mock_isdir):
|
||||||
|
mock_isdir.return_value = False
|
||||||
|
numa_insp.collect_numa_topology_info(self.data, self.failures)
|
||||||
|
self.assertNotIn("numa_topology", self.data)
|
||||||
|
|
||||||
|
@mock.patch.object(numa_insp, 'get_nodes_nics_info', autospec=True)
|
||||||
|
@mock.patch.object(numa_insp, 'get_nodes_cores_info', autospec=True)
|
||||||
|
@mock.patch.object(numa_insp, 'get_nodes_memory_info', autospec=True)
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
@mock.patch.object(os, 'listdir', autospec=True)
|
||||||
|
def test_collect_no_nics_dirs(self, mock_listdir, mock_isdir,
|
||||||
|
mock_memory_info, mock_cores_info,
|
||||||
|
mock_nics_info):
|
||||||
|
numa_node_dirs = ['node0', 'node1']
|
||||||
|
mock_listdir.return_value = numa_node_dirs
|
||||||
|
mock_isdir.return_value = True
|
||||||
|
mock_memory_info.return_value = [{'numa_node': 0, 'size_kb': 1560000},
|
||||||
|
{'numa_node': 1, 'size_kb': 1200000}]
|
||||||
|
mock_cores_info.return_value = [{'cpu': 0, 'numa_node': 0,
|
||||||
|
'thread_siblings': [0, 1, 2, 3]},
|
||||||
|
{'cpu': 1, 'numa_node': 1,
|
||||||
|
'thread_siblings': [4, 5, 6]}]
|
||||||
|
mock_nics_info.side_effect = errors.IncompatibleNumaFormatError("")
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
numa_insp.collect_numa_topology_info(self.data, self.failures)
|
||||||
|
self.assertNotIn("numa_topology", self.data)
|
||||||
|
|
||||||
|
@mock.patch.object(numa_insp, 'get_nodes_nics_info', autospec=True)
|
||||||
|
@mock.patch.object(numa_insp, 'get_nodes_cores_info', autospec=True)
|
||||||
|
@mock.patch.object(numa_insp, 'get_nodes_memory_info', autospec=True)
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
@mock.patch.object(os, 'listdir', autospec=True)
|
||||||
|
def test_collect_failure(self, mock_listdir, mock_isdir, mock_memory_info,
|
||||||
|
mock_cores_info, mock_nics_info):
|
||||||
|
numa_node_dirs = ['node0', 'node1']
|
||||||
|
mock_listdir.return_value = numa_node_dirs
|
||||||
|
mock_isdir.return_value = True
|
||||||
|
mock_memory_info.side_effect = errors.IncompatibleNumaFormatError("")
|
||||||
|
mock_cores_info.side_effect = errors.IncompatibleNumaFormatError("")
|
||||||
|
mock_nics_info.side_effect = errors.IncompatibleNumaFormatError("")
|
||||||
|
numa_insp.collect_numa_topology_info(self.data, self.failures)
|
||||||
|
self.assertNotIn("numa_topology", self.data)
|
||||||
|
self.assertFalse(self.failures)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetNumaTopologyInfo(test_base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestGetNumaTopologyInfo, self).setUp()
|
||||||
|
self.data = {}
|
||||||
|
self.failures = utils.AccumulatedFailures()
|
||||||
|
|
||||||
|
def test_get_numa_node_id_valid_format(self):
|
||||||
|
numa_node_dir = '/sys/devices/system/node/node0'
|
||||||
|
expected_numa_node_id = 0
|
||||||
|
numa_node_id = numa_insp.get_numa_node_id(numa_node_dir)
|
||||||
|
self.assertEqual(expected_numa_node_id, numa_node_id)
|
||||||
|
|
||||||
|
def test_get_numa_node_id_invalid_format(self):
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_numa_node_id,
|
||||||
|
'/sys/devices/system/node/node-*0')
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_numa_node_id,
|
||||||
|
'/sys/devices/system/node/nod')
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_numa_node_id,
|
||||||
|
'')
|
||||||
|
|
||||||
|
@mock.patch.object(numa_insp, 'get_numa_node_id', autospec=True)
|
||||||
|
def test_get_nodes_memory_info(self, mock_node_id):
|
||||||
|
numa_node_dirs = ['/sys/devices/system/node/node0',
|
||||||
|
'/sys/devices/system/node/node1']
|
||||||
|
mock_node_id.side_effect = [0, 1]
|
||||||
|
reads = [['Node 0 Ignored Line',
|
||||||
|
'Node 0 MemTotal: 1560000 kB'],
|
||||||
|
['Node 1 MemTotal: 1200000 kB',
|
||||||
|
'Node 1 Ignored Line']]
|
||||||
|
expected_meminfo = [{'numa_node': 0, 'size_kb': 1560000},
|
||||||
|
{'numa_node': 1, 'size_kb': 1200000}]
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
mock_meminfo_file = mock.MagicMock()
|
||||||
|
mock_meminfo_file.__enter__.side_effect = reads
|
||||||
|
mock_open.return_value = mock_meminfo_file
|
||||||
|
ram = numa_insp.get_nodes_memory_info(numa_node_dirs)
|
||||||
|
self.assertListEqual(expected_meminfo, ram)
|
||||||
|
|
||||||
|
@mock.patch.object(numa_insp, 'get_numa_node_id', autospec=True)
|
||||||
|
def test_bad_nodes_memory_info(self, mock_node_id):
|
||||||
|
numa_node_dirs = ['/sys/devices/system/node/node0',
|
||||||
|
'/sys/devices/system/node/node1']
|
||||||
|
mock_node_id.side_effect = [0, 1]
|
||||||
|
reads = [['Node 0 MemTotal: 1560000 kB'], IOError]
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
mock_meminfo_file = mock.MagicMock()
|
||||||
|
mock_meminfo_file.__enter__.side_effect = reads
|
||||||
|
mock_open.return_value = mock_meminfo_file
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_nodes_memory_info,
|
||||||
|
numa_node_dirs)
|
||||||
|
|
||||||
|
@mock.patch.object(numa_insp, 'get_numa_node_id', autospec=True)
|
||||||
|
def test_nodes_invalid_numa_format_memory_info(self, mock_node_id):
|
||||||
|
numa_node_dirs = ['/sys/devices/system/node/node0',
|
||||||
|
'/sys/devices/system/node/node1']
|
||||||
|
mock_node_id.side_effect = [0, 1]
|
||||||
|
reads = [['Node 0: MemTotal: 1560000 kB'],
|
||||||
|
['Node 1 MemTotal: 1200000 kB']]
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
mock_meminfo_file = mock.MagicMock()
|
||||||
|
mock_meminfo_file.__enter__.side_effect = reads
|
||||||
|
mock_open.return_value = mock_meminfo_file
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_nodes_memory_info,
|
||||||
|
numa_node_dirs)
|
||||||
|
|
||||||
|
@mock.patch.object(numa_insp, 'get_numa_node_id', autospec=True)
|
||||||
|
def test_nodes_invalid_memory_unit(self, mock_node_id):
|
||||||
|
numa_node_dirs = ['/sys/devices/system/node/node0',
|
||||||
|
'/sys/devices/system/node/node1']
|
||||||
|
mock_node_id.side_effect = [0, 1]
|
||||||
|
reads = [['Node 0 MemTotal: 1560000 TB'],
|
||||||
|
['Node 1 MemTotal: 1200000 kB']]
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
mock_meminfo_file = mock.MagicMock()
|
||||||
|
mock_meminfo_file.__enter__.side_effect = reads
|
||||||
|
mock_open.return_value = mock_meminfo_file
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_nodes_memory_info,
|
||||||
|
numa_node_dirs)
|
||||||
|
|
||||||
|
@mock.patch.object(numa_insp, 'get_numa_node_id', autospec=True)
|
||||||
|
def test_get_numa_node_id_invalid_format_memory_info(self,
|
||||||
|
mock_node_id):
|
||||||
|
numa_node_dirs = ['/sys/devices/system/node/node-*0',
|
||||||
|
'/sys/devices/system/node/node1']
|
||||||
|
mock_node_id.side_effect = errors.IncompatibleNumaFormatError
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_nodes_memory_info,
|
||||||
|
numa_node_dirs)
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
@mock.patch.object(os, 'listdir', autospec=True)
|
||||||
|
@mock.patch.object(numa_insp, 'get_numa_node_id', autospec=True)
|
||||||
|
def test_get_nodes_cores_info(self, mock_node_id,
|
||||||
|
mock_listdir, mock_isdir):
|
||||||
|
numa_node_dirs = ['/sys/devices/system/node/node0',
|
||||||
|
'/sys/devices/system/node/node1']
|
||||||
|
mock_node_id.side_effect = [0, 1]
|
||||||
|
mock_listdir.side_effect = [['cpu0', 'cpu1', 'cpu2',
|
||||||
|
'cpu3', 'cpu4'],
|
||||||
|
['cpu5', 'cpu6', 'cpu7']]
|
||||||
|
mock_isdir.return_value = True
|
||||||
|
reads = ['0', '0', '1', '1', '1', '0', '0', '0']
|
||||||
|
expected_cores_info = [{'cpu': 0, 'numa_node': 0,
|
||||||
|
'thread_siblings': [0, 1]},
|
||||||
|
{'cpu': 1, 'numa_node': 0,
|
||||||
|
'thread_siblings': [2, 3, 4]},
|
||||||
|
{'cpu': 0, 'numa_node': 1,
|
||||||
|
'thread_siblings': [5, 6, 7]}]
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
mock_core_id_file = mock_open.return_value.read
|
||||||
|
mock_core_id_file.side_effect = reads
|
||||||
|
cpus = numa_insp.get_nodes_cores_info(numa_node_dirs)
|
||||||
|
self.assertEqual(len(cpus), len(expected_cores_info))
|
||||||
|
for cpu in cpus:
|
||||||
|
self.assertIn(cpu, expected_cores_info)
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
@mock.patch.object(os, 'listdir', autospec=True)
|
||||||
|
@mock.patch.object(numa_insp, 'get_numa_node_id', autospec=True)
|
||||||
|
def test_bad_nodes_cores_info(self, mock_node_id,
|
||||||
|
mock_listdir, mock_isdir):
|
||||||
|
numa_node_dirs = ['/sys/devices/system/node/node0']
|
||||||
|
mock_node_id.return_value = 0
|
||||||
|
thread_dirs = ['cpu0', 'cpu1', 'cpu2', 'cpu3', 'cpu4', 'cpu5']
|
||||||
|
mock_listdir.return_value = thread_dirs
|
||||||
|
mock_isdir.return_value = True
|
||||||
|
reads = ['0', '0', '1', '1', '1', IOError]
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
mock_core_id_file = mock_open.return_value.read
|
||||||
|
mock_core_id_file.side_effect = reads
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_nodes_cores_info,
|
||||||
|
numa_node_dirs)
|
||||||
|
|
||||||
|
@mock.patch.object(numa_insp, 'get_numa_node_id', autospec=True)
|
||||||
|
def test_get_numa_node_id_invalid_format_cores_info(self,
|
||||||
|
mock_node_id):
|
||||||
|
numa_node_dirs = ['/sys/devices/system/node/nodeid0']
|
||||||
|
mock_node_id.side_effect = errors.IncompatibleNumaFormatError
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_nodes_cores_info,
|
||||||
|
numa_node_dirs)
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
@mock.patch.object(os, 'listdir', autospec=True)
|
||||||
|
@mock.patch.object(numa_insp, 'get_numa_node_id', autospec=True)
|
||||||
|
def test_nodes_invalid_threaddir_format_cores_info(self, mock_node_id,
|
||||||
|
mock_listdir,
|
||||||
|
mock_isdir):
|
||||||
|
numa_node_dirs = ['/sys/devices/system/node/node0']
|
||||||
|
mock_node_id.return_value = 0
|
||||||
|
thread_dirs = ['cpuid0', 'cpu1', 'cpu2', 'cpu3', 'cpu4', 'cpu5']
|
||||||
|
mock_listdir.return_value = thread_dirs
|
||||||
|
mock_isdir.return_value = True
|
||||||
|
reads = ['0', '0', '1', '1', '1', '2']
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
mock_core_id_file = mock_open.return_value.read
|
||||||
|
mock_core_id_file.side_effect = reads
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_nodes_cores_info,
|
||||||
|
numa_node_dirs)
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
@mock.patch.object(os, 'listdir', autospec=True)
|
||||||
|
@mock.patch.object(numa_insp, 'get_numa_node_id', autospec=True)
|
||||||
|
def test_bad_nodes_thread_dirs(self, mock_node_id,
|
||||||
|
mock_listdir, mock_isdir):
|
||||||
|
numa_node_dirs = ['/sys/devices/system/node/node0']
|
||||||
|
mock_node_id.return_value = 0
|
||||||
|
mock_listdir.side_effect = errors.IncompatibleNumaFormatError("")
|
||||||
|
mock_isdir.return_value = True
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_nodes_cores_info,
|
||||||
|
numa_node_dirs)
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
@mock.patch.object(os, 'listdir', autospec=True)
|
||||||
|
def test_get_nodes_nics_info(self, mock_listdir, mock_isdir):
|
||||||
|
nic_dirs = ['enp0s01', 'enp0s02']
|
||||||
|
mock_listdir.return_value = nic_dirs
|
||||||
|
mock_isdir.return_value = True
|
||||||
|
reads = ['0', '1']
|
||||||
|
expected_nicsinfo = [{'name': 'enp0s01', 'numa_node': 0},
|
||||||
|
{'name': 'enp0s02', 'numa_node': 1}]
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
mock_nicsinfo_file = mock_open.return_value.read
|
||||||
|
mock_nicsinfo_file.side_effect = reads
|
||||||
|
nics = numa_insp.get_nodes_nics_info('/sys/class/net/')
|
||||||
|
self.assertListEqual(expected_nicsinfo, nics)
|
||||||
|
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
@mock.patch.object(os, 'listdir', autospec=True)
|
||||||
|
def test_bad_nodes_nics_info(self, mock_listdir, mock_isdir):
|
||||||
|
nic_dirs = ['enp0s01', 'enp0s02']
|
||||||
|
mock_listdir.return_value = nic_dirs
|
||||||
|
mock_isdir.return_value = True
|
||||||
|
reads = ['0', IOError]
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
mock_nicsinfo_file = mock_open.return_value.read
|
||||||
|
mock_nicsinfo_file.side_effect = reads
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_nodes_nics_info,
|
||||||
|
'/sys/class/net/')
|
||||||
|
|
||||||
|
@mock.patch.object(os, 'listdir', autospec=True)
|
||||||
|
@mock.patch.object(os.path, 'isdir', autospec=True)
|
||||||
|
def test_no_nics_dir(self, mock_isdir, mock_listdir):
|
||||||
|
mock_isdir.return_value = False
|
||||||
|
nic_dirs = ['enp0s01', 'enp0s02']
|
||||||
|
mock_listdir.return_value = nic_dirs
|
||||||
|
reads = ['0', '1']
|
||||||
|
mock_open = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open):
|
||||||
|
mock_nicsinfo_file = mock_open.return_value.read
|
||||||
|
mock_nicsinfo_file.side_effect = reads
|
||||||
|
self.assertRaises(errors.IncompatibleNumaFormatError,
|
||||||
|
numa_insp.get_nodes_nics_info,
|
||||||
|
'/sys/class/net/')
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Adds new optional NUMA topology collector named ``numa_topology``.
|
||||||
|
It collects NUMA specific RAM, CPU and NIC information.
|
@ -40,6 +40,7 @@ ironic_python_agent.inspector.collectors =
|
|||||||
logs = ironic_python_agent.inspector:collect_logs
|
logs = ironic_python_agent.inspector:collect_logs
|
||||||
extra-hardware = ironic_python_agent.inspector:collect_extra_hardware
|
extra-hardware = ironic_python_agent.inspector:collect_extra_hardware
|
||||||
pci-devices = ironic_python_agent.inspector:collect_pci_devices_info
|
pci-devices = ironic_python_agent.inspector:collect_pci_devices_info
|
||||||
|
numa-topology = ironic_python_agent.numa_inspector:collect_numa_topology_info
|
||||||
|
|
||||||
[pbr]
|
[pbr]
|
||||||
autodoc_index_modules = True
|
autodoc_index_modules = True
|
||||||
|
Loading…
Reference in New Issue
Block a user