diff --git a/.zuul.yaml b/.zuul.yaml index e304ca65..52a2d4a7 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -6,6 +6,7 @@ - stx-utilities-tox-pep8 - stx-utilities-tox-pylint - stx-utilities-ceph-manager-tox-bandit + - stx-utilities-ceph-manager-tox-py39 - stx-utilities-ceph-client-tox-bandit - stx-utilities-pci-irq-affinity-agent-tox-py27 - stx-utilities-pci-irq-affinity-agent-tox-py39 @@ -16,6 +17,7 @@ - stx-utilities-tox-pep8 - stx-utilities-tox-pylint - stx-utilities-ceph-manager-tox-bandit + - stx-utilities-ceph-manager-tox-py39 - stx-utilities-ceph-client-tox-bandit - stx-utilities-pci-irq-affinity-agent-tox-py27 - stx-utilities-pci-irq-affinity-agent-tox-py39 @@ -71,6 +73,19 @@ tox_envlist: bandit tox_extra_args: -c ./ceph/ceph-manager/ceph-manager/tox.ini +- job: + name: stx-utilities-ceph-manager-tox-py39 + parent: tox + description: | + Run py39 unittests for utilities ceph-mananger + nodeset: debian-bullseye + files: + - ./ceph/ceph-manager/ceph-manager/* + vars: + tox_envlist: py39 + python_version: 3.9 + tox_extra_args: -c ./ceph/ceph-manager/ceph-manager/tox.ini + - job: name: stx-utilities-ceph-client-tox-bandit parent: tox diff --git a/ceph/ceph-manager/ceph-manager/ceph_manager/monitor.py b/ceph/ceph-manager/ceph-manager/ceph_manager/monitor.py index e71ba790..01c028bd 100644 --- a/ceph/ceph-manager/ceph-manager/ceph_manager/monitor.py +++ b/ceph/ceph-manager/ceph-manager/ceph_manager/monitor.py @@ -21,6 +21,7 @@ from ceph_manager.i18n import _ from ceph_manager.i18n import _LE from ceph_manager.i18n import _LI from ceph_manager.i18n import _LW +from ceph_manager.sysinv_api import upgrade LOG = logging.getLogger(__name__) @@ -35,12 +36,14 @@ LOG = logging.getLogger(__name__) # class HandleUpgradesMixin(object): - def __init__(self, service): + def __init__(self, service, conf): self.service = service + self.sysinv_upgrade_api = upgrade.SysinvUpgradeApi(conf) self.wait_for_upgrade_complete = False def setup(self, config): - self._set_upgrade(self.service.retry_get_software_upgrade_status()) + self._set_upgrade( + self.sysinv_upgrade_api.retry_get_software_upgrade_status()) def _set_upgrade(self, upgrade): state = upgrade.get('state') @@ -109,7 +112,7 @@ class HandleUpgradesMixin(object): and (constants.CEPH_HEALTH_WARN_REQUIRE_JEWEL_OSDS_NOT_SET in health['detail'])): try: - upgrade = self.service.get_software_upgrade_status() + upgrade = self.sysinv_upgrade_api.get_software_upgrade_status() except Exception as ex: LOG.warn(_LW( "Getting software upgrade status failed " @@ -152,7 +155,7 @@ class HandleUpgradesMixin(object): class Monitor(HandleUpgradesMixin): - def __init__(self, service): + def __init__(self, service, conf): self.service = service self.current_ceph_health = "" self.tiers_size = {} @@ -160,7 +163,7 @@ class Monitor(HandleUpgradesMixin): self.primary_tier_name = constants.SB_TIER_DEFAULT_NAMES[ constants.SB_TIER_TYPE_CEPH] + constants.CEPH_CRUSH_TIER_SUFFIX self.cluster_is_up = False - super(Monitor, self).__init__(service) + super(Monitor, self).__init__(service, conf) def setup(self, config): super(Monitor, self).setup(config) diff --git a/ceph/ceph-manager/ceph-manager/ceph_manager/server.py b/ceph/ceph-manager/ceph-manager/ceph_manager/server.py index 28531a2f..ef20a5ac 100644 --- a/ceph/ceph-manager/ceph-manager/ceph_manager/server.py +++ b/ceph/ceph-manager/ceph-manager/ceph_manager/server.py @@ -23,13 +23,10 @@ import oslo_messaging as messaging from oslo_service import service # noinspection PyUnresolvedReferences from oslo_service.periodic_task import PeriodicTasks -# noinspection PyUnresolvedReferences -from retrying import retry from ceph_manager import constants from ceph_manager import utils from ceph_manager.i18n import _LI -from ceph_manager.i18n import _LW from ceph_manager.monitor import Monitor from cephclient import wrapper @@ -94,38 +91,16 @@ class RpcEndpoint(PeriodicTasks): return self.service.monitor.cluster_is_up -class SysinvConductorUpgradeApi(object): - def __init__(self): - self.sysinv_conductor = None - super(SysinvConductorUpgradeApi, self).__init__() - - def get_software_upgrade_status(self): - LOG.info(_LI("Getting software upgrade status from sysinv")) - cctxt = self.sysinv_conductor.prepare(timeout=2) - upgrade = cctxt.call({}, 'get_software_upgrade_status') - LOG.info(_LI("Software upgrade status: %s") % str(upgrade)) - return upgrade - - @retry(wait_fixed=1000, - retry_on_exception=lambda e: - LOG.warn(_LW( - "Getting software upgrade status failed " - "with: %s. Retrying... ") % str(e)) or True) - def retry_get_software_upgrade_status(self): - return self.get_software_upgrade_status() - - -class Service(SysinvConductorUpgradeApi, service.Service): +class Service(service.Service): def __init__(self, conf): super(Service, self).__init__() self.conf = conf self.rpc_server = None - self.sysinv_conductor = None self.ceph_api = None self.entity_instance_id = '' self.fm_api = fm_api.FaultAPIs() - self.monitor = Monitor(self) + self.monitor = Monitor(self, conf) self.config = None self.config_desired = None self.config_applied = None @@ -135,17 +110,13 @@ class Service(SysinvConductorUpgradeApi, service.Service): # pylint: disable=protected-access sysinv_conf = self.conf._namespace._normalized[0]['DEFAULT'] - url = "rabbit://{user}:{password}@{host}:{port}".format( - user=sysinv_conf['rabbit_userid'][0], - password=sysinv_conf['rabbit_password'][0], - host=utils.ipv6_bracketed(sysinv_conf['rabbit_host'][0]), - port=sysinv_conf['rabbit_port'][0] - ) + url = "rabbit://{user}:{password}@{host}:{port}"\ + "".format(user=sysinv_conf['rabbit_userid'][0], + password=sysinv_conf['rabbit_password'][0], + host=utils.ipv6_bracketed( + sysinv_conf['rabbit_host'][0]), + port=sysinv_conf['rabbit_port'][0]) transport = messaging.get_transport(self.conf, url=url) - self.sysinv_conductor = messaging.RPCClient( - transport, - messaging.Target( - topic=constants.SYSINV_CONDUCTOR_TOPIC)) self.ceph_api = wrapper.CephWrapper( endpoint='http://localhost:{}'.format(constants.CEPH_MGR_PORT)) diff --git a/ceph/ceph-manager/ceph-manager/ceph_manager/sysinv_api/__init__.py b/ceph/ceph-manager/ceph-manager/ceph_manager/sysinv_api/__init__.py new file mode 100644 index 00000000..104a1016 --- /dev/null +++ b/ceph/ceph-manager/ceph-manager/ceph_manager/sysinv_api/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 diff --git a/ceph/ceph-manager/ceph-manager/ceph_manager/sysinv_api/upgrade.py b/ceph/ceph-manager/ceph-manager/ceph_manager/sysinv_api/upgrade.py new file mode 100755 index 00000000..93cb0b06 --- /dev/null +++ b/ceph/ceph-manager/ceph-manager/ceph_manager/sysinv_api/upgrade.py @@ -0,0 +1,93 @@ +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +from keystoneauth1 import session as ksession +from keystoneauth1.identity import v3 +from oslo_log import log +from retrying import retry +from urllib3 import util + +from ceph_manager.i18n import _LI +from ceph_manager.i18n import _LW +from ceph_manager.utils import ipv6_bracketed + +LOG = log.getLogger(__name__) +MAX_RETRY = 5 + + +class SysinvUpgradeApi(object): + def __init__(self, conf): + # pylint: disable=protected-access + sysinv_conf = conf._namespace._normalized[0]['DEFAULT'] + sysinv_api_bind_ip = sysinv_conf['sysinv_api_bind_ip'][0] + sysinv_api_port = sysinv_conf['sysinv_api_port'][0] + + self.base_url = util.Url( + scheme='http', + host=ipv6_bracketed(sysinv_api_bind_ip), + port=sysinv_api_port, + path='/v1').url + + # pylint: disable=protected-access + auth_conf = conf._namespace._normalized[0]['keystone_authtoken'] + self.auth_url = auth_conf['auth_url'][0] + self.auth_username = auth_conf['username'][0] + self.auth_password = auth_conf['password'][0] + self.auth_user_domain_name = auth_conf['user_domain_name'][0] + self.auth_project_name = auth_conf['project_name'][0] + self.auth_project_domain_name = auth_conf['project_domain_name'][0] + + def _rest_api_request(self, method, api_cmd, api_cmd_headers=None, + api_cmd_payload=None): + headers = {} + headers['Accept'] = "application/json" + + if api_cmd_headers is not None: + headers.update(api_cmd_headers) + + session = self._get_session() + response = session.request( + api_cmd, method, headers=headers, json=api_cmd_payload) + + return response.json() + + def _get_session(self): + auth = v3.Password(auth_url=self.auth_url + "/v3", + username=self.auth_username, + password=self.auth_password, + project_name=self.auth_project_name, + user_domain_name=self.auth_user_domain_name, + project_domain_name=self.auth_project_domain_name) + session = ksession.Session(auth=auth) + + return session + + def _get_upgrades(self): + url = self.base_url + '/upgrade' + response = self._rest_api_request('GET', url) + return response.get('upgrades', []) + + def get_software_upgrade_status(self): + LOG.info(_LI("Getting software upgrade status from sysinv")) + upgrade = { + 'from_version': None, + 'to_version': None, + 'state': None + } + + upgrades = self._get_upgrades() + if upgrades: + upgrade = upgrades[0] + + LOG.info(_LI("Software upgrade status: %s") % str(upgrade)) + return upgrade + + @retry(stop_max_attempt_number=MAX_RETRY, + wait_fixed=1000, + retry_on_exception=lambda e: + LOG.warn(_LW( + "Getting software upgrade status failed " + "with: %s. Retrying... ") % str(e)) or True) + def retry_get_software_upgrade_status(self): + return self.get_software_upgrade_status() diff --git a/ceph/ceph-manager/ceph-manager/ceph_manager/tests/test_upgrade_api.py b/ceph/ceph-manager/ceph-manager/ceph_manager/tests/test_upgrade_api.py new file mode 100644 index 00000000..0a3a6ca2 --- /dev/null +++ b/ceph/ceph-manager/ceph-manager/ceph_manager/tests/test_upgrade_api.py @@ -0,0 +1,84 @@ +import unittest + +from keystoneauth1.exceptions import base +import mock + +from ceph_manager.sysinv_api import upgrade + +SYSINV_CONF = { + 'sysinv_api_bind_ip': '192.168.1.1', + 'sysinv_api_port': 12345 +} +KEYSTONE_CONF = { + 'auth_url': 'http://example.com', + 'username': 'sysadmin', + 'password': 'hunter2', + 'user_domain_name': 'Default', + 'project_name': 'sysinv', + 'project_domain_name': 'Default' +} + +UPGRADE_DICT = { + 'from_version': '123', + 'to_version': '456', + 'state': 'done' +} + + +class SysinvUpgradeApiTest(unittest.TestCase): + def setUp(self): + conf = mock.MagicMock() + conf._namespace._normalized.return_value = [{'DEFAULT': SYSINV_CONF}] + conf._namespace._normalized.return_value = [ + {'keystone_authtoken': KEYSTONE_CONF}] + + self.api = upgrade.SysinvUpgradeApi(conf) + + self.session_mock = mock.MagicMock() + self.response_mock = mock.MagicMock() + + self.session_mock.request.return_value = self.response_mock + + self.api._get_session = mock.MagicMock(return_value=self.session_mock) + + def test_get_software_upgrade_status_has_upgrade(self): + self.response_mock.json.return_value = {'upgrades': [UPGRADE_DICT]} + + status = self.api.get_software_upgrade_status() + + self.session_mock.request.assert_called_once() + assert status == UPGRADE_DICT + + def test_get_software_upgrade_status_no_upgrade(self): + expected = { + 'from_version': None, + 'to_version': None, + 'state': None + } + self.response_mock.json.return_value = {'upgrades': []} + + status = self.api.get_software_upgrade_status() + + self.session_mock.request.assert_called_once() + assert status == expected + + def test_retry_get_software_upgrade_status_should_retry(self): + self.response_mock.json.return_value = {'upgrades': [UPGRADE_DICT]} + self.session_mock.request.side_effect = [ + base.ClientException('Boom!'), self.response_mock] + + status = self.api.retry_get_software_upgrade_status() + + assert self.session_mock.request.call_count == 2 + assert status == UPGRADE_DICT + + def test_retry_get_software_upgrade_status_retry_limit(self): + ex = base.ClientException('Boom!') + self.session_mock.request.side_effect = [ + ex for _ in range(upgrade.MAX_RETRY+1)] + + with self.assertRaises(base.ClientException) as context: + self.api.retry_get_software_upgrade_status() + + assert context.exception == ex + assert self.session_mock.request.call_count == upgrade.MAX_RETRY diff --git a/ceph/ceph-manager/ceph-manager/setup.py b/ceph/ceph-manager/ceph-manager/setup.py index 40cf5012..a12ba50c 100644 --- a/ceph/ceph-manager/ceph-manager/setup.py +++ b/ceph/ceph-manager/ceph-manager/setup.py @@ -13,7 +13,7 @@ setuptools.setup( version='1.0.0', description='CEPH manager', license='Apache-2.0', - packages=['ceph_manager'], + packages=['ceph_manager', 'ceph_manager.sysinv_api'], entry_points={ } ) diff --git a/ceph/ceph-manager/ceph-manager/test-requirements.txt b/ceph/ceph-manager/ceph-manager/test-requirements.txt index 98738d01..882e61cf 100644 --- a/ceph/ceph-manager/ceph-manager/test-requirements.txt +++ b/ceph/ceph-manager/ceph-manager/test-requirements.txt @@ -6,7 +6,9 @@ bandit;python_version>="3.0" mock flake8 eventlet +keystoneauth1 pytest oslo.log oslo.i18n flake8-import-order +retrying diff --git a/ceph/ceph-manager/ceph-manager/tox.ini b/ceph/ceph-manager/ceph-manager/tox.ini index 8c4854a3..3fa5ba5d 100644 --- a/ceph/ceph-manager/ceph-manager/tox.ini +++ b/ceph/ceph-manager/ceph-manager/tox.ini @@ -2,7 +2,7 @@ [tox] minversion = 1.6 -envlist = py27,pep8,bandit +envlist = py27,py39,pep8,bandit skipsdist = True # tox does not work if the path to the workdir is too long, so move it to /tmp toxworkdir = /tmp/{env:USER}_utilities_ceph_manager_tox @@ -11,7 +11,9 @@ toxworkdir = /tmp/{env:USER}_utilities_ceph_manager_tox basepython = python3.9 setenv = VIRTUAL_ENV={envdir} usedevelop = True -install_command = pip install -U --force-reinstall {opts} {packages} +install_command = pip install -U --force-reinstall \ + -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/starlingx/root/raw/branch/master/build-tools/requirements/debian/upper-constraints.txt} \ + {opts} {packages} deps = -r{toxinidir}/test-requirements.txt commands = pytest {posargs} whitelist_externals = bash @@ -19,6 +21,9 @@ passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY [testenv:py27] basepython = python2.7 +install_command = pip install -U --force-reinstall \ + -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/stable/stein/upper-constraints.txt} \ + {opts} {packages} [testenv:pep8] commands = @@ -40,6 +45,6 @@ skips = B104,B110 exclude = tests [testenv:bandit] -basepython = python3 +basepython = python3.9 deps = -r{toxinidir}/test-requirements.txt commands = bandit --ini tox.ini -n 5 -r ceph_manager