import io import os import unittest from copy import copy from tests.helpers import patch_open, FakeRelation from testtools import TestCase from mock import MagicMock, patch, call from charmhelpers.fetch import ubuntu as fetch from charmhelpers.core.hookenv import WORKLOAD_STATES, flush import charmhelpers.contrib.openstack.utils as openstack import charmhelpers.contrib.openstack.deferred_events as deferred_events from charmhelpers.contrib.openstack.exceptions import ServiceActionError import contextlib import six if not six.PY3: builtin_open = '__builtin__.open' builtin_import = '__builtin__.__import__' else: builtin_open = 'builtins.open' builtin_import = 'builtins.__import__' FAKE_CODENAME = 'precise' # mocked return of openstack.lsb_release() FAKE_RELEASE = { 'DISTRIB_CODENAME': 'precise', 'DISTRIB_RELEASE': '12.04', 'DISTRIB_ID': 'Ubuntu', 'DISTRIB_DESCRIPTION': '"Ubuntu 12.04"' } FAKE_REPO = { # liberty patch release 'neutron-common': { 'pkg_vers': '2:7.0.1-0ubuntu1', 'os_release': 'liberty', 'os_version': '2015.2' }, # liberty release version 'nova-common': { 'pkg_vers': '2:12.0.0~b1-0ubuntu1', 'os_release': 'liberty', 'os_version': '2015.2' }, 'nova': { 'pkg_vers': '2012.2.3-0ubuntu2.1', 'os_release': 'folsom', 'os_version': '2012.2' }, 'glance-common': { 'pkg_vers': '2012.1.3+stable-20130423-74b067df-0ubuntu1', 'os_release': 'essex', 'os_version': '2012.1' }, 'keystone-common': { 'pkg_vers': '1:2013.1-0ubuntu1.1~cloud0', 'os_release': 'grizzly', 'os_version': '2013.1' }, # Exercise swift version detection 'swift-storage': { 'pkg_vers': '1.8.0-0ubuntu1', 'os_release': 'grizzly', 'os_version': '1.8.0' }, 'swift-proxy': { 'pkg_vers': '1.13.1-0ubuntu1', 'os_release': 'icehouse', 'os_version': '1.13.1' }, 'swift-common': { 'pkg_vers': '1.10.0~rc1-0ubuntu1', 'os_release': 'havana', 'os_version': '1.10.0' }, 'swift-mitaka-dev': { 'pkg_vers': '2.7.1.dev8.201605111703.trusty-0ubuntu1', 'os_release': 'mitaka', 'os_version': '2.7.0' }, # a package that's available in the cache but is not installed 'cinder-common': { 'os_release': 'havana', 'os_version': '2013.2' }, # poorly formed openstack version 'bad-version': { 'pkg_vers': '1:2200.1-0ubuntu1.1~cloud0', 'os_release': None, 'os_version': None } } MOUNTS = [ ['/mnt', '/dev/vdb'] ] url = 'deb ' + openstack.CLOUD_ARCHIVE_URL UCA_SOURCES = [ ('cloud:precise-folsom/proposed', url + ' precise-proposed/folsom main'), ('cloud:precise-folsom', url + ' precise-updates/folsom main'), ('cloud:precise-folsom/updates', url + ' precise-updates/folsom main'), ('cloud:precise-grizzly/proposed', url + ' precise-proposed/grizzly main'), ('cloud:precise-grizzly', url + ' precise-updates/grizzly main'), ('cloud:precise-grizzly/updates', url + ' precise-updates/grizzly main'), ('cloud:precise-havana/proposed', url + ' precise-proposed/havana main'), ('cloud:precise-havana', url + ' precise-updates/havana main'), ('cloud:precise-havana/updates', url + ' precise-updates/havana main'), ('cloud:precise-icehouse/proposed', url + ' precise-proposed/icehouse main'), ('cloud:precise-icehouse', url + ' precise-updates/icehouse main'), ('cloud:precise-icehouse/updates', url + ' precise-updates/icehouse main'), ] # Mock python-dnspython resolver used by get_host_ip() class FakeAnswer(object): def __init__(self, ip): self.ip = ip def __str__(self): return self.ip class FakeResolver(object): def __init__(self, ip): self.ip = ip def query(self, hostname, query_type): if self.ip == '': return [] else: return [FakeAnswer(self.ip)] class FakeReverse(object): def from_address(self, address): return '156.94.189.91.in-addr.arpa' class FakeDNSName(object): def __init__(self, dnsname): pass class FakeDNS(object): def __init__(self, ip): self.resolver = FakeResolver(ip) self.reversename = FakeReverse() self.name = MagicMock() self.name.Name = FakeDNSName class OpenStackHelpersTestCase(TestCase): def setUp(self): super(OpenStackHelpersTestCase, self).setUp() self.patch(fetch, 'get_apt_dpkg_env', lambda: {}) # Make sleep() and log() into noops for testing for funcname in ('charmhelpers.core.decorators.log', 'charmhelpers.core.decorators.time.sleep'): patcher = patch(funcname, return_value=None) patcher.start() self.addCleanup(patcher.stop) def _apt_cache(self): # mocks out the apt cache def cache_get(package): pkg = MagicMock() if package in FAKE_REPO and 'pkg_vers' in FAKE_REPO[package]: pkg.name = package pkg.current_ver.ver_str = FAKE_REPO[package]['pkg_vers'] elif (package in FAKE_REPO and 'pkg_vers' not in FAKE_REPO[package]): pkg.name = package pkg.current_ver = None else: raise KeyError return pkg cache = MagicMock() cache.__getitem__.side_effect = cache_get return cache @patch.object(openstack, 'filter_missing_packages') def test_get_installed_semantic_versioned_packages(self, mock_filter): def _filter_missing_packages(pkgs): return [x for x in pkgs if x in ['cinder-common']] mock_filter.side_effect = _filter_missing_packages self.assertEquals( openstack.get_installed_semantic_versioned_packages(), ['cinder-common']) @patch('charmhelpers.contrib.openstack.utils.lsb_release') def test_os_codename_from_install_source(self, mocked_lsb): """Test mapping install source to OpenStack release name""" mocked_lsb.return_value = FAKE_RELEASE # the openstack release shipped with respective ubuntu/lsb release. self.assertEquals(openstack.get_os_codename_install_source('distro'), 'essex') # proposed pocket self.assertEquals(openstack.get_os_codename_install_source( 'distro-proposed'), 'essex') self.assertEquals(openstack.get_os_codename_install_source( 'proposed'), 'essex') # various cloud archive pockets src = 'cloud:precise-grizzly' self.assertEquals(openstack.get_os_codename_install_source(src), 'grizzly') src = 'cloud:precise-grizzly/proposed' self.assertEquals(openstack.get_os_codename_install_source(src), 'grizzly') # ppas and full repo urls. src = 'ppa:openstack-ubuntu-testing/havana-trunk-testing' self.assertEquals(openstack.get_os_codename_install_source(src), 'havana') src = ('deb http://ubuntu-cloud.archive.canonical.com/ubuntu ' 'precise-havana main') self.assertEquals(openstack.get_os_codename_install_source(src), 'havana') self.assertEquals(openstack.get_os_codename_install_source(None), '') @patch.object(openstack, 'get_os_version_codename') @patch.object(openstack, 'get_os_codename_install_source') def test_os_version_from_install_source(self, codename, version): codename.return_value = 'grizzly' openstack.get_os_version_install_source('cloud:precise-grizzly') version.assert_called_with('grizzly') @patch('charmhelpers.contrib.openstack.utils.lsb_release') def test_os_codename_from_bad_install_source(self, mocked_lsb): """Test mapping install source to OpenStack release name""" _fake_release = copy(FAKE_RELEASE) _fake_release['DISTRIB_CODENAME'] = 'natty' mocked_lsb.return_value = _fake_release _e = 'charmhelpers.contrib.openstack.utils.error_out' with patch(_e) as mocked_err: openstack.get_os_codename_install_source('distro') _er = ('Could not derive openstack release for this Ubuntu ' 'release: natty') mocked_err.assert_called_with(_er) def test_os_codename_from_version(self): """Test mapping OpenStack numerical versions to code name""" self.assertEquals(openstack.get_os_codename_version('2013.1'), 'grizzly') @patch('charmhelpers.contrib.openstack.utils.error_out') def test_os_codename_from_bad_version(self, mocked_error): """Test mapping a bad OpenStack numerical versions to code name""" openstack.get_os_codename_version('2014.5.5') expected_err = ('Could not determine OpenStack codename for ' 'version 2014.5.5') mocked_error.assert_called_with(expected_err) def test_os_version_from_codename(self): """Test mapping a OpenStack codename to numerical version""" self.assertEquals(openstack.get_os_version_codename('folsom'), '2012.2') @patch('charmhelpers.contrib.openstack.utils.error_out') def test_os_version_from_bad_codename(self, mocked_error): """Test mapping a bad OpenStack codename to numerical version""" openstack.get_os_version_codename('foo') expected_err = 'Could not derive OpenStack version for codename: foo' mocked_error.assert_called_with(expected_err) def test_os_version_swift_from_codename(self): """Test mapping a swift codename to numerical version""" self.assertEquals(openstack.get_os_version_codename_swift('liberty'), '2.5.0') def test_get_swift_codename_single_version_kilo(self): self.assertEquals(openstack.get_swift_codename('2.2.2'), 'kilo') @patch('charmhelpers.contrib.openstack.utils.error_out') def test_os_version_swift_from_bad_codename(self, mocked_error): """Test mapping a bad swift codename to numerical version""" openstack.get_os_version_codename_swift('foo') expected_err = 'Could not derive swift version for codename: foo' mocked_error.assert_called_with(expected_err) def test_get_swift_codename_multiple_versions_liberty(self): with patch('subprocess.check_output') as _subp: _subp.return_value = b"... trusty-updates/liberty/main ..." self.assertEquals(openstack.get_swift_codename('2.5.0'), 'liberty') def test_get_swift_codename_multiple_versions_mitaka(self): with patch('subprocess.check_output') as _subp: _subp.return_value = b"... trusty-updates/mitaka/main ..." self.assertEquals(openstack.get_swift_codename('2.5.0'), 'mitaka') def test_get_swift_codename_none(self): self.assertEquals(openstack.get_swift_codename('1.2.3'), None) @patch("charmhelpers.core.hookenv.cache", new={}) @patch.object(openstack, 'openstack_release') @patch.object(openstack, 'filter_installed_packages') @patch.object(openstack, 'apt_install') def test_get_installed_os_version_no_package(self, mock_apt_install, mock_filter_installed_packages, mock_openstack_release): mock_openstack_release.return_value = {} self.assertEquals( openstack.get_installed_os_version(), None) @patch("charmhelpers.core.hookenv.cache", new={}) @patch.object(openstack, 'openstack_release') @patch.object(openstack, 'filter_installed_packages') @patch.object(openstack, 'apt_install') def test_get_installed_os_version_with_package(self, mock_apt_install, mock_filter_installed_packages, mock_openstack_release): mock_openstack_release.return_value = {'OPENSTACK_CODENAME': 'wallaby'} self.assertEquals( openstack.get_installed_os_version(), 'wallaby') @patch.object(openstack, 'get_installed_os_version') @patch.object(openstack, 'snap_install_requested') def test_os_codename_from_package(self, mock_snap_install_requested, mock_get_installed_os_version): """Test deriving OpenStack codename from an installed package""" mock_snap_install_requested.return_value = False mock_get_installed_os_version.return_value = None with patch.object(openstack, 'apt_cache') as cache: cache.return_value = self._apt_cache() for pkg, vers in six.iteritems(FAKE_REPO): # test fake repo for all "installed" packages if pkg.startswith('bad-'): continue if 'pkg_vers' not in vers: continue self.assertEquals(openstack.get_os_codename_package(pkg), vers['os_release']) @patch.object(openstack, 'get_installed_os_version') @patch.object(openstack, 'snap_install_requested') @patch('charmhelpers.contrib.openstack.utils.error_out') def test_os_codename_from_bad_package_version(self, mocked_error, mock_snap_install_requested, mock_get_installed_os_version): """Test deriving OpenStack codename for a poorly versioned package""" mock_snap_install_requested.return_value = False mock_get_installed_os_version.return_value = None with patch.object(openstack, 'apt_cache') as cache: cache.return_value = self._apt_cache() openstack.get_os_codename_package('bad-version') _e = ('Could not determine OpenStack codename for version 2200.1') mocked_error.assert_called_with(_e) @patch.object(openstack, 'get_installed_os_version') @patch.object(openstack, 'snap_install_requested') @patch('charmhelpers.contrib.openstack.utils.error_out') def test_os_codename_from_bad_package(self, mocked_error, mock_snap_install_requested, mock_get_installed_os_version): """Test deriving OpenStack codename from an unavailable package""" mock_snap_install_requested.return_value = False mock_get_installed_os_version.return_value = None with patch.object(openstack, 'apt_cache') as cache: cache.return_value = self._apt_cache() try: openstack.get_os_codename_package('foo') except Exception: # ignore exceptions that raise when error_out is mocked # and doesn't sys.exit(1) pass e = 'Could not determine version of package with no installation '\ 'candidate: foo' mocked_error.assert_called_with(e) @patch.object(openstack, 'get_installed_os_version') @patch.object(openstack, 'snap_install_requested') def test_os_codename_from_bad_package_nonfatal( self, mock_snap_install_requested, mock_get_installed_os_version): """Test OpenStack codename from an unavailable package is non-fatal""" mock_snap_install_requested.return_value = False mock_get_installed_os_version.return_value = None with patch.object(openstack, 'apt_cache') as cache: cache.return_value = self._apt_cache() self.assertEquals( None, openstack.get_os_codename_package('foo', fatal=False) ) @patch.object(openstack, 'get_installed_os_version') @patch.object(openstack, 'snap_install_requested') @patch('charmhelpers.contrib.openstack.utils.error_out') def test_os_codename_from_uninstalled_package(self, mock_error, mock_snap_install_requested, mock_get_installed_os_version): """Test OpenStack codename from an available but uninstalled pkg""" mock_snap_install_requested.return_value = False mock_get_installed_os_version.return_value = None with patch.object(openstack, 'apt_cache') as cache: cache.return_value = self._apt_cache() try: openstack.get_os_codename_package('cinder-common', fatal=True) except Exception: pass e = ('Could not determine version of uninstalled package: ' 'cinder-common') mock_error.assert_called_with(e) @patch.object(openstack, 'get_installed_os_version') @patch.object(openstack, 'snap_install_requested') def test_os_codename_from_uninstalled_package_nonfatal( self, mock_snap_install_requested, mock_get_installed_os_version): """Test OpenStack codename from avail uninstalled pkg is non fatal""" mock_snap_install_requested.return_value = False mock_get_installed_os_version.return_value = None with patch.object(openstack, 'apt_cache') as cache: cache.return_value = self._apt_cache() self.assertEquals( None, openstack.get_os_codename_package('cinder-common', fatal=False) ) @patch.object(openstack, 'get_installed_os_version') @patch.object(openstack, 'snap_install_requested') @patch('charmhelpers.contrib.openstack.utils.error_out') def test_os_version_from_package(self, mocked_error, mock_snap_install_requested, mock_get_installed_os_version): """Test deriving OpenStack version from an installed package""" mock_snap_install_requested.return_value = False mock_get_installed_os_version.return_value = None with patch.object(openstack, 'apt_cache') as cache: cache.return_value = self._apt_cache() for pkg, vers in six.iteritems(FAKE_REPO): if pkg.startswith('bad-'): continue if 'pkg_vers' not in vers: continue self.assertEquals(openstack.get_os_version_package(pkg), vers['os_version']) @patch.object(openstack, 'get_installed_os_version') @patch.object(openstack, 'snap_install_requested') @patch('charmhelpers.contrib.openstack.utils.error_out') def test_os_version_from_bad_package(self, mocked_error, mock_snap_install_requested, mock_get_installed_os_version): """Test deriving OpenStack version from an uninstalled package""" mock_snap_install_requested.return_value = False mock_get_installed_os_version.return_value = None with patch.object(openstack, 'apt_cache') as cache: cache.return_value = self._apt_cache() try: openstack.get_os_version_package('foo') except Exception: # ignore exceptions that raise when error_out is mocked # and doesn't sys.exit(1) pass e = 'Could not determine version of package with no installation '\ 'candidate: foo' mocked_error.assert_called_with(e) @patch.object(openstack, 'get_installed_os_version') @patch.object(openstack, 'snap_install_requested') def test_os_version_from_bad_package_nonfatal( self, mock_snap_install_requested, mock_get_installed_os_version): """Test OpenStack version from an uninstalled package is non-fatal""" mock_snap_install_requested.return_value = False mock_get_installed_os_version.return_value = None with patch.object(openstack, 'apt_cache') as cache: cache.return_value = self._apt_cache() self.assertEquals( None, openstack.get_os_version_package('foo', fatal=False) ) @patch.object(openstack, 'lsb_release') @patch.object(openstack, 'get_os_codename_package') @patch('charmhelpers.contrib.openstack.utils.config') def test_os_release_uncached(self, config, get_cn, mock_lsb_release): openstack._os_rel = None get_cn.return_value = 'folsom' mock_lsb_release.return_value = { 'DISTRIB_CODENAME': 'bionic', } self.assertEquals('folsom', openstack.os_release('nova-common')) @patch.object(openstack, 'lsb_release') def test_os_release_cached(self, mock_lsb_release): openstack._os_rel = 'foo' mock_lsb_release.return_value = { 'DISTRIB_CODENAME': 'bionic', } self.assertEquals('foo', openstack.os_release('nova-common')) @patch.object(openstack, 'juju_log') @patch('sys.exit') def test_error_out(self, mocked_exit, juju_log): """Test erroring out""" openstack.error_out('Everything broke.') _log = 'FATAL ERROR: Everything broke.' juju_log.assert_called_with(_log, level='ERROR') mocked_exit.assert_called_with(1) def test_get_source_and_pgp_key(self): tests = { "source|key": ('source', 'key'), "source|": ('source', None), "|key": ('', 'key'), "source": ('source', None), } for k, v in six.iteritems(tests): self.assertEqual(openstack.get_source_and_pgp_key(k), v) # These should still work, even though the bulk of the functionality has # moved to charmhelpers.fetch.add_source() def test_configure_install_source_distro(self): """Test configuring installation from distro""" self.assertIsNone(openstack.configure_installation_source('distro')) def test_configure_install_source_ppa(self): """Test configuring installation source from PPA""" with patch('subprocess.check_call') as mock: src = 'ppa:gandelman-a/openstack' openstack.configure_installation_source(src) ex_cmd = [ 'add-apt-repository', '--yes', 'ppa:gandelman-a/openstack'] mock.assert_called_with(ex_cmd, env={}) @patch('subprocess.check_call') @patch.object(fetch, 'import_key') def test_configure_install_source_deb_url(self, _import, _spcc): """Test configuring installation source from deb repo url""" src = ('deb http://ubuntu-cloud.archive.canonical.com/ubuntu ' 'precise-havana main|KEYID') openstack.configure_installation_source(src) _import.assert_called_with('KEYID') _spcc.assert_called_once_with( ['add-apt-repository', '--yes', 'deb http://ubuntu-cloud.archive.canonical.com/ubuntu ' 'precise-havana main'], env={}) @patch.object(fetch, 'get_distrib_codename') @patch(builtin_open) @patch('subprocess.check_call') def test_configure_install_source_distro_proposed( self, _spcc, _open, _lsb): """Test configuring installation source from deb repo url""" _lsb.return_value = FAKE_CODENAME _file = MagicMock(spec=io.FileIO) _open.return_value = _file openstack.configure_installation_source('distro-proposed') _file.__enter__().write.assert_called_once_with( '# Proposed\ndeb http://archive.ubuntu.com/ubuntu ' 'precise-proposed main universe multiverse restricted\n') src = ('deb http://archive.ubuntu.com/ubuntu/ precise-proposed ' 'restricted main multiverse universe') openstack.configure_installation_source(src) _spcc.assert_called_once_with( ['add-apt-repository', '--yes', 'deb http://archive.ubuntu.com/ubuntu/ precise-proposed ' 'restricted main multiverse universe'], env={}) @patch('charmhelpers.fetch.filter_installed_packages') @patch('charmhelpers.fetch.apt_install') @patch.object(openstack, 'error_out') @patch.object(openstack, 'juju_log') def test_add_source_cloud_invalid_pocket(self, _log, _out, apt_install, filter_pkg): openstack.configure_installation_source("cloud:havana-updates") _e = ('Invalid Cloud Archive release specified: ' 'havana-updates on this Ubuntuversion') _s = _out.call_args[0][0] self.assertTrue(_s.startswith(_e)) @patch.object(fetch, 'filter_installed_packages') @patch.object(fetch, 'apt_install') @patch.object(fetch, 'get_distrib_codename') def test_add_source_cloud_pocket_style(self, get_distrib_codename, apt_install, filter_pkg): source = "cloud:precise-updates/havana" get_distrib_codename.return_value = 'precise' result = ( "# Ubuntu Cloud Archive\n" "deb http://ubuntu-cloud.archive.canonical.com/ubuntu " "precise-updates/havana main\n") with patch_open() as (mock_open, mock_file): openstack.configure_installation_source(source) mock_file.write.assert_called_with(result) filter_pkg.assert_called_with(['ubuntu-cloud-keyring']) @patch.object(fetch, 'filter_installed_packages') @patch.object(fetch, 'apt_install') @patch.object(fetch, 'get_distrib_codename') def test_add_source_cloud_os_style(self, get_distrib_codename, apt_install, filter_pkg): source = "cloud:precise-havana" get_distrib_codename.return_value = 'precise' result = ( "# Ubuntu Cloud Archive\n" "deb http://ubuntu-cloud.archive.canonical.com/ubuntu " "precise-updates/havana main\n") with patch_open() as (mock_open, mock_file): openstack.configure_installation_source(source) mock_file.write.assert_called_with(result) filter_pkg.assert_called_with(['ubuntu-cloud-keyring']) @patch.object(fetch, 'filter_installed_packages') @patch.object(fetch, 'apt_install') def test_add_source_cloud_distroless_style(self, apt_install, filter_pkg): source = "cloud:havana" result = ( "# Ubuntu Cloud Archive\n" "deb http://ubuntu-cloud.archive.canonical.com/ubuntu " "precise-updates/havana main\n") with patch_open() as (mock_open, mock_file): openstack.configure_installation_source(source) mock_file.write.assert_called_with(result) filter_pkg.assert_called_with(['ubuntu-cloud-keyring']) @patch('charmhelpers.fetch.ubuntu.log', lambda *args, **kwargs: None) @patch('charmhelpers.contrib.openstack.utils.juju_log', lambda *args, **kwargs: None) @patch('charmhelpers.contrib.openstack.utils.error_out') def test_configure_bad_install_source(self, _error): openstack.configure_installation_source('foo') _error.assert_called_with("Unknown source: 'foo'") @patch.object(fetch, 'get_distrib_codename') def test_configure_install_source_uca_staging(self, _lsb): """Test configuring installation source from UCA staging sources""" _lsb.return_value = FAKE_CODENAME # staging pockets are configured as PPAs with patch('subprocess.check_call') as _subp: src = 'cloud:precise-folsom/staging' openstack.configure_installation_source(src) cmd = ['add-apt-repository', '-y', 'ppa:ubuntu-cloud-archive/folsom-staging'] _subp.assert_called_with(cmd, env={}) @patch(builtin_open) @patch.object(fetch, 'apt_install') @patch.object(fetch, 'get_distrib_codename') @patch.object(fetch, 'filter_installed_packages') def test_configure_install_source_uca_repos( self, _fip, _lsb, _install, _open): """Test configuring installation source from UCA sources""" _lsb.return_value = FAKE_CODENAME _file = MagicMock(spec=io.FileIO) _open.return_value = _file _fip.side_effect = lambda x: x for src, url in UCA_SOURCES: actual_url = "# Ubuntu Cloud Archive\n{}\n".format(url) openstack.configure_installation_source(src) _install.assert_called_with(['ubuntu-cloud-keyring'], fatal=True) _open.assert_called_with( '/etc/apt/sources.list.d/cloud-archive.list', 'w' ) _file.__enter__().write.assert_called_with(actual_url) @patch('charmhelpers.contrib.openstack.utils.error_out') def test_configure_install_source_bad_uca(self, mocked_error): """Test configuring installation source from bad UCA source""" try: openstack.configure_installation_source('cloud:foo-bar') except Exception: # ignore exceptions that raise when error_out is mocked # and doesn't sys.exit(1) pass _e = ('Invalid Cloud Archive release specified: foo-bar' ' on this Ubuntuversion') _s = mocked_error.call_args[0][0] self.assertTrue(_s.startswith(_e)) @patch.object(openstack, 'fetch_import_key') def test_import_key_calls_fetch_import_key(self, fetch_import_key): openstack.import_key('random-string') fetch_import_key.assert_called_once_with('random-string') @patch.object(openstack, 'juju_log', lambda *args, **kwargs: None) @patch.object(openstack, 'fetch_import_key') @patch.object(openstack, 'sys') def test_import_key_calls_sys_exit_on_error(self, mock_sys, fetch_import_key): def raiser(_): raise openstack.GPGKeyError("an error occurred") fetch_import_key.side_effect = raiser openstack.import_key('random failure') mock_sys.exit.assert_called_once_with(1) @patch('os.mkdir') @patch('os.path.exists') @patch('charmhelpers.contrib.openstack.utils.charm_dir') @patch(builtin_open) def test_save_scriptrc(self, _open, _charm_dir, _exists, _mkdir): """Test generation of scriptrc from environment""" scriptrc = ['#!/bin/bash\n', 'export setting1=foo\n', 'export setting2=bar\n'] _file = MagicMock(spec=io.FileIO) _open.return_value = _file _charm_dir.return_value = '/var/lib/juju/units/testing-foo-0/charm' _exists.return_value = False os.environ['JUJU_UNIT_NAME'] = 'testing-foo/0' openstack.save_script_rc(setting1='foo', setting2='bar') rcdir = '/var/lib/juju/units/testing-foo-0/charm/scripts' _mkdir.assert_called_with(rcdir) expected_f = '/var/lib/juju/units/testing-foo-0/charm/scripts/scriptrc' _open.assert_called_with(expected_f, 'wt') _mkdir.assert_called_with(os.path.dirname(expected_f)) _file.__enter__().write.assert_has_calls( list(call(line) for line in scriptrc), any_order=True) @patch.object(openstack, 'lsb_release') @patch.object(openstack, 'get_os_version_package') @patch.object(openstack, 'get_os_version_codename_swift') @patch.object(openstack, 'config') def test_openstack_upgrade_detection_true(self, config, vers_swift, vers_pkg, lsb): """Test it detects when an openstack package has available upgrade""" lsb.return_value = FAKE_RELEASE config.return_value = 'cloud:precise-havana' vers_pkg.return_value = '2013.1.1' self.assertTrue(openstack.openstack_upgrade_available('nova-common')) # milestone to major release detection vers_pkg.return_value = '2013.2~b1' self.assertTrue(openstack.openstack_upgrade_available('nova-common')) vers_pkg.return_value = '1.9.0' vers_swift.return_value = '2.5.0' self.assertTrue(openstack.openstack_upgrade_available('swift-proxy')) vers_pkg.return_value = '2.5.0' vers_swift.return_value = '2.10.0' self.assertTrue(openstack.openstack_upgrade_available('swift-proxy')) @patch.object(openstack, 'lsb_release') @patch.object(openstack, 'get_os_version_package') @patch.object(openstack, 'config') def test_openstack_upgrade_detection_false(self, config, vers_pkg, lsb): """Test it detects when an openstack upgrade is not necessary""" lsb.return_value = FAKE_RELEASE config.return_value = 'cloud:precise-folsom' vers_pkg.return_value = '2013.1.1' self.assertFalse(openstack.openstack_upgrade_available('nova-common')) # milestone to majro release detection vers_pkg.return_value = '2013.1~b1' self.assertFalse(openstack.openstack_upgrade_available('nova-common')) # ugly duckling testing config.return_value = 'cloud:precise-havana' vers_pkg.return_value = '1.10.0' self.assertFalse(openstack.openstack_upgrade_available('swift-proxy')) @patch.object(openstack, 'is_block_device') @patch.object(openstack, 'error_out') def test_ensure_block_device_bad_config(self, err, is_bd): """Test it doesn't prepare storage with bad config""" openstack.ensure_block_device(block_device='none') self.assertTrue(err.called) @patch.object(openstack, 'is_block_device') @patch.object(openstack, 'ensure_loopback_device') def test_ensure_block_device_loopback(self, ensure_loopback, is_bd): """Test it ensures loopback device when checking block device""" defsize = openstack.DEFAULT_LOOPBACK_SIZE is_bd.return_value = True ensure_loopback.return_value = '/tmp/cinder.img' result = openstack.ensure_block_device('/tmp/cinder.img') ensure_loopback.assert_called_with('/tmp/cinder.img', defsize) self.assertEquals(result, '/tmp/cinder.img') ensure_loopback.return_value = '/tmp/cinder-2.img' result = openstack.ensure_block_device('/tmp/cinder-2.img|15G') ensure_loopback.assert_called_with('/tmp/cinder-2.img', '15G') self.assertEquals(result, '/tmp/cinder-2.img') @patch.object(openstack, 'is_block_device') def test_ensure_standard_block_device(self, is_bd): """Test it looks for storage at both relative and full device path""" for dev in ['vdb', '/dev/vdb']: openstack.ensure_block_device(dev) is_bd.assert_called_with('/dev/vdb') @patch.object(openstack, 'is_block_device') @patch.object(openstack, 'error_out') def test_ensure_nonexistent_block_device(self, error_out, is_bd): """Test it will not ensure a non-existent block device""" is_bd.return_value = False openstack.ensure_block_device(block_device='foo') self.assertTrue(error_out.called) @patch.object(openstack, 'juju_log') @patch.object(openstack, 'umount') @patch.object(openstack, 'mounts') @patch.object(openstack, 'zap_disk') @patch.object(openstack, 'is_lvm_physical_volume') def test_clean_storage_unmount(self, is_pv, zap_disk, mounts, umount, log): """Test it unmounts block device when cleaning storage""" is_pv.return_value = False zap_disk.return_value = True mounts.return_value = MOUNTS openstack.clean_storage('/dev/vdb') umount.called_with('/dev/vdb', True) @patch.object(openstack, 'juju_log') @patch.object(openstack, 'remove_lvm_physical_volume') @patch.object(openstack, 'deactivate_lvm_volume_group') @patch.object(openstack, 'mounts') @patch.object(openstack, 'is_lvm_physical_volume') def test_clean_storage_lvm_wipe(self, is_pv, mounts, rm_lv, rm_vg, log): """Test it removes traces of LVM when cleaning storage""" mounts.return_value = [] is_pv.return_value = True openstack.clean_storage('/dev/vdb') rm_lv.assert_called_with('/dev/vdb') rm_vg .assert_called_with('/dev/vdb') @patch.object(openstack, 'zap_disk') @patch.object(openstack, 'is_lvm_physical_volume') @patch.object(openstack, 'mounts') def test_clean_storage_zap_disk(self, mounts, is_pv, zap_disk): """It removes traces of LVM when cleaning storage""" mounts.return_value = [] is_pv.return_value = False openstack.clean_storage('/dev/vdb') zap_disk.assert_called_with('/dev/vdb') @patch('os.path.isfile') @patch(builtin_open) def test_get_matchmaker_map(self, _open, _isfile): _isfile.return_value = True mm_data = """ { "cinder-scheduler": [ "juju-t-machine-4" ] } """ fh = _open.return_value.__enter__.return_value fh.read.return_value = mm_data self.assertEqual( openstack.get_matchmaker_map(), {'cinder-scheduler': ['juju-t-machine-4']} ) @patch('os.path.isfile') @patch(builtin_open) def test_get_matchmaker_map_nofile(self, _open, _isfile): _isfile.return_value = False self.assertEqual( openstack.get_matchmaker_map(), {} ) def test_incomplete_relation_data(self): configs = MagicMock() configs.complete_contexts.return_value = ['pgsql-db', 'amqp'] required_interfaces = { 'database': ['shared-db', 'pgsql-db'], 'message': ['amqp', 'zeromq-configuration'], 'identity': ['identity-service']} expected_result = 'identity' result = openstack.incomplete_relation_data( configs, required_interfaces) self.assertTrue(expected_result in result.keys()) @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_complete( self, is_unit_paused_set, status_set, log): configs = MagicMock() configs.complete_contexts.return_value = ['shared-db', 'amqp', 'identity-service'] required_interfaces = { 'database': ['shared-db', 'pgsql-db'], 'message': ['amqp', 'zeromq-configuration'], 'identity': ['identity-service']} openstack.set_os_workload_status(configs, required_interfaces) status_set.assert_called_with('active', 'Unit is ready') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.incomplete_relation_data', return_value={'identity': {'identity-service': {'related': True}}}) @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_related_incomplete( self, is_unit_paused_set, status_set, incomplete_relation_data, log): configs = MagicMock() configs.complete_contexts.return_value = ['shared-db', 'amqp'] required_interfaces = { 'database': ['shared-db', 'pgsql-db'], 'message': ['amqp', 'zeromq-configuration'], 'identity': ['identity-service']} openstack.set_os_workload_status(configs, required_interfaces) status_set.assert_called_with('waiting', "Incomplete relations: identity") @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.incomplete_relation_data', return_value={'identity': {'identity-service': {'related': False}}}) @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_absent( self, is_unit_paused_set, status_set, incomplete_relation_data, log): configs = MagicMock() configs.complete_contexts.return_value = ['shared-db', 'amqp'] required_interfaces = { 'database': ['shared-db', 'pgsql-db'], 'message': ['amqp', 'zeromq-configuration'], 'identity': ['identity-service']} openstack.set_os_workload_status(configs, required_interfaces) status_set.assert_called_with('blocked', 'Missing relations: identity') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.hook_name', return_value='identity-service-relation-broken') @patch('charmhelpers.contrib.openstack.utils.incomplete_relation_data', return_value={'identity': {'identity-service': {'related': True}}}) @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_related_broken( self, is_unit_paused_set, status_set, incomplete_relation_data, hook_name, log): configs = MagicMock() configs.complete_contexts.return_value = ['shared-db', 'amqp'] required_interfaces = { 'database': ['shared-db', 'pgsql-db'], 'message': ['amqp', 'zeromq-configuration'], 'identity': ['identity-service']} openstack.set_os_workload_status(configs, required_interfaces) status_set.assert_called_with('blocked', "Missing relations: identity") @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.incomplete_relation_data', return_value={'identity': {'identity-service': {'related': True}}, 'message': {'amqp': {'missing_data': ['rabbitmq-password'], 'related': True}}, 'database': {'shared-db': {'related': False}} }) @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_mixed( self, is_unit_paused_set, status_set, incomplete_relation_data, log): configs = MagicMock() configs.complete_contexts.return_value = ['shared-db', 'amqp'] required_interfaces = { 'database': ['shared-db', 'pgsql-db'], 'message': ['amqp', 'zeromq-configuration'], 'identity': ['identity-service']} openstack.set_os_workload_status(configs, required_interfaces) args = status_set.call_args actual_parm1 = args[0][0] actual_parm2 = args[0][1] expected1 = ("Missing relations: database; incomplete relations: " "identity, message") expected2 = ("Missing relations: database; incomplete relations: " "message, identity") self.assertTrue(actual_parm1 == 'blocked') self.assertTrue(actual_parm2 == expected1 or actual_parm2 == expected2) @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_complete_with_services_list( self, is_unit_paused_set, status_set, log, port_has_listener, service_running): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} services = ['database', 'identity'] # Assume that the service and ports are open. port_has_listener.return_value = True service_running.return_value = True openstack.set_os_workload_status( configs, required_interfaces, services=services) status_set.assert_called_with('active', 'Unit is ready') @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_complete_services_list_not_running( self, is_unit_paused_set, status_set, log, port_has_listener, service_running): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} services = ['database', 'identity'] port_has_listener.return_value = True # Fail the identity service service_running.side_effect = [True, False] openstack.set_os_workload_status( configs, required_interfaces, services=services) status_set.assert_called_with( 'blocked', 'Services not running that should be: identity') @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_complete_with_services( self, is_unit_paused_set, status_set, log, port_has_listener, service_running): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} services = [ {'service': 'database', 'ports': [10, 20]}, {'service': 'identity', 'ports': [30]}, ] # Assume that the service and ports are open. port_has_listener.return_value = True service_running.return_value = True openstack.set_os_workload_status( configs, required_interfaces, services=services) status_set.assert_called_with('active', 'Unit is ready') @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_complete_service_not_running( self, is_unit_paused_set, status_set, log, port_has_listener, service_running): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} services = [ {'service': 'database', 'ports': [10, 20]}, {'service': 'identity', 'ports': [30]}, ] port_has_listener.return_value = True # Fail the identity service service_running.side_effect = [True, False] openstack.set_os_workload_status( configs, required_interfaces, services=services) status_set.assert_called_with( 'blocked', 'Services not running that should be: identity') @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_complete_port_not_open( self, is_unit_paused_set, status_set, log, port_has_listener, service_running): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} services = [ {'service': 'database', 'ports': [10, 20]}, {'service': 'identity', 'ports': [30]}, ] port_has_listener.side_effect = [True, False, True] # Fail the identity service service_running.return_value = True openstack.set_os_workload_status( configs, required_interfaces, services=services) status_set.assert_called_with( 'blocked', 'Services with ports not open that should be:' ' database: [20]') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=False) def test_set_os_workload_status_complete_ports_not_open( self, is_unit_paused_set, status_set, log, port_has_listener): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} ports = [50, 60, 70] port_has_listener.side_effect = [True, False, True] openstack.set_os_workload_status( configs, required_interfaces, ports=ports) status_set.assert_called_with( 'blocked', 'Ports which should be open, but are not: 60') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=True) def test_set_os_workload_status_paused_simple( self, is_unit_paused_set, status_set, log): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} openstack.set_os_workload_status(configs, required_interfaces) status_set.assert_called_with( 'maintenance', "Paused. Use 'resume' action to resume normal service.") @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=True) def test_set_os_workload_status_paused_services_check( self, is_unit_paused_set, status_set, log, port_has_listener, service_running): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} services = [ {'service': 'database', 'ports': [10, 20]}, {'service': 'identity', 'ports': [30]}, ] port_has_listener.return_value = False service_running.side_effect = [False, False] openstack.set_os_workload_status( configs, required_interfaces, services=services) status_set.assert_called_with( 'maintenance', "Paused. Use 'resume' action to resume normal service.") @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=True) def test_set_os_workload_status_paused_services_fail( self, is_unit_paused_set, status_set, log, port_has_listener, service_running): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} services = [ {'service': 'database', 'ports': [10, 20]}, {'service': 'identity', 'ports': [30]}, ] port_has_listener.return_value = False # Fail the identity service service_running.side_effect = [False, True] openstack.set_os_workload_status( configs, required_interfaces, services=services) status_set.assert_called_with( 'blocked', "Services should be paused but these services running: identity") @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=True) def test_set_os_workload_status_paused_services_ports_fail( self, is_unit_paused_set, status_set, log, port_has_listener, service_running): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} services = [ {'service': 'database', 'ports': [10, 20]}, {'service': 'identity', 'ports': [30]}, ] # make the service 20 port be still listening. port_has_listener.side_effect = [False, True, False] service_running.return_value = False openstack.set_os_workload_status( configs, required_interfaces, services=services) status_set.assert_called_with( 'blocked', "Services should be paused but these service:ports are open:" " database: [20]") @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=True) def test_set_os_workload_status_paused_ports_check( self, is_unit_paused_set, status_set, log, port_has_listener): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} ports = [50, 60, 70] port_has_listener.side_effect = [False, False, False] openstack.set_os_workload_status( configs, required_interfaces, ports=ports) status_set.assert_called_with( 'maintenance', "Paused. Use 'resume' action to resume normal service.") @patch('charmhelpers.contrib.openstack.utils.port_has_listener') @patch.object(openstack, 'juju_log') @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.is_unit_paused_set', return_value=True) def test_set_os_workload_status_paused_ports_fail( self, is_unit_paused_set, status_set, log, port_has_listener): configs = MagicMock() configs.complete_contexts.return_value = [] required_interfaces = {} # fail port 70 to make it seem to be running ports = [50, 60, 70] port_has_listener.side_effect = [False, False, True] openstack.set_os_workload_status( configs, required_interfaces, ports=ports) status_set.assert_called_with( 'blocked', "Services should be paused but " "these ports which should be closed, but are open: 70") @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') def test_check_actually_paused_simple_services( self, port_has_listener, service_running): services = ['database', 'identity'] port_has_listener.return_value = False service_running.return_value = False state, message = openstack.check_actually_paused( services) self.assertEquals(state, None) self.assertEquals(message, None) @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') def test_check_actually_paused_simple_services_fail( self, port_has_listener, service_running): services = ['database', 'identity'] port_has_listener.return_value = False service_running.side_effect = [False, True] state, message = openstack.check_actually_paused( services) self.assertEquals(state, 'blocked') self.assertEquals( message, "Services should be paused but these services running: identity") @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') def test_check_actually_paused_services_dict( self, port_has_listener, service_running): services = [ {'service': 'database', 'ports': [10, 20]}, {'service': 'identity', 'ports': [30]}, ] # Assume that the service and ports are open. port_has_listener.return_value = False service_running.return_value = False state, message = openstack.check_actually_paused( services) self.assertEquals(state, None) self.assertEquals(message, None) @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') def test_check_actually_paused_services_dict_fail( self, port_has_listener, service_running): services = [ {'service': 'database', 'ports': [10, 20]}, {'service': 'identity', 'ports': [30]}, ] # Assume that the service and ports are open. port_has_listener.return_value = False service_running.side_effect = [False, True] state, message = openstack.check_actually_paused( services) self.assertEquals(state, 'blocked') self.assertEquals( message, "Services should be paused but these services running: identity") @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') def test_check_actually_paused_services_dict_ports_fail( self, port_has_listener, service_running): services = [ {'service': 'database', 'ports': [10, 20]}, {'service': 'identity', 'ports': [30]}, ] # Assume that the service and ports are open. port_has_listener.side_effect = [False, True, False] service_running.return_value = False state, message = openstack.check_actually_paused( services) self.assertEquals(state, 'blocked') self.assertEquals(message, 'Services should be paused but these service:ports' ' are open: database: [20]') @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') def test_check_actually_paused_ports_okay( self, port_has_listener, service_running): port_has_listener.side_effect = [False, False, False] service_running.return_value = False ports = [50, 60, 70] state, message = openstack.check_actually_paused( ports=ports) self.assertEquals(state, None) self.assertEquals(state, None) @patch('charmhelpers.contrib.openstack.utils.service_running') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') def test_check_actually_paused_ports_fail( self, port_has_listener, service_running): port_has_listener.side_effect = [False, True, False] service_running.return_value = False ports = [50, 60, 70] state, message = openstack.check_actually_paused( ports=ports) self.assertEquals(state, 'blocked') self.assertEquals(message, 'Services should be paused but these ports ' 'which should be closed, but are open: 60') @staticmethod def _unit_paused_helper(hook_data_mock): # HookData()() returns a tuple (kv, delta_config, delta_relation) # but we only want kv in the test. kv = MagicMock() @contextlib.contextmanager def hook_data__call__(): yield (kv, True, False) hook_data__call__.return_value = (kv, True, False) hook_data_mock.return_value = hook_data__call__ return kv @patch('charmhelpers.contrib.openstack.utils.unitdata.HookData') def test_set_unit_paused(self, hook_data): kv = self._unit_paused_helper(hook_data) openstack.set_unit_paused() kv.set.assert_called_once_with('unit-paused', True) @patch('charmhelpers.contrib.openstack.utils.unitdata.HookData') def test_set_unit_upgrading(self, hook_data): kv = self._unit_paused_helper(hook_data) openstack.set_unit_upgrading() kv.set.assert_called_once_with('unit-upgrading', True) @patch('charmhelpers.contrib.openstack.utils.unitdata.HookData') def test_clear_unit_paused(self, hook_data): kv = self._unit_paused_helper(hook_data) openstack.clear_unit_paused() kv.set.assert_called_once_with('unit-paused', False) @patch('charmhelpers.contrib.openstack.utils.unitdata.HookData') def test_clear_unit_upgrading(self, hook_data): kv = self._unit_paused_helper(hook_data) openstack.clear_unit_upgrading() kv.set.assert_called_once_with('unit-upgrading', False) @patch('charmhelpers.contrib.openstack.utils.unitdata.HookData') def test_is_unit_paused_set(self, hook_data): kv = self._unit_paused_helper(hook_data) kv.get.return_value = True r = openstack.is_unit_paused_set() kv.get.assert_called_once_with('unit-paused') self.assertEquals(r, True) kv.get.return_value = False r = openstack.is_unit_paused_set() self.assertEquals(r, False) @patch('charmhelpers.contrib.openstack.utils.unitdata.HookData') def test_is_unit_upgrading_set(self, hook_data): kv = self._unit_paused_helper(hook_data) kv.get.return_value = True r = openstack.is_unit_upgrading_set() kv.get.assert_called_once_with('unit-upgrading') self.assertEquals(r, True) kv.get.return_value = False r = openstack.is_unit_upgrading_set() self.assertEquals(r, False) @patch.object(openstack, 'config') @patch.object(openstack, 'is_unit_paused_set') @patch.object(openstack.deferred_events, 'is_restart_permitted') @patch.object(openstack.deferred_events, 'set_deferred_hook') @patch.object(openstack.deferred_events, 'clear_deferred_hook') def test_is_hook_allowed(self, clear_deferred_hook, set_deferred_hook, is_restart_permitted, is_unit_paused_set, config): # Test unit not paused and not checking whether restarts are allowed is_unit_paused_set.return_value = False self.assertEqual( openstack.is_hook_allowed( 'config-changed', check_deferred_restarts=False), (True, '')) self.assertFalse(clear_deferred_hook.called) # Test unit paused and not checking whether restarts are allowed is_unit_paused_set.return_value = True self.assertEqual( openstack.is_hook_allowed( 'config-changed', check_deferred_restarts=False), (False, 'Unit is pause or upgrading. Skipping config-changed')) # Test unit not paused and restarts allowed clear_deferred_hook.reset_mock() is_unit_paused_set.return_value = False is_restart_permitted.return_value = True self.assertEqual( openstack.is_hook_allowed( 'config-changed', check_deferred_restarts=True), (True, '')) clear_deferred_hook.assert_called_once_with('config-changed') # Test unit not paused and restarts not allowed # enable-auto-restarts not enabled as part of this hook clear_deferred_hook.reset_mock() set_deferred_hook.reset_mock() is_unit_paused_set.return_value = False is_restart_permitted.return_value = False config().changed.return_value = False self.assertEqual( openstack.is_hook_allowed( 'config-changed', check_deferred_restarts=True), (False, 'auto restarts are disabled')) self.assertFalse(clear_deferred_hook.called) set_deferred_hook.assert_called_once_with('config-changed') # Test unit not paused and restarts not allowed. # enable-auto-restarts enabled as part of this hook clear_deferred_hook.reset_mock() set_deferred_hook.reset_mock() is_unit_paused_set.return_value = False is_restart_permitted.return_value = False config().changed.return_value = True self.assertEqual( openstack.is_hook_allowed( 'config-changed', check_deferred_restarts=True), (False, 'auto restarts are disabled')) self.assertFalse(clear_deferred_hook.called) # Test unit paused and restarts not allowed # enable-auto-restarts not enabled as part of this hook clear_deferred_hook.reset_mock() set_deferred_hook.reset_mock() is_unit_paused_set.return_value = True is_restart_permitted.return_value = False config().changed.return_value = False self.assertEqual( openstack.is_hook_allowed( 'config-changed', check_deferred_restarts=True), (False, 'Unit is pause or upgrading. Skipping config-changed and ' 'auto restarts are disabled')) self.assertFalse(clear_deferred_hook.called) set_deferred_hook.assert_called_once_with('config-changed') @patch('charmhelpers.contrib.openstack.utils.service_stop') def test_manage_payload_services_ok(self, service_stop): services = ['service1', 'service2'] service_stop.side_effect = [True, True] self.assertEqual( openstack.manage_payload_services('stop', services=services), (True, [])) @patch('charmhelpers.contrib.openstack.utils.service_stop') def test_manage_payload_services_fails(self, service_stop): services = ['service1', 'service2'] service_stop.side_effect = [True, False] self.assertEqual( openstack.manage_payload_services('stop', services=services), (False, ["service2 didn't stop cleanly."])) @patch('charmhelpers.contrib.openstack.utils.service_stop') def test_manage_payload_services_charm_func(self, service_stop): bespoke_func = MagicMock() bespoke_func.return_value = None services = ['service1', 'service2'] service_stop.side_effect = [True, True] self.assertEqual( openstack.manage_payload_services('stop', services=services, charm_func=bespoke_func), (True, [])) bespoke_func.assert_called_once_with() @patch('charmhelpers.contrib.openstack.utils.service_stop') def test_manage_payload_services_charm_func_msg(self, service_stop): bespoke_func = MagicMock() bespoke_func.return_value = 'it worked' services = ['service1', 'service2'] service_stop.side_effect = [True, True] self.assertEqual( openstack.manage_payload_services('stop', services=services, charm_func=bespoke_func), (True, ['it worked'])) bespoke_func.assert_called_once_with() @patch('charmhelpers.contrib.openstack.utils.service_stop') def test_manage_payload_services_charm_func_fails(self, service_stop): bespoke_func = MagicMock() bespoke_func.side_effect = Exception('it failed') services = ['service1', 'service2'] service_stop.side_effect = [True, True] self.assertEqual( openstack.manage_payload_services('stop', services=services, charm_func=bespoke_func), (False, ['it failed'])) bespoke_func.assert_called_once_with() def test_manage_payload_services_wrong_action(self): self.assertRaises( RuntimeError, openstack.manage_payload_services, 'mangle') @patch('charmhelpers.contrib.openstack.utils.service_pause') @patch('charmhelpers.contrib.openstack.utils.set_unit_paused') def test_pause_unit_okay(self, set_unit_paused, service_pause): services = ['service1', 'service2'] service_pause.side_effect = [True, True] openstack.pause_unit(None, services=services) set_unit_paused.assert_called_once_with() self.assertEquals(service_pause.call_count, 2) @patch('charmhelpers.contrib.openstack.utils.service_pause') @patch('charmhelpers.contrib.openstack.utils.set_unit_paused') def test_pause_unit_service_fails(self, set_unit_paused, service_pause): services = ['service1', 'service2'] service_pause.side_effect = [True, True] openstack.pause_unit(None, services=services) set_unit_paused.assert_called_once_with() self.assertEquals(service_pause.call_count, 2) # Fail the 2nd service service_pause.side_effect = [True, False] try: openstack.pause_unit(None, services=services) raise Exception("pause_unit should have raised Exception") except Exception as e: self.assertEquals(e.args[0], "Couldn't pause: service2 didn't pause cleanly.") @patch('charmhelpers.contrib.openstack.utils.service_pause') @patch('charmhelpers.contrib.openstack.utils.set_unit_paused') def test_pause_unit_service_charm_func( self, set_unit_paused, service_pause): services = ['service1', 'service2'] service_pause.return_value = True charm_func = MagicMock() charm_func.return_value = None openstack.pause_unit(None, services=services, charm_func=charm_func) charm_func.assert_called_once_with() # fail the charm_func charm_func.return_value = "Custom charm failed" try: openstack.pause_unit( None, services=services, charm_func=charm_func) raise Exception("pause_unit should have raised Exception") except Exception as e: self.assertEquals(e.args[0], "Couldn't pause: Custom charm failed") @patch('charmhelpers.contrib.openstack.utils.service_pause') @patch('charmhelpers.contrib.openstack.utils.set_unit_paused') def test_pause_unit_assess_status_func( self, set_unit_paused, service_pause): services = ['service1', 'service2'] service_pause.return_value = True assess_status_func = MagicMock() assess_status_func.return_value = None openstack.pause_unit(assess_status_func, services=services) assess_status_func.assert_called_once_with() # fail the assess_status_func assess_status_func.return_value = "assess_status_func failed" try: openstack.pause_unit(assess_status_func, services=services) raise Exception("pause_unit should have raised Exception") except Exception as e: self.assertEquals(e.args[0], "Couldn't pause: assess_status_func failed") @patch('charmhelpers.contrib.openstack.utils.service_pause') @patch('charmhelpers.contrib.openstack.utils.set_unit_paused') @patch('charmhelpers.contrib.openstack.utils.port_has_listener') def test_pause_unit_retry_port_check_retries( self, port_has_listener, set_unit_paused, service_pause): service_pause.return_value = True port_has_listener.side_effect = [True, False] wait_for_ports_func = openstack.make_wait_for_ports_barrier([77]) openstack.pause_unit(None, services=['service1'], ports=[77], charm_func=wait_for_ports_func) port_has_listener.assert_has_calls([call('0.0.0.0', 77), call('0.0.0.0', 77)]) @patch('charmhelpers.contrib.openstack.utils.service_resume') @patch('charmhelpers.contrib.openstack.utils.clear_unit_paused') def test_resume_unit_okay(self, clear_unit_paused, service_resume): services = ['service1', 'service2'] service_resume.side_effect = [True, True] openstack.resume_unit(None, services=services) clear_unit_paused.assert_called_once_with() self.assertEquals(service_resume.call_count, 2) @patch('charmhelpers.contrib.openstack.utils.service_resume') @patch('charmhelpers.contrib.openstack.utils.clear_unit_paused') def test_resume_unit_service_fails( self, clear_unit_paused, service_resume): services = ['service1', 'service2'] service_resume.side_effect = [True, True] openstack.resume_unit(None, services=services) clear_unit_paused.assert_called_once_with() self.assertEquals(service_resume.call_count, 2) # Fail the 2nd service service_resume.side_effect = [True, False] try: openstack.resume_unit(None, services=services) raise Exception("resume_unit should have raised Exception") except Exception as e: self.assertEquals( e.args[0], "Couldn't resume: service2 didn't resume cleanly.") @patch('charmhelpers.contrib.openstack.utils.service_resume') @patch('charmhelpers.contrib.openstack.utils.clear_unit_paused') def test_resume_unit_service_charm_func( self, clear_unit_paused, service_resume): services = ['service1', 'service2'] service_resume.return_value = True charm_func = MagicMock() charm_func.return_value = None openstack.resume_unit(None, services=services, charm_func=charm_func) charm_func.assert_called_once_with() # fail the charm_func charm_func.return_value = "Custom charm failed" try: openstack.resume_unit( None, services=services, charm_func=charm_func) raise Exception("resume_unit should have raised Exception") except Exception as e: self.assertEquals(e.args[0], "Couldn't resume: Custom charm failed") @patch('charmhelpers.contrib.openstack.utils.service_resume') @patch('charmhelpers.contrib.openstack.utils.clear_unit_paused') def test_resume_unit_assess_status_func( self, clear_unit_paused, service_resume): services = ['service1', 'service2'] service_resume.return_value = True assess_status_func = MagicMock() assess_status_func.return_value = None openstack.resume_unit(assess_status_func, services=services) assess_status_func.assert_called_once_with() # fail the assess_status_func assess_status_func.return_value = "assess_status_func failed" try: openstack.resume_unit(assess_status_func, services=services) raise Exception("resume_unit should have raised Exception") except Exception as e: self.assertEquals(e.args[0], "Couldn't resume: assess_status_func failed") @patch('charmhelpers.contrib.openstack.utils.status_set') @patch('charmhelpers.contrib.openstack.utils.' '_determine_os_workload_status') def test_make_assess_status_func(self, _determine_os_workload_status, status_set): _determine_os_workload_status.return_value = ('active', 'fine') f = openstack.make_assess_status_func('one', 'two', three='three') r = f() self.assertEquals(r, None) _determine_os_workload_status.assert_called_once_with( 'one', 'two', three='three') status_set.assert_called_once_with('active', 'fine') # return something other than 'active' or 'maintenance' _determine_os_workload_status.return_value = ('broken', 'damaged') r = f() self.assertEquals(r, 'damaged') # TODO(ajkavanagh) -- there should be a test for # _determine_os_workload_status() as the policyd override code has changed # it, but there wasn't a test previously. @patch.object(openstack, 'restart_on_change_helper') @patch.object(openstack, 'is_unit_paused_set') def test_pausable_restart_on_change( self, is_unit_paused_set, restart_on_change_helper): @openstack.pausable_restart_on_change({}) def test_func(): pass # test with pause: restart_on_change_helper should not be called. is_unit_paused_set.return_value = True test_func() self.assertEquals(restart_on_change_helper.call_count, 0) # test without pause: restart_on_change_helper should be called. is_unit_paused_set.return_value = False test_func() self.assertEquals(restart_on_change_helper.call_count, 1) @patch.object(openstack, 'restart_on_change_helper') @patch.object(openstack, 'is_unit_paused_set') def test_pausable_restart_on_change_with_callable( self, is_unit_paused_set, restart_on_change_helper): mock_test = MagicMock() mock_test.called_set = False def _restart_map(): mock_test.called_set = True return {"a": "b"} @openstack.pausable_restart_on_change(_restart_map) def test_func(): pass self.assertFalse(mock_test.called_set) is_unit_paused_set.return_value = False test_func() self.assertEquals(restart_on_change_helper.call_count, 1) self.assertTrue(mock_test.called_set) @patch.object(openstack, 'juju_log') @patch.object(openstack, 'action_set') @patch.object(openstack, 'action_fail') @patch.object(openstack, 'openstack_upgrade_available') @patch('charmhelpers.contrib.openstack.utils.config') def test_openstack_upgrade(self, config, openstack_upgrade_available, action_fail, action_set, log): def do_openstack_upgrade(configs): pass openstack_upgrade_available.return_value = True # action-managed-upgrade=True config.side_effect = [True] openstack.do_action_openstack_upgrade('package-xyz', do_openstack_upgrade, None) self.assertTrue(openstack_upgrade_available.called) msg = ('success, upgrade completed.') action_set.assert_called_with({'outcome': msg}) self.assertFalse(action_fail.called) @patch.object(openstack, 'juju_log') @patch.object(openstack, 'action_set') @patch.object(openstack, 'action_fail') @patch.object(openstack, 'openstack_upgrade_available') @patch('charmhelpers.contrib.openstack.utils.config') def test_openstack_upgrade_not_avail(self, config, openstack_upgrade_available, action_fail, action_set, log): def do_openstack_upgrade(configs): pass openstack_upgrade_available.return_value = False openstack.do_action_openstack_upgrade('package-xyz', do_openstack_upgrade, None) self.assertTrue(openstack_upgrade_available.called) msg = ('no upgrade available.') action_set.assert_called_with({'outcome': msg}) self.assertFalse(action_fail.called) @patch.object(openstack, 'juju_log') @patch.object(openstack, 'action_set') @patch.object(openstack, 'action_fail') @patch.object(openstack, 'openstack_upgrade_available') @patch('charmhelpers.contrib.openstack.utils.config') def test_openstack_upgrade_config_false(self, config, openstack_upgrade_available, action_fail, action_set, log): def do_openstack_upgrade(configs): pass openstack_upgrade_available.return_value = True # action-managed-upgrade=False config.side_effect = [False] openstack.do_action_openstack_upgrade('package-xyz', do_openstack_upgrade, None) self.assertTrue(openstack_upgrade_available.called) msg = ('action-managed-upgrade config is False, skipped upgrade.') action_set.assert_called_with({'outcome': msg}) self.assertFalse(action_fail.called) @patch.object(openstack, 'juju_log') @patch.object(openstack, 'action_set') @patch.object(openstack, 'action_fail') @patch.object(openstack, 'openstack_upgrade_available') @patch('traceback.format_exc') @patch('charmhelpers.contrib.openstack.utils.config') def test_openstack_upgrade_traceback(self, config, traceback, openstack_upgrade_available, action_fail, action_set, log): def do_openstack_upgrade(configs): oops() # noqa openstack_upgrade_available.return_value = True # action-managed-upgrade=False config.side_effect = [True] openstack.do_action_openstack_upgrade('package-xyz', do_openstack_upgrade, None) self.assertTrue(openstack_upgrade_available.called) msg = 'do_openstack_upgrade resulted in an unexpected error' action_fail.assert_called_with(msg) self.assertTrue(action_set.called) self.assertTrue(traceback.called) @patch.object(openstack, 'os_release') @patch.object(openstack, 'application_version_set') def test_os_application_version_set(self, mock_application_version_set, mock_os_release): with patch.object(fetch, 'apt_cache') as cache: cache.return_value = self._apt_cache() mock_os_release.return_value = 'mitaka' openstack.os_application_version_set('neutron-common') mock_application_version_set.assert_called_with('7.0.1') openstack.os_application_version_set('cinder-common') mock_application_version_set.assert_called_with('mitaka') @patch.object(openstack, 'valid_snap_channel') @patch('charmhelpers.contrib.openstack.utils.config') def test_snap_install_requested(self, config, valid_snap_channel): valid_snap_channel.return_value = True # Expect True flush('snap_install_requested') config.return_value = 'snap:ocata/edge' self.assertTrue(openstack.snap_install_requested()) valid_snap_channel.assert_called_with('edge') flush('snap_install_requested') config.return_value = 'snap:pike' self.assertTrue(openstack.snap_install_requested()) valid_snap_channel.assert_called_with('stable') flush('snap_install_requested') config.return_value = 'snap:pike/stable/jamespage' self.assertTrue(openstack.snap_install_requested()) valid_snap_channel.assert_called_with('stable') # Expect False flush('snap_install_requested') config.return_value = 'cloud:xenial-ocata' self.assertFalse(openstack.snap_install_requested()) def test_get_snaps_install_info_from_origin(self): snaps = ['os_project'] mode = 'jailmode' # snap:track/channel src = 'snap:ocata/beta' expected = {snaps[0]: {'mode': mode, 'channel': '--channel=ocata/beta'}} self.assertEqual( expected, openstack.get_snaps_install_info_from_origin(snaps, src, mode=mode)) # snap:track/channel/branch src = 'snap:ocata/beta/jamespage' expected = {snaps[0]: {'mode': mode, 'channel': '--channel=ocata/beta/jamespage'}} self.assertEqual( expected, openstack.get_snaps_install_info_from_origin(snaps, src, mode=mode)) # snap:track src = 'snap:pike' expected = {snaps[0]: {'mode': mode, 'channel': '--channel=pike'}} self.assertEqual( expected, openstack.get_snaps_install_info_from_origin(snaps, src, mode=mode)) @patch.object(openstack, 'snap_install') def test_install_os_snaps(self, mock_snap_install): snaps = ['os_project'] mode = 'jailmode' # snap:track/channel src = 'snap:ocata/beta' openstack.install_os_snaps( openstack.get_snaps_install_info_from_origin( snaps, src, mode=mode)) mock_snap_install.assert_called_with( 'os_project', '--channel=ocata/beta', '--jailmode') # snap:track src = 'snap:pike' openstack.install_os_snaps( openstack.get_snaps_install_info_from_origin( snaps, src, mode=mode)) mock_snap_install.assert_called_with( 'os_project', '--channel=pike', '--jailmode') @patch.object(openstack, 'set_unit_upgrading') @patch.object(openstack, 'is_unit_paused_set') def test_series_upgrade_prepare( self, is_unit_paused_set, set_unit_upgrading): is_unit_paused_set.return_value = False fake_pause_helper = MagicMock() fake_configs = MagicMock() openstack.series_upgrade_prepare(fake_pause_helper, fake_configs) set_unit_upgrading.assert_called_once() fake_pause_helper.assert_called_once_with(fake_configs) @patch.object(openstack, 'set_unit_upgrading') @patch.object(openstack, 'is_unit_paused_set') def test_series_upgrade_prepare_no_pause( self, is_unit_paused_set, set_unit_upgrading): is_unit_paused_set.return_value = True fake_pause_helper = MagicMock() fake_configs = MagicMock() openstack.series_upgrade_prepare(fake_pause_helper, fake_configs) set_unit_upgrading.assert_called_once() fake_pause_helper.assert_not_called() @patch.object(openstack, 'clear_unit_upgrading') @patch.object(openstack, 'clear_unit_paused') def test_series_upgrade_complete( self, clear_unit_paused, clear_unit_upgrading): fake_resume_helper = MagicMock() fake_configs = MagicMock() openstack.series_upgrade_complete(fake_resume_helper, fake_configs) clear_unit_upgrading.assert_called_once() clear_unit_paused.assert_called_once() fake_configs.write_all.assert_called_once() fake_resume_helper.assert_called_once_with(fake_configs) @patch.object(openstack, 'juju_log') @patch.object(openstack, 'leader_get') def test_is_db_initialised(self, leader_get, juju_log): leader_get.return_value = 'True' self.assertTrue(openstack.is_db_initialised()) leader_get.return_value = 'False' self.assertFalse(openstack.is_db_initialised()) leader_get.return_value = None self.assertFalse(openstack.is_db_initialised()) @patch.object(openstack, 'juju_log') @patch.object(openstack, 'leader_set') def test_set_db_initialised(self, leader_set, juju_log): openstack.set_db_initialised() leader_set.assert_called_once_with({'db-initialised': True}) @patch.object(openstack, 'juju_log') @patch.object(openstack, 'relation_ids') @patch.object(openstack, 'related_units') @patch.object(openstack, 'relation_get') def test_is_db_maintenance_mode(self, relation_get, related_units, relation_ids, juju_log): relation_ids.return_value = ['rid:1'] related_units.return_value = ['unit/0', 'unit/2'] rsettings = { 'rid:1': { 'unit/0': { 'private-ip': '1.2.3.4', 'cluster-series-upgrading': 'True'}, 'unit/2': { 'private-ip': '1.2.3.5'}}} relation_get.side_effect = lambda unit, rid: rsettings[rid][unit] self.assertTrue(openstack.is_db_maintenance_mode()) rsettings = { 'rid:1': { 'unit/0': { 'private-ip': '1.2.3.4'}, 'unit/2': { 'private-ip': '1.2.3.5'}}} self.assertFalse(openstack.is_db_maintenance_mode()) rsettings = { 'rid:1': { 'unit/0': { 'private-ip': '1.2.3.4', 'cluster-series-upgrading': 'False'}, 'unit/2': { 'private-ip': '1.2.3.5'}}} self.assertFalse(openstack.is_db_maintenance_mode()) rsettings = { 'rid:1': { 'unit/0': { 'private-ip': '1.2.3.4', 'cluster-series-upgrading': 'lskjfsd'}, 'unit/2': { 'private-ip': '1.2.3.5'}}} self.assertFalse(openstack.is_db_maintenance_mode()) def test_get_endpoint_key(self): self.assertEqual( openstack.get_endpoint_key('placement', 'is:2', 'keystone/0'), 'placement-is_2-keystone_0') @patch.object(openstack, 'relation_get') @patch.object(openstack, 'related_units') @patch.object(openstack, 'relation_ids') def test_get_endpoint_notifications(self, relation_ids, related_units, relation_get): id_svc_rel_units = { 'identity-service:3': ['keystone/0', 'keystone/1', 'keystone/2'] } def _related_units(relid): return id_svc_rel_units[relid] id_svc_rel_data = { 'keystone/0': { 'ep_changed': '{"placement": "d5c3"}'}, 'keystone/1': { 'ep_changed': '{"nova": "4d06", "neutron": "2aa6"}'}, 'keystone/2': {}} def _relation_get(unit, rid, attribute): return id_svc_rel_data[unit].get(attribute) relation_ids.return_value = id_svc_rel_units.keys() related_units.side_effect = _related_units relation_get.side_effect = _relation_get self.assertEqual( openstack.get_endpoint_notifications(['neutron']), { 'neutron-identity-service_3-keystone_1': '2aa6'}) self.assertEqual( openstack.get_endpoint_notifications(['placement', 'neutron']), { 'neutron-identity-service_3-keystone_1': '2aa6', 'placement-identity-service_3-keystone_0': 'd5c3'}) @patch.object(openstack, 'get_endpoint_notifications') @patch.object(openstack.unitdata, 'HookData') def test_endpoint_changed(self, HookData, get_endpoint_notifications): self.kv_data = {} def _kv_get(key): return self.kv_data.get(key) kv = self._unit_paused_helper(HookData) kv.get.side_effect = _kv_get # Check endpoint_changed returns True when there are new notifications. get_endpoint_notifications.return_value = { 'neutron-identity-service_3-keystone_1': '2aa6', 'placement-identity-service_3-keystone_0': 'd5c3'} self.assertTrue(openstack.endpoint_changed('placement')) # Check endpoint_changed returns False when there are new # notifications but they are not the ones being looked for. self.assertTrue(openstack.endpoint_changed('nova')) # Check endpoint_changed returns False if the notification # has already been seen get_endpoint_notifications.return_value = { 'placement-identity-service_3-keystone_0': 'd5c3'} self.kv_data = { 'placement-identity-service_3-keystone_0': 'd5c3'} self.assertFalse(openstack.endpoint_changed('placement')) @patch.object(openstack, 'get_endpoint_notifications') @patch.object(openstack.unitdata, 'HookData') def test_save_endpoint_changed_triggers(self, HookData, get_endpoint_notifications): kv = self._unit_paused_helper(HookData) get_endpoint_notifications.return_value = { 'neutron-identity-service_3-keystone_1': '2aa6', 'placement-identity-service_3-keystone_0': 'd5c3'} openstack.save_endpoint_changed_triggers(['neutron', 'placement']) kv_set_calls = [ call('neutron-identity-service_3-keystone_1', '2aa6'), call('placement-identity-service_3-keystone_0', 'd5c3')] kv.set.assert_has_calls(kv_set_calls, any_order=True) class OpenStackUtilsAdditionalTests(TestCase): SHARED_DB_RELATIONS = { 'shared-db:8': { 'mysql-svc1/0': { 'allowed_units': 'client/0', }, 'mysql-svc1/1': {}, 'mysql-svc1/2': { 'allowed_units': 'client/0 client/1', }, }, 'shared-db:12': { 'mysql-svc2/0': { 'allowed_units': 'client/1', }, 'mysql-svc2/1': { 'allowed_units': 'client/3', }, 'mysql-svc2/2': { 'allowed_units': {}, }, } } SCALE_RELATIONS = { 'cluster:2': { 'keystone/1': {}, 'keystone/2': {}}, 'shared-db:12': { 'mysql-svc2/0': { 'allowed_units': 'client/1', }, 'mysql-svc2/1': { 'allowed_units': 'client/3', }, 'mysql-svc2/2': { 'allowed_units': {}, }}, } SCALE_RELATIONS_HA = { 'cluster:2': { 'keystone/1': {'unit-state-keystone-1': 'READY'}, 'keystone/2': {}}, 'shared-db:12': { 'mysql-svc2/0': { 'allowed_units': 'client/1', }, 'mysql-svc2/1': { 'allowed_units': 'client/3', }, 'mysql-svc2/2': { 'allowed_units': {}, }}, 'ha:32': { 'hacluster-keystone/1': {}} } All_PEERS_READY = { 'cluster:2': { 'keystone/1': {'unit-state-keystone-1': 'READY'}, 'keystone/2': {'unit-state-keystone-2': 'READY'}}} PEERS_NOT_READY = { 'cluster:2': { 'keystone/1': {'unit-state-keystone-1': 'READY'}, 'keystone/2': {}}} def setUp(self): super(OpenStackUtilsAdditionalTests, self).setUp() [self._patch(m) for m in [ 'expect_ha', 'expected_peer_units', 'expected_related_units', 'juju_log', 'metadata', 'related_units', 'relation_get', 'relation_id', 'relation_ids', 'relation_set', 'local_unit', ]] def _patch(self, method): _m = patch.object(openstack, method) mock = _m.start() self.addCleanup(_m.stop) setattr(self, method, mock) def setup_relation(self, relation_map): relation = FakeRelation(relation_map) self.relation_id.side_effect = relation.relation_id self.relation_get.side_effect = relation.get self.relation_ids.side_effect = relation.relation_ids self.related_units.side_effect = relation.related_units return relation def test_is_db_ready(self): relation = self.setup_relation(self.SHARED_DB_RELATIONS) # Check unit allowed in 1st relation self.local_unit.return_value = 'client/0' self.assertTrue(openstack.is_db_ready()) # Check unit allowed in 2nd relation self.local_unit.return_value = 'client/3' self.assertTrue(openstack.is_db_ready()) # Check unit not allowed in any list self.local_unit.return_value = 'client/5' self.assertFalse(openstack.is_db_ready()) # Check call with an invalid relation self.local_unit.return_value = 'client/3' # None returned if not in a relation context (eg update-status) relation.clear_relation_context() self.assertRaises( Exception, openstack.is_db_ready, use_current_context=True) # Check unit allowed using current relation context relation.set_relation_context('mysql-svc2/0', 'shared-db:12') self.local_unit.return_value = 'client/1' self.assertTrue(openstack.is_db_ready(use_current_context=True)) # Check unit not allowed using current relation context relation.set_relation_context('mysql-svc2/0', 'shared-db:12') self.local_unit.return_value = 'client/0' self.assertFalse(openstack.is_db_ready(use_current_context=True)) @patch.object(openstack, 'container_scoped_relations') def test_is_expected_scale_noha(self, container_scoped_relations): self.setup_relation(self.SCALE_RELATIONS) self.expect_ha.return_value = False eru = { 'shared-db': ['mysql/0', 'mysql/1', 'mysql/2']} def _expected_related_units(reltype): return eru[reltype] self.expected_related_units.side_effect = _expected_related_units container_scoped_relations.return_value = ['ha', 'domain-backend'] # All peer and db units are present self.expected_peer_units.return_value = ['keystone/0', 'keystone/2'] self.assertTrue(openstack.is_expected_scale()) # db units are present but a peer is missing self.expected_peer_units.return_value = ['keystone/0', 'keystone/2', 'keystone/3'] self.assertFalse(openstack.is_expected_scale()) # peer units are present but a db unit is missing eru['shared-db'].append('mysql/3') self.expected_peer_units.return_value = ['keystone/0', 'keystone/2'] self.assertFalse(openstack.is_expected_scale()) eru['shared-db'].remove('mysql/3') # Expect ha but ha unit is missing self.expect_ha.return_value = True self.expected_peer_units.return_value = ['keystone/0', 'keystone/2'] self.assertFalse(openstack.is_expected_scale()) @patch.object(openstack, 'container_scoped_relations') def test_is_expected_scale_ha(self, container_scoped_relations): self.setup_relation(self.SCALE_RELATIONS_HA) eru = { 'shared-db': ['mysql/0', 'mysql/1', 'mysql/2']} def _expected_related_units(reltype): return eru[reltype] self.expected_related_units.side_effect = _expected_related_units container_scoped_relations.return_value = ['ha', 'domain-backend'] self.expect_ha.return_value = True self.expected_peer_units.return_value = ['keystone/0', 'keystone/2'] self.assertTrue(openstack.is_expected_scale()) def test_container_scoped_relations(self): _metadata = { 'provides': { 'amqp': {'interface': 'rabbitmq'}, 'identity-service': {'interface': 'keystone'}, 'ha': { 'interface': 'hacluster', 'scope': 'container'}}, 'peers': { 'cluster': {'interface': 'openstack-ha'}}} self.metadata.return_value = _metadata self.assertEqual(openstack.container_scoped_relations(), ['ha']) def test_get_peer_key(self): self.assertEqual( openstack.get_peer_key('cinder/0'), 'unit-state-cinder-0') def test_inform_peers_unit_state(self): self.local_unit.return_value = 'client/0' self.setup_relation(self.All_PEERS_READY) openstack.inform_peers_unit_state('READY') self.relation_set.assert_called_once_with( relation_id='cluster:2', relation_settings={'unit-state-client-0': 'READY'}) def test_get_peers_unit_state(self): self.setup_relation(self.All_PEERS_READY) self.assertEqual( openstack.get_peers_unit_state(), {'keystone/1': 'READY', 'keystone/2': 'READY'}) self.setup_relation(self.PEERS_NOT_READY) self.assertEqual( openstack.get_peers_unit_state(), {'keystone/1': 'READY', 'keystone/2': 'UNKNOWN'}) def test_are_peers_ready(self): self.setup_relation(self.All_PEERS_READY) self.assertTrue(openstack.are_peers_ready()) self.setup_relation(self.PEERS_NOT_READY) self.assertFalse(openstack.are_peers_ready()) @patch.object(openstack, 'inform_peers_unit_state') def test_inform_peers_if_ready(self, inform_peers_unit_state): self.setup_relation(self.All_PEERS_READY) def _not_ready(): return False, "Its all gone wrong" def _ready(): return True, "Hurray!" openstack.inform_peers_if_ready(_not_ready) inform_peers_unit_state.assert_called_once_with('NOTREADY', 'cluster') inform_peers_unit_state.reset_mock() openstack.inform_peers_if_ready(_ready) inform_peers_unit_state.assert_called_once_with('READY', 'cluster') @patch.object(openstack, 'is_expected_scale') @patch.object(openstack, 'is_db_initialised') @patch.object(openstack, 'is_db_ready') @patch.object(openstack, 'is_unit_paused_set') @patch.object(openstack, 'is_db_maintenance_mode') def test_check_api_unit_ready(self, is_db_maintenance_mode, is_unit_paused_set, is_db_ready, is_db_initialised, is_expected_scale): is_db_maintenance_mode.return_value = True self.assertFalse(openstack.check_api_unit_ready()[0]) is_db_maintenance_mode.return_value = False is_unit_paused_set.return_value = True self.assertFalse(openstack.check_api_unit_ready()[0]) is_db_maintenance_mode.return_value = False is_unit_paused_set.return_value = False is_db_ready.return_value = False self.assertFalse(openstack.check_api_unit_ready()[0]) is_db_maintenance_mode.return_value = False is_unit_paused_set.return_value = False is_db_ready.return_value = True is_db_initialised.return_value = False self.assertFalse(openstack.check_api_unit_ready()[0]) is_db_maintenance_mode.return_value = False is_unit_paused_set.return_value = False is_db_ready.return_value = True is_db_initialised.return_value = True is_expected_scale.return_value = False self.assertFalse(openstack.check_api_unit_ready()[0]) is_db_maintenance_mode.return_value = False is_unit_paused_set.return_value = False is_db_ready.return_value = True is_db_initialised.return_value = True is_expected_scale.return_value = True self.assertTrue(openstack.check_api_unit_ready()[0]) @patch.object(openstack, 'is_expected_scale') @patch.object(openstack, 'is_db_initialised') @patch.object(openstack, 'is_db_ready') @patch.object(openstack, 'is_unit_paused_set') @patch.object(openstack, 'is_db_maintenance_mode') def test_get_api_unit_status(self, is_db_maintenance_mode, is_unit_paused_set, is_db_ready, is_db_initialised, is_expected_scale): is_db_maintenance_mode.return_value = True self.assertEqual( openstack.get_api_unit_status()[0].value, 'maintenance') is_db_maintenance_mode.return_value = False is_unit_paused_set.return_value = True self.assertEqual( openstack.get_api_unit_status()[0].value, 'blocked') is_db_maintenance_mode.return_value = False is_unit_paused_set.return_value = False is_db_ready.return_value = False self.assertEqual( openstack.get_api_unit_status()[0].value, 'waiting') is_db_maintenance_mode.return_value = False is_unit_paused_set.return_value = False is_db_ready.return_value = True is_db_initialised.return_value = False self.assertEqual( openstack.get_api_unit_status()[0].value, 'waiting') is_db_maintenance_mode.return_value = False is_unit_paused_set.return_value = False is_db_ready.return_value = True is_db_initialised.return_value = True is_expected_scale.return_value = False self.assertEqual( openstack.get_api_unit_status()[0].value, 'waiting') is_db_maintenance_mode.return_value = False is_unit_paused_set.return_value = False is_db_ready.return_value = True is_db_initialised.return_value = True is_expected_scale.return_value = True self.assertEqual( openstack.get_api_unit_status()[0].value, 'active') @patch.object(openstack, 'get_api_unit_status') def test_check_api_application_ready(self, get_api_unit_status): get_api_unit_status.return_value = (WORKLOAD_STATES.ACTIVE, 'Hurray') self.assertTrue(openstack.check_api_application_ready()[0]) get_api_unit_status.return_value = (WORKLOAD_STATES.BLOCKED, ':-(') self.assertFalse(openstack.check_api_application_ready()[0]) @patch.object(openstack, 'get_api_unit_status') def test_get_api_application_status(self, get_api_unit_status): get_api_unit_status.return_value = (WORKLOAD_STATES.ACTIVE, 'Hurray') self.assertEqual( openstack.get_api_application_status()[0].value, 'active') get_api_unit_status.return_value = (WORKLOAD_STATES.BLOCKED, ':-(') self.assertEqual( openstack.get_api_application_status()[0].value, 'blocked') @patch.object(openstack.deferred_events, 'clear_deferred_restarts') @patch.object(openstack, 'manage_payload_services') @patch.object(openstack.deferred_events, 'get_deferred_restarts') def test_restart_services_action(self, get_deferred_restarts, manage_payload_services, clear_deferred_restarts): deferred_restarts = [ deferred_events.ServiceEvent( timestamp=123, service='svcA', reason='ReasonA', action='restart')] get_deferred_restarts.return_value = deferred_restarts manage_payload_services.return_value = (None, None) openstack.restart_services_action(deferred_only=True) manage_payload_services.assert_has_calls([ call('stop', services=['svcA'], charm_func=None), call('start', services=['svcA'])]) clear_deferred_restarts.assert_called_once_with(['svcA']) self.assertRaises( ValueError, openstack.restart_services_action, ['svcA'], deferred_only=True) manage_payload_services.return_value = (None, 'something went wrong') self.assertRaises( ServiceActionError, openstack.restart_services_action, ['svcA']) if __name__ == '__main__': unittest.main()