From 88e5a47bf6d041cea794ddc4acf46de0269766ca Mon Sep 17 00:00:00 2001 From: Andrew Schoen Date: Thu, 26 Feb 2015 15:12:29 -0600 Subject: [PATCH] Verify that the correct ceph version is installed on updates and install This adds a function to teuthology.packaging that can be reused to fetch the installed version of a package for the given remote. Signed-off-by: Andrew Schoen --- teuthology/packaging.py | 40 +++++++++++++++++++ teuthology/task/install.py | 51 +++++++++++++++++++----- teuthology/test/task/__init__.py | 0 teuthology/test/task/test_install.py | 27 +++++++++++++ teuthology/test/test_packaging.py | 59 ++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 teuthology/test/task/__init__.py create mode 100644 teuthology/test/task/test_install.py diff --git a/teuthology/packaging.py b/teuthology/packaging.py index c49861b374..df6786ed15 100644 --- a/teuthology/packaging.py +++ b/teuthology/packaging.py @@ -1,5 +1,6 @@ import logging import ast +import re from cStringIO import StringIO @@ -197,3 +198,42 @@ def get_koji_package_name(package, build_info, arch="x86_64"): ) return pkg_name + + +def get_package_version(remote, package): + installed_ver = None + if remote.os.package_type == "deb": + proc = remote.run( + args=[ + 'dpkg-query', '-W', '-f', '${Version}', package + ], + stdout=StringIO(), + ) + else: + proc = remote.run( + args=[ + 'rpm', '-q', package, '--qf', '%{VERSION}' + ], + stdout=StringIO(), + ) + if proc.exitstatus == 0: + installed_ver = proc.stdout.getvalue().strip() + # Does this look like a version string? + # this assumes a version string starts with non-alpha characters + if installed_ver and re.match('^[^a-zA-Z]', installed_ver): + log.info("The installed version of {pkg} is {ver}".format( + pkg=package, + ver=installed_ver, + )) + else: + installed_ver = None + else: + # should this throw an exception and stop the job? + log.warning( + "Unable to determine if {pkg} is installed: {stdout}".format( + pkg=package, + stdout=proc.stdout.getvalue().strip(), + ) + ) + + return installed_ver diff --git a/teuthology/task/install.py b/teuthology/task/install.py index 4f235b8459..72a7cee41c 100644 --- a/teuthology/task/install.py +++ b/teuthology/task/install.py @@ -9,7 +9,7 @@ import subprocess from teuthology.config import config as teuth_config from teuthology import misc as teuthology -from teuthology import contextutil +from teuthology import contextutil, packaging from teuthology.exceptions import VersionNotFoundError from teuthology.parallel import parallel from ..orchestra import run @@ -238,6 +238,16 @@ def _block_looking_for_package_version(remote, base_url, wait=False): raise VersionNotFoundError(base_url) break version = r.stdout.getvalue().strip() + # FIXME: 'version' as retreived from the repo is actually the RPM version + # PLUS *part* of the release. Example: + # Right now, ceph master is given the following version in the repo file: + # v0.67-rc3.164.gd5aa3a9 - whereas in reality the RPM version is 0.61.7 + # and the release is 37.g1243c97.el6 (for centos6). + # Point being, I have to mangle a little here. + if version[0] == 'v': + version = version[1:] + if '-' in version: + version = version.split('-')[0] return version def _get_local_dir(config, remote): @@ -470,6 +480,32 @@ def _update_rpm_package_list_and_install(ctx, remote, rpm, config): run.Raw(';'), 'fi']) +def verify_ceph_version(ctx, config, remote): + """ + Ensures that the version of ceph installed is what + was asked for in the config. + """ + base_url = _get_baseurl(ctx, remote, config) + version = _block_looking_for_package_version( + remote, + base_url, + config.get('wait_for_package', False) + ) + installed_ver = packaging.get_package_version(remote, 'ceph') + if installed_ver and version in installed_ver: + msg = "The correct ceph version {ver} is installed.".format( + ver=version + ) + log.info(msg) + else: + raise RuntimeError( + "Ceph version {ver} was not installed, found {installed}.".format( + ver=version, + installed=installed_ver + ) + ) + + def purge_data(ctx): """ Purge /var/lib/ceph on every remote in ctx. @@ -527,6 +563,10 @@ def install_packages(ctx, pkgs, config): install_pkgs[system_type], ctx, remote, pkgs[system_type], config) + for remote in ctx.cluster.remotes.iterkeys(): + # verifies that the install worked as expected + verify_ceph_version(ctx, config, remote) + def _remove_deb(ctx, config, remote, debs): """ @@ -906,10 +946,6 @@ def _upgrade_rpm_packages(ctx, config, remote, pkgs): arch=distinfo['arch'],) ) - log.info("Getting version and release for the currently installed ceph...") - remote.run(args=[ - 'rpm', '-q', 'ceph', '--qf', '%{VERSION}-%{RELEASE}' - ]) base_url = _get_baseurl(ctx, remote, config) log.info('Repo base URL: %s', base_url) project = config.get('project', 'ceph') @@ -943,10 +979,6 @@ def _upgrade_rpm_packages(ctx, config, remote, pkgs): args = ['sudo', 'yum', '-y', 'install'] args += pkgs remote.run(args=args) - log.info("Getting version and release for the newly upgraded ceph...") - remote.run(args=[ - 'rpm', '-q', 'ceph', '--qf', '%{VERSION}-%{RELEASE}' - ]) def upgrade_old_style(ctx, node, remote, pkgs, system_type): @@ -1042,6 +1074,7 @@ def upgrade_common(ctx, config, deploy_style): node['project'] = project deploy_style(ctx, node, remote, pkgs, system_type) + verify_ceph_version(ctx, node, remote) docstring_for_upgrade = """" diff --git a/teuthology/test/task/__init__.py b/teuthology/test/task/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/teuthology/test/task/test_install.py b/teuthology/test/task/test_install.py new file mode 100644 index 0000000000..3052b56515 --- /dev/null +++ b/teuthology/test/task/test_install.py @@ -0,0 +1,27 @@ +import pytest + +from mock import patch, Mock + +from teuthology.task import install + + +class TestInstall(object): + + @patch("teuthology.task.install._get_baseurl") + @patch("teuthology.task.install._block_looking_for_package_version") + @patch("teuthology.task.install.packaging.get_package_version") + def test_verify_ceph_version_success(self, m_get_package_version, m_block, + m_get_baseurl): + m_block.return_value = "0.89.0" + m_get_package_version.return_value = "0.89.0" + install.verify_ceph_version(Mock(), Mock(), Mock()) + + @patch("teuthology.task.install._get_baseurl") + @patch("teuthology.task.install._block_looking_for_package_version") + @patch("teuthology.task.install.packaging.get_package_version") + def test_verify_ceph_version_failed(self, m_get_package_version, m_block, + m_get_baseurl): + m_block.return_value = "0.89.0" + m_get_package_version.return_value = "0.89.1" + with pytest.raises(RuntimeError): + install.verify_ceph_version(Mock(), Mock(), Mock()) diff --git a/teuthology/test/test_packaging.py b/teuthology/test/test_packaging.py index 04f91d8173..3f74bf6019 100644 --- a/teuthology/test/test_packaging.py +++ b/teuthology/test/test_packaging.py @@ -145,3 +145,62 @@ class TestPackaging(object): m_ctx.summary = dict() with pytest.raises(RuntimeError): packaging.get_koji_build_info(1, m_remote, m_ctx) + + def test_get_package_version_deb_found(self): + remote = Mock() + remote.os.package_type = "deb" + proc = Mock() + proc.exitstatus = 0 + proc.stdout.getvalue.return_value = "2.2" + remote.run.return_value = proc + result = packaging.get_package_version(remote, "apache2") + assert result == "2.2" + + def test_get_package_version_deb_command(self): + remote = Mock() + remote.os.package_type = "deb" + packaging.get_package_version(remote, "apache2") + args, kwargs = remote.run.call_args + expected_args = ['dpkg-query', '-W', '-f', '${Version}', 'apache2'] + assert expected_args == kwargs['args'] + + def test_get_package_version_rpm_found(self): + remote = Mock() + remote.os.package_type = "rpm" + proc = Mock() + proc.exitstatus = 0 + proc.stdout.getvalue.return_value = "2.2" + remote.run.return_value = proc + result = packaging.get_package_version(remote, "httpd") + assert result == "2.2" + + def test_get_package_version_rpm_command(self): + remote = Mock() + remote.os.package_type = "rpm" + packaging.get_package_version(remote, "httpd") + args, kwargs = remote.run.call_args + expected_args = ['rpm', '-q', 'httpd', '--qf', '%{VERSION}'] + assert expected_args == kwargs['args'] + + def test_get_package_version_not_found(self): + remote = Mock() + remote.os.package_type = "rpm" + proc = Mock() + proc.exitstatus = 1 + proc.stdout.getvalue.return_value = "not installed" + remote.run.return_value = proc + result = packaging.get_package_version(remote, "httpd") + assert result is None + + def test_get_package_version_invalid_version(self): + # this tests the possibility that the package is not found + # but the exitstatus is still 0. Not entirely sure we'll ever + # hit this condition, but I want to test the codepath regardless + remote = Mock() + remote.os.package_type = "rpm" + proc = Mock() + proc.exitstatus = 0 + proc.stdout.getvalue.return_value = "not installed" + remote.run.return_value = proc + result = packaging.get_package_version(remote, "httpd") + assert result is None -- 2.39.5