summaryrefslogtreecommitdiff
path: root/ironic_python_agent
diff options
context:
space:
mode:
authorJaganathan Palanisamy <jpalanis@redhat.com>2017-02-09 07:04:33 -0500
committerJaganathan Palanisamy <jpalanis@redhat.com>2017-05-16 08:07:58 -0400
commitcc9e05da504693af6ceafff64527200bcbe4bb7d (patch)
tree5cc5a420cee2408538a170385db7fdbe4b87321c /ironic_python_agent
parent15878b7b181098db7abb6601ada0e245b6722d46 (diff)
downloadironic-python-agent-cc9e05da504693af6ceafff64527200bcbe4bb7d.tar.gz
NUMA-topology collector
Implement the optional collector for fetching the NUMA topology details. Collects RAM, CPU Cores, thread siblings and NICS data for each NUMA node and stored under "numa_topology" key. Closes-bug: #1635253 Co-Authored-By: Jaganathan Palanisamy <jpalanis@redhat.com> Change-Id: I5a546c009d95f39b7af4d89cf785be8acb8ebc67 Signed-off-by: karthik s <ksundara@redhat.com>
Diffstat (limited to 'ironic_python_agent')
-rw-r--r--ironic_python_agent/errors.py6
-rw-r--r--ironic_python_agent/numa_inspector.py255
-rw-r--r--ironic_python_agent/tests/unit/test_numa_inspector.py350
3 files changed, 611 insertions, 0 deletions
diff --git a/ironic_python_agent/errors.py b/ironic_python_agent/errors.py
index 2c49ffc8..11f769f0 100644
--- a/ironic_python_agent/errors.py
+++ b/ironic_python_agent/errors.py
@@ -293,6 +293,12 @@ class ISCSIError(RESTError):
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):
"""Error executing TGT command."""
diff --git a/ironic_python_agent/numa_inspector.py b/ironic_python_agent/numa_inspector.py
new file mode 100644
index 00000000..4111217f
--- /dev/null
+++ b/ironic_python_agent/numa_inspector.py
@@ -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
diff --git a/ironic_python_agent/tests/unit/test_numa_inspector.py b/ironic_python_agent/tests/unit/test_numa_inspector.py
new file mode 100644
index 00000000..501853f5
--- /dev/null
+++ b/ironic_python_agent/tests/unit/test_numa_inspector.py
@@ -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/')