Introduce os-capability parsing

This patch introduces a way to parse cpuset information by looking
into the 'lscpu' output. The implementation is presently done for
linux but given docker or some such container runtimes can work on
other operating systems too, a more generic approach has been taken.

Eventually host_capabilities module should expose general purpose
methods leaving the details of the os inside the respective dir.

Change-Id: I0f92594d8e5ce013f47174c09bc941ea1de3f0a8
This commit is contained in:
Sudipta Biswas 2017-02-08 18:59:16 +05:30
parent b51b114f13
commit 236ffb9bae
9 changed files with 154 additions and 0 deletions

View File

@ -388,5 +388,9 @@ class EntityNotFound(ZunException):
message = _("The %(entity)s (%(name)s) could not be found.")
class CommandError(ZunException):
message = _("The command: %(cmd)s failed on the system.")
class NoValidHost(ZunException):
message = _("No valid host was found. %(reason)s")

View File

View File

@ -0,0 +1,25 @@
# Copyright 2017 IBM Corp
# All Rights Reserved.
#
# 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.
class Host(object):
def __init__(self):
self.capabilities = None
def get_cpu_numa_info(self):
"""This method returns a dict containing the cpuset info for a host"""
raise NotImplementedError()

View File

@ -0,0 +1,60 @@
# Copyright 2017 IBM Corp
# All Rights Reserved.
#
# 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.
from collections import defaultdict
import re
import six
from oslo_concurrency import processutils
from oslo_log import log as logging
from zun.common import exception
from zun.common.i18n import _LE
from zun.container.os_capability import host_capability
LOG = logging.getLogger(__name__)
class LinuxHost(host_capability.Host):
def get_cpu_numa_info(self):
# TODO(sbiswas7): rootwrap changes for zun required.
old_lscpu = False
try:
output = processutils.execute('lscpu', '-p=socket,cpu,online')
except processutils.ProcessExecutionError as e:
LOG.exception(_LE("There was a problem while executing lscpu "
"-p=socket,cpu,online : %s"), six.text_type(e))
# There is a possibility that an older version of lscpu is used
# So let's try without the online column
try:
output = processutils.execute('lscpu', '-p=socket,cpu')
old_lscpu = True
except processutils.ProcessExecutionError as e:
LOG.exception(_LE("There was a problem while executing lscpu "
"-p=socket,cpu : %s"), six.text_type(e))
raise exception.CommandError(cmd="lscpu")
if old_lscpu:
cpu_sock_pair = re.findall("\d+(?:,\d+)?", str(output))
else:
cpu_sock_pair = re.findall("\d+(?:,\d+,[Y/N])?", str(output))
sock_map = defaultdict(list)
for value in cpu_sock_pair:
val = value.split(",")
if len(val) == 3 and val[2] == 'Y':
sock_map[val[0]].append(val[1])
elif len(val) == 2 and old_lscpu:
sock_map[val[0]].append(val[1])
return sock_map

View File

@ -0,0 +1,65 @@
# Copyright 2017 IBM Corp.
#
# 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 mock
from oslo_concurrency import processutils
from zun.common import exception
from zun.container.os_capability.linux import os_capability_linux
from zun.tests import base
LSCPU_ON = """# The following is the parsable format, which can be fed to other
# programs. Each different item in every column has an unique ID
# starting from zero.
# Socket,CPU,Online
0,0,Y
0,8,Y
1,16,Y
1,24,Y
2,32,Y"""
LSCPU_NO_ONLINE = """# The following is the parsable format, which can be fed to
# programs. Each different item in every column has an unique ID
# starting from zero.
# Socket,CPU
0,0
0,1
1,2
1,3"""
class TestOSCapability(base.BaseTestCase):
def setUp(self):
super(TestOSCapability, self).setUp()
@mock.patch('oslo_concurrency.processutils.execute')
def test_get_cpu_numa_info_with_online(self, mock_output):
mock_output.return_value = LSCPU_ON
output = os_capability_linux.LinuxHost().get_cpu_numa_info()
expected_output = {'0': ['0', '8'], '1': ['16', '24'], '2': ['32']}
self.assertEqual(expected_output, output)
@mock.patch('oslo_concurrency.processutils.execute')
def test_get_cpu_numa_info_exception(self, mock_output):
mock_output.side_effect = processutils.ProcessExecutionError()
self.assertRaises(exception.CommandError,
os_capability_linux.LinuxHost().get_cpu_numa_info)
@mock.patch('oslo_concurrency.processutils.execute')
def test_get_cpu_numa_info_without_online(self, mock_output):
mock_output.side_effect = [processutils.ProcessExecutionError(),
LSCPU_NO_ONLINE]
expected_output = {'0': ['0', '1'], '1': ['2', '3']}
output = os_capability_linux.LinuxHost().get_cpu_numa_info()
self.assertEqual(expected_output, output)