ceph-mon charm only upgrades when the ceph version changes, for the case of upgrading from Pike to Queens the charm is skipping any upgrades, because the Cloud Archive has Luminous for those 2 releases. This patch checks if the requested ceph version is luminous and if the 'source' changed from pike to queens to then upgrade /etc/apt/sources.list.d/cloud-archive.list via add_source() Change-Id: I05b7d722e45d3a02a97866903a67bd9b16d4f552 Closes-Bug: 1778823
483 lines
20 KiB
Python
483 lines
20 KiB
Python
import copy
|
|
import unittest
|
|
import sys
|
|
|
|
from mock import patch, MagicMock, DEFAULT, call
|
|
|
|
# python-apt is not installed as part of test-requirements but is imported by
|
|
# some charmhelpers modules so create a fake import.
|
|
mock_apt = MagicMock()
|
|
sys.modules['apt'] = mock_apt
|
|
mock_apt.apt_pkg = MagicMock()
|
|
|
|
import charmhelpers.contrib.storage.linux.ceph as ceph
|
|
import test_utils
|
|
|
|
with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec:
|
|
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
|
lambda *args, **kwargs: f(*args, **kwargs))
|
|
import ceph_hooks
|
|
|
|
|
|
TO_PATCH = [
|
|
'config',
|
|
'is_leader',
|
|
'is_relation_made',
|
|
'leader_get',
|
|
'leader_set',
|
|
'log',
|
|
'mon_relation',
|
|
'relation_ids',
|
|
'related_units',
|
|
'relation_get',
|
|
'relations_of_type',
|
|
'status_set',
|
|
]
|
|
|
|
CHARM_CONFIG = {'config-flags': '',
|
|
'auth-supported': False,
|
|
'fsid': '1234',
|
|
'loglevel': 1,
|
|
'use-syslog': True,
|
|
'osd-journal-size': 1024,
|
|
'use-direct-io': True,
|
|
'osd-format': 'ext4',
|
|
'monitor-hosts': '',
|
|
'prefer-ipv6': False,
|
|
'default-rbd-features': None,
|
|
'nagios_degraded_thresh': '1',
|
|
'nagios_misplaced_thresh': '10',
|
|
'nagios_recovery_rate': '1',
|
|
'nagios_raise_nodeepscrub': True,
|
|
'disable-pg-max-object-skew': False}
|
|
|
|
|
|
class CephHooksTestCase(unittest.TestCase):
|
|
def setUp(self):
|
|
super(CephHooksTestCase, self).setUp()
|
|
|
|
@patch.object(ceph_hooks, 'get_public_addr', lambda *args: "10.0.0.1")
|
|
@patch.object(ceph_hooks, 'get_cluster_addr', lambda *args: "10.1.0.1")
|
|
@patch.object(ceph_hooks, 'cmp_pkgrevno', lambda *args: 1)
|
|
@patch.object(ceph_hooks, 'get_mon_hosts', lambda *args: ['10.0.0.1',
|
|
'10.0.0.2'])
|
|
@patch.object(ceph_hooks, 'get_networks', lambda *args: "")
|
|
@patch.object(ceph_hooks, 'leader_get', lambda *args: '1234')
|
|
@patch.object(ceph, 'config')
|
|
@patch.object(ceph_hooks, 'config')
|
|
def test_get_ceph_context(self, mock_config, mock_config2):
|
|
config = copy.deepcopy(CHARM_CONFIG)
|
|
mock_config.side_effect = lambda key: config[key]
|
|
mock_config2.side_effect = lambda key: config[key]
|
|
ctxt = ceph_hooks.get_ceph_context()
|
|
expected = {'auth_supported': False,
|
|
'ceph_cluster_network': '',
|
|
'ceph_public_network': '',
|
|
'cluster_addr': '10.1.0.1',
|
|
'dio': 'true',
|
|
'fsid': '1234',
|
|
'loglevel': 1,
|
|
'mon_hosts': '10.0.0.1 10.0.0.2',
|
|
'old_auth': False,
|
|
'public_addr': '10.0.0.1',
|
|
'use_syslog': 'true'}
|
|
self.assertEqual(ctxt, expected)
|
|
|
|
@patch.object(ceph_hooks, 'get_public_addr', lambda *args: "10.0.0.1")
|
|
@patch.object(ceph_hooks, 'get_cluster_addr', lambda *args: "10.1.0.1")
|
|
@patch.object(ceph_hooks, 'cmp_pkgrevno',
|
|
lambda pkg, ver: -1 if ver == '12.1.0' else 1)
|
|
@patch.object(ceph_hooks, 'get_mon_hosts', lambda *args: ['10.0.0.1',
|
|
'10.0.0.2'])
|
|
@patch.object(ceph_hooks, 'get_networks', lambda *args: "")
|
|
@patch.object(ceph_hooks, 'leader_get', lambda *args: '1234')
|
|
@patch.object(ceph, 'config')
|
|
@patch.object(ceph_hooks, 'config')
|
|
def test_get_ceph_context_rbd_features(self, mock_config, mock_config2):
|
|
config = copy.deepcopy(CHARM_CONFIG)
|
|
config['default-rbd-features'] = 1
|
|
mock_config.side_effect = lambda key: config[key]
|
|
mock_config2.side_effect = lambda key: config[key]
|
|
ctxt = ceph_hooks.get_ceph_context()
|
|
expected = {'auth_supported': False,
|
|
'ceph_cluster_network': '',
|
|
'ceph_public_network': '',
|
|
'cluster_addr': '10.1.0.1',
|
|
'dio': 'true',
|
|
'fsid': '1234',
|
|
'loglevel': 1,
|
|
'mon_hosts': '10.0.0.1 10.0.0.2',
|
|
'old_auth': False,
|
|
'public_addr': '10.0.0.1',
|
|
'use_syslog': 'true',
|
|
'rbd_features': 1}
|
|
self.assertEqual(ctxt, expected)
|
|
|
|
@patch.object(ceph_hooks, 'get_public_addr', lambda *args: "10.0.0.1")
|
|
@patch.object(ceph_hooks, 'get_cluster_addr', lambda *args: "10.1.0.1")
|
|
@patch.object(ceph_hooks, 'cmp_pkgrevno', lambda *args: 1)
|
|
@patch.object(ceph_hooks, 'get_mon_hosts', lambda *args: ['10.0.0.1',
|
|
'10.0.0.2'])
|
|
@patch.object(ceph_hooks, 'get_networks', lambda *args: "")
|
|
@patch.object(ceph_hooks, 'leader_get', lambda *args: '1234')
|
|
@patch.object(ceph, 'config')
|
|
@patch.object(ceph_hooks, 'config')
|
|
def test_get_ceph_context_w_config_flags(self, mock_config, mock_config2):
|
|
config = copy.deepcopy(CHARM_CONFIG)
|
|
config['config-flags'] = '{"mon": {"mon sync max retries": 10}}'
|
|
mock_config.side_effect = lambda key: config[key]
|
|
mock_config2.side_effect = lambda key: config[key]
|
|
ctxt = ceph_hooks.get_ceph_context()
|
|
expected = {'auth_supported': False,
|
|
'ceph_cluster_network': '',
|
|
'ceph_public_network': '',
|
|
'cluster_addr': '10.1.0.1',
|
|
'dio': 'true',
|
|
'fsid': '1234',
|
|
'loglevel': 1,
|
|
'mon_hosts': '10.0.0.1 10.0.0.2',
|
|
'old_auth': False,
|
|
'mon': {'mon sync max retries': 10},
|
|
'public_addr': '10.0.0.1',
|
|
'use_syslog': 'true'}
|
|
self.assertEqual(ctxt, expected)
|
|
|
|
@patch.object(ceph_hooks, 'get_public_addr', lambda *args: "10.0.0.1")
|
|
@patch.object(ceph_hooks, 'get_cluster_addr', lambda *args: "10.1.0.1")
|
|
@patch.object(ceph_hooks, 'cmp_pkgrevno', lambda *args: 1)
|
|
@patch.object(ceph_hooks, 'get_mon_hosts', lambda *args: ['10.0.0.1',
|
|
'10.0.0.2'])
|
|
@patch.object(ceph_hooks, 'get_networks', lambda *args: "")
|
|
@patch.object(ceph_hooks, 'leader_get', lambda *args: '1234')
|
|
@patch.object(ceph, 'config')
|
|
@patch.object(ceph_hooks, 'config')
|
|
def test_get_ceph_context_w_config_flags_invalid(self, mock_config,
|
|
mock_config2):
|
|
config = copy.deepcopy(CHARM_CONFIG)
|
|
config['config-flags'] = ('{"mon": {"mon sync max retries": 10},'
|
|
'"foo": "bar"}')
|
|
mock_config.side_effect = lambda key: config[key]
|
|
mock_config2.side_effect = lambda key: config[key]
|
|
ctxt = ceph_hooks.get_ceph_context()
|
|
expected = {'auth_supported': False,
|
|
'ceph_cluster_network': '',
|
|
'ceph_public_network': '',
|
|
'cluster_addr': '10.1.0.1',
|
|
'dio': 'true',
|
|
'fsid': '1234',
|
|
'loglevel': 1,
|
|
'mon_hosts': '10.0.0.1 10.0.0.2',
|
|
'old_auth': False,
|
|
'mon': {'mon sync max retries': 10},
|
|
'public_addr': '10.0.0.1',
|
|
'use_syslog': 'true'}
|
|
self.assertEqual(ctxt, expected)
|
|
|
|
@patch.object(ceph_hooks, 'config')
|
|
def test_nrpe_dependency_installed(self, mock_config):
|
|
config = copy.deepcopy(CHARM_CONFIG)
|
|
mock_config.side_effect = lambda key: config[key]
|
|
with patch.multiple(ceph_hooks,
|
|
apt_install=DEFAULT,
|
|
rsync=DEFAULT,
|
|
log=DEFAULT,
|
|
write_file=DEFAULT,
|
|
nrpe=DEFAULT) as mocks:
|
|
ceph_hooks.update_nrpe_config()
|
|
mocks["apt_install"].assert_called_once_with(
|
|
["python-dbus", "lockfile-progs"])
|
|
|
|
@patch.object(ceph_hooks, 'service_pause')
|
|
@patch.object(ceph_hooks, 'notify_radosgws')
|
|
@patch.object(ceph_hooks, 'ceph')
|
|
@patch.object(ceph_hooks, 'notify_client')
|
|
@patch.object(ceph_hooks, 'config')
|
|
def test_upgrade_charm_with_nrpe_relation_installs_dependencies(
|
|
self,
|
|
mock_config,
|
|
mock_notify_client,
|
|
mock_ceph,
|
|
mock_notify_radosgws,
|
|
mock_service_pause):
|
|
config = copy.deepcopy(CHARM_CONFIG)
|
|
mock_config.side_effect = lambda key: config[key]
|
|
with patch.multiple(
|
|
ceph_hooks,
|
|
apt_install=DEFAULT,
|
|
rsync=DEFAULT,
|
|
log=DEFAULT,
|
|
write_file=DEFAULT,
|
|
nrpe=DEFAULT,
|
|
emit_cephconf=DEFAULT,
|
|
mon_relation_joined=DEFAULT,
|
|
is_relation_made=DEFAULT) as mocks, patch(
|
|
"charmhelpers.contrib.hardening.harden.config"):
|
|
mocks["is_relation_made"].return_value = True
|
|
ceph_hooks.upgrade_charm()
|
|
mocks["apt_install"].assert_called_with(
|
|
["python-dbus", "lockfile-progs"])
|
|
mock_notify_client.assert_called_once_with()
|
|
mock_notify_radosgws.assert_called_once_with()
|
|
mock_ceph.update_monfs.assert_called_once_with()
|
|
mock_service_pause.assert_called_with('ceph-create-keys')
|
|
|
|
|
|
class RelatedUnitsTestCase(unittest.TestCase):
|
|
|
|
_units = {
|
|
'osd:0': ['ceph-osd-a/0',
|
|
'ceph-osd-a/1',
|
|
'ceph-osd-a/2'],
|
|
'osd:23': ['ceph-osd-b/1',
|
|
'ceph-osd-b/2',
|
|
'ceph-osd-b/3'],
|
|
}
|
|
|
|
def setUp(self):
|
|
super(RelatedUnitsTestCase, self).setUp()
|
|
|
|
@patch.object(ceph_hooks, 'relation_ids')
|
|
@patch.object(ceph_hooks, 'related_units')
|
|
def test_related_osd_single_relation(self,
|
|
related_units,
|
|
relation_ids):
|
|
relation_ids.return_value = ['osd:0']
|
|
related_units.side_effect = lambda x: self._units.get(x)
|
|
self.assertTrue(ceph_hooks.related_osds())
|
|
self.assertFalse(ceph_hooks.related_osds(6))
|
|
relation_ids.assert_called_with('osd')
|
|
related_units.assert_called_with('osd:0')
|
|
|
|
@patch.object(ceph_hooks, 'relation_ids')
|
|
@patch.object(ceph_hooks, 'related_units')
|
|
def test_related_osd_multi_relation(self,
|
|
related_units,
|
|
relation_ids):
|
|
relation_ids.return_value = ['osd:0', 'osd:23']
|
|
related_units.side_effect = lambda x: self._units.get(x)
|
|
self.assertTrue(ceph_hooks.related_osds())
|
|
self.assertTrue(ceph_hooks.related_osds(6))
|
|
self.assertFalse(ceph_hooks.related_osds(9))
|
|
relation_ids.assert_called_with('osd')
|
|
related_units.assert_has_calls([
|
|
call('osd:0'),
|
|
call('osd:23')
|
|
])
|
|
|
|
@patch.object(ceph_hooks, 'ready_for_service')
|
|
@patch.object(ceph_hooks.ceph, 'is_quorum')
|
|
@patch.object(ceph_hooks, 'remote_unit')
|
|
@patch.object(ceph_hooks, 'relation_get')
|
|
@patch.object(ceph_hooks.ceph, 'is_leader')
|
|
@patch.object(ceph_hooks, 'process_requests')
|
|
@patch.object(ceph_hooks, 'relation_set')
|
|
def test_client_relation_changed_non_rel_hook(self, relation_set,
|
|
process_requests,
|
|
is_leader,
|
|
relation_get,
|
|
remote_unit,
|
|
is_quorum,
|
|
ready_for_service):
|
|
# Check for LP #1738154
|
|
ready_for_service.return_value = True
|
|
process_requests.return_value = 'AOK'
|
|
is_leader.return_value = True
|
|
relation_get.return_value = {'broker_req': 'req'}
|
|
remote_unit.return_value = None
|
|
is_quorum.return_value = True
|
|
ceph_hooks.client_relation_changed(relid='rel1', unit='glance/0')
|
|
relation_set.assert_called_once_with(
|
|
relation_id='rel1',
|
|
relation_settings={
|
|
'broker-rsp-glance-0': 'AOK',
|
|
'broker_rsp': 'AOK'})
|
|
relation_set.reset_mock()
|
|
remote_unit.return_value = 'glance/0'
|
|
ceph_hooks.client_relation_changed()
|
|
relation_set.assert_called_once_with(
|
|
relation_id=None,
|
|
relation_settings={
|
|
'broker-rsp-glance-0': 'AOK',
|
|
'broker_rsp': 'AOK'})
|
|
|
|
|
|
class BootstrapSourceTestCase(test_utils.CharmTestCase):
|
|
|
|
def setUp(self):
|
|
super(BootstrapSourceTestCase, self).setUp(ceph_hooks, TO_PATCH)
|
|
self.config.side_effect = self.test_config.get
|
|
self.leader_get.side_effect = self.test_leader_settings.get
|
|
self.leader_set.side_effect = self.test_leader_settings.set
|
|
self.relation_get.side_effect = self.test_relation.get
|
|
self.test_config.set('no-bootstrap', True)
|
|
self.is_leader.return_value = True
|
|
self.relation_ids.return_value = ['bootstrap-source:0']
|
|
self.related_units.return_value = ['ceph/0', 'ceph/1', 'ceph/2']
|
|
|
|
def test_bootstrap_source_no_bootstrap(self):
|
|
"""Ensure the config option of no-bootstrap is set to continue"""
|
|
self.test_config.set('no-bootstrap', False)
|
|
ceph_hooks.bootstrap_source_relation_changed()
|
|
self.status_set.assert_called_once_with('blocked',
|
|
'Cannot join the '
|
|
'bootstrap-source relation '
|
|
'when no-bootstrap is False')
|
|
|
|
def test_bootstrap_source_not_leader(self):
|
|
"""Ensure the processing is deferred to the leader"""
|
|
self.is_leader.return_value = False
|
|
ceph_hooks.bootstrap_source_relation_changed()
|
|
self.assertEqual(self.leader_set.call_count, 0)
|
|
|
|
def test_bootstrap_source_relation_data_not_ready(self):
|
|
"""Ensures no bootstrapping done if relation data not present"""
|
|
ceph_hooks.bootstrap_source_relation_changed()
|
|
expected_calls = []
|
|
relid = 'bootstrap-source:0'
|
|
for unit in ('ceph/0', 'ceph/1', 'ceph/2'):
|
|
expected_calls.append(call('monitor-secret', unit, relid))
|
|
expected_calls.append(call('fsid', unit, relid))
|
|
self.relation_get.has_calls(expected_calls)
|
|
self.assertEqual(self.leader_set.call_count, 0)
|
|
self.assertEqual(self.mon_relation.call_count, 0)
|
|
|
|
def test_bootstrap_source_good_path(self):
|
|
"""Tests the good path where all is setup and relations established"""
|
|
self.test_relation.set({'monitor-secret': 'abcd',
|
|
'fsid': '1234'})
|
|
ceph_hooks.bootstrap_source_relation_changed()
|
|
self.leader_set.assert_called_with({'fsid': '1234',
|
|
'monitor-secret': 'abcd'})
|
|
self.mon_relation.assert_called_once_with()
|
|
|
|
def test_bootstrap_source_different_fsid_secret(self):
|
|
"""Tests where the bootstrap relation has a different fsid"""
|
|
self.test_relation.set({'monitor-secret': 'abcd',
|
|
'fsid': '1234'})
|
|
self.test_leader_settings.set({'monitor-secret': 'mysecret',
|
|
'fsid': '7890'})
|
|
self.assertRaises(AssertionError,
|
|
ceph_hooks.bootstrap_source_relation_changed)
|
|
|
|
@patch.object(ceph_hooks, 'emit_cephconf')
|
|
@patch.object(ceph_hooks, 'create_sysctl')
|
|
@patch.object(ceph_hooks, 'check_for_upgrade')
|
|
@patch.object(ceph_hooks, 'get_mon_hosts')
|
|
@patch.object(ceph_hooks, 'bootstrap_source_relation_changed')
|
|
def test_config_changed_no_bootstrap_changed(self,
|
|
bootstrap_source_rel_changed,
|
|
get_mon_hosts,
|
|
check_for_upgrade,
|
|
create_sysctl,
|
|
emit_ceph_conf):
|
|
"""Tests that changing no-bootstrap invokes the bs relation changed"""
|
|
self.relations_of_type.return_value = []
|
|
self.is_relation_made.return_value = True
|
|
self.test_config.set_changed('no-bootstrap', True)
|
|
ceph_hooks.config_changed()
|
|
bootstrap_source_rel_changed.assert_called_once()
|
|
|
|
@patch.object(ceph_hooks, 'get_public_addr')
|
|
def test_get_mon_hosts(self, get_public_addr):
|
|
"""Tests that bootstrap-source relations are used"""
|
|
unit_addrs = {
|
|
'mon:0': {
|
|
'ceph-mon/0': '172.16.0.2',
|
|
'ceph-mon/1': '172.16.0.3',
|
|
},
|
|
'bootstrap-source:1': {
|
|
'ceph/0': '172.16.10.2',
|
|
'ceph/1': '172.16.10.3',
|
|
'cehp/2': '172.16.10.4',
|
|
}
|
|
}
|
|
|
|
def rel_ids_side_effect(relname):
|
|
for key in unit_addrs.keys():
|
|
if key.split(':')[0] == relname:
|
|
return [key]
|
|
return None
|
|
|
|
def rel_get_side_effect(attr, unit, relid):
|
|
return unit_addrs[relid][unit]
|
|
|
|
def rel_units_side_effect(relid):
|
|
if relid in unit_addrs:
|
|
return unit_addrs[relid].keys()
|
|
return []
|
|
|
|
self.relation_ids.side_effect = rel_ids_side_effect
|
|
self.related_units.side_effect = rel_units_side_effect
|
|
get_public_addr.return_value = '172.16.0.4'
|
|
self.relation_get.side_effect = rel_get_side_effect
|
|
hosts = ceph_hooks.get_mon_hosts()
|
|
self.assertEqual(hosts, [
|
|
'172.16.0.2:6789', '172.16.0.3:6789', '172.16.0.4:6789',
|
|
'172.16.10.2:6789', '172.16.10.3:6789', '172.16.10.4:6789',
|
|
])
|
|
|
|
|
|
class RGWRelationTestCase(test_utils.CharmTestCase):
|
|
|
|
TO_PATCH = [
|
|
'relation_get',
|
|
'get_public_addr',
|
|
'ready_for_service',
|
|
'remote_unit',
|
|
'apt_install',
|
|
'filter_installed_packages',
|
|
'leader_get',
|
|
'ceph',
|
|
'process_requests',
|
|
'log',
|
|
'relation_set',
|
|
'config',
|
|
]
|
|
|
|
test_key = 'OTQ1MDdiODYtMmZhZi00M2IwLTkzYTgtZWI0MGRhNzdmNzBlCg=='
|
|
test_fsid = '96ca5e7d-a9e3-4af1-be2b-85621eb6a8e8'
|
|
|
|
def setUp(self):
|
|
super(RGWRelationTestCase, self).setUp(ceph_hooks, self.TO_PATCH)
|
|
self.relation_get.side_effect = self.test_relation.get
|
|
self.config.side_effect = self.test_config.get
|
|
self.test_config.set('auth-supported', 'cephx')
|
|
self.filter_installed_packages.side_effect = lambda pkgs: pkgs
|
|
self.ready_for_service.return_value = True
|
|
self.leader_get.return_value = self.test_fsid
|
|
self.ceph.is_leader.return_value = True
|
|
self.ceph.get_radosgw_key.return_value = self.test_key
|
|
self.get_public_addr.return_value = '10.10.10.2'
|
|
|
|
def test_legacy_radosgw_key(self):
|
|
self.test_relation.set({
|
|
'key_name': None
|
|
})
|
|
ceph_hooks.radosgw_relation('radosgw:1', 'ceph-radosgw/0')
|
|
self.relation_set.assert_called_once_with(
|
|
relation_id='radosgw:1',
|
|
relation_settings={
|
|
'fsid': self.test_fsid,
|
|
'auth': self.test_config.get('auth-supported'),
|
|
'ceph-public-address': '10.10.10.2',
|
|
'radosgw_key': self.test_key,
|
|
}
|
|
)
|
|
self.ceph.get_radosgw_key.assert_called_once_with()
|
|
|
|
def test_per_unit_radosgw_key(self):
|
|
self.test_relation.set({
|
|
'key_name': 'testhostname'
|
|
})
|
|
ceph_hooks.radosgw_relation('radosgw:1', 'ceph-radosgw/0')
|
|
self.relation_set.assert_called_once_with(
|
|
relation_id='radosgw:1',
|
|
relation_settings={
|
|
'fsid': self.test_fsid,
|
|
'auth': self.test_config.get('auth-supported'),
|
|
'ceph-public-address': '10.10.10.2',
|
|
'testhostname_key': self.test_key,
|
|
}
|
|
)
|
|
self.ceph.get_radosgw_key.assert_called_once_with(name='testhostname')
|