Fix Ceph upgrade issue by porting charm to common framework
Move helpers as-is from reactive handler module to charm class. Set default of ``source`` configuration option to 'distro' to be in line with other reactive charms and the expectations of the ``openstack.utils.get_source_and_pgp_key`` function. Unpin flake8. Func-Test-Pr: https://github.com/openstack-charmers/zaza-openstack-tests/pull/313 Depends-On: I6fbde855ba8f83ef5e265bd5b5dfb0d01eae830b Change-Id: I2d8e8b244db935673406d2cce9ef34e7252cb50b Closes-Bug: #1879072
This commit is contained in:
parent
bf0e4724e7
commit
ff7ff85779
@ -5,7 +5,7 @@ options:
|
||||
description: Mon and OSD debug level. Max is 20.
|
||||
source:
|
||||
type: string
|
||||
default:
|
||||
default: distro
|
||||
description: |
|
||||
Optional configuration to support use of additional sources such as:
|
||||
- ppa:myteam/ppa
|
||||
|
@ -1,5 +1,14 @@
|
||||
includes: ['layer:basic', 'layer:apt', 'interface:ceph-mds']
|
||||
includes: ['layer:ceph', 'interface:ceph-mds']
|
||||
options:
|
||||
status:
|
||||
patch-hookenv: False
|
||||
basic:
|
||||
use_venv: True
|
||||
include_system_packages: False
|
||||
repo: https://git.openstack.org/openstack/charm-ceph-fs
|
||||
config:
|
||||
deletes:
|
||||
- debug
|
||||
- ssl_ca
|
||||
- ssl_cert
|
||||
- ssl_key
|
||||
- use-internal-endpoints
|
||||
- verbose
|
||||
|
13
src/lib/__init__.py
Normal file
13
src/lib/__init__.py
Normal file
@ -0,0 +1,13 @@
|
||||
# Copyright 2020 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
13
src/lib/charm/__init__.py
Normal file
13
src/lib/charm/__init__.py
Normal file
@ -0,0 +1,13 @@
|
||||
# Copyright 2020 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
13
src/lib/charm/openstack/__init__.py
Normal file
13
src/lib/charm/openstack/__init__.py
Normal file
@ -0,0 +1,13 @@
|
||||
# Copyright 2020 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
172
src/lib/charm/openstack/ceph_fs.py
Normal file
172
src/lib/charm/openstack/ceph_fs.py
Normal file
@ -0,0 +1,172 @@
|
||||
# Copyright 2020 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import socket
|
||||
|
||||
import dns.resolver
|
||||
|
||||
import charms_openstack.adapters
|
||||
import charms_openstack.charm
|
||||
import charms_openstack.plugins
|
||||
|
||||
import charmhelpers.core as ch_core
|
||||
|
||||
# NOTE(fnordahl) theese out of style imports are here to help keeping helpers
|
||||
# moved from reactive module as-is to make the diff managable. At some point
|
||||
# in time we should replace them in favor of common helpers that would do the
|
||||
# same job.
|
||||
from charmhelpers.core.hookenv import (
|
||||
config, log, cached, DEBUG, unit_get,
|
||||
network_get_primary_address,
|
||||
status_set)
|
||||
from charmhelpers.contrib.network.ip import (
|
||||
get_address_in_network,
|
||||
get_ipv6_addr)
|
||||
|
||||
|
||||
charms_openstack.charm.use_defaults('charm.default-select-release')
|
||||
|
||||
|
||||
class CephFSCharmConfigurationAdapter(
|
||||
charms_openstack.adapters.ConfigurationAdapter):
|
||||
|
||||
@property
|
||||
def hostname(self):
|
||||
return self.charm_instance.hostname
|
||||
|
||||
@property
|
||||
def mds_name(self):
|
||||
return self.charm_instance.hostname
|
||||
|
||||
@property
|
||||
def networks(self):
|
||||
return self.charm_instance.get_networks('ceph-public-network')
|
||||
|
||||
@property
|
||||
def public_addr(self):
|
||||
if ch_core.hookenv.config('prefer-ipv6'):
|
||||
return get_ipv6_addr()[0]
|
||||
else:
|
||||
return self.charm_instance.get_public_addr()
|
||||
|
||||
|
||||
class CephFSCharmRelationAdapters(
|
||||
charms_openstack.adapters.OpenStackRelationAdapters):
|
||||
relation_adapters = {
|
||||
'ceph-mds': charms_openstack.plugins.CephRelationAdapter,
|
||||
}
|
||||
|
||||
|
||||
class BaseCephFSCharm(charms_openstack.plugins.CephCharm):
|
||||
abstract_class = True
|
||||
name = 'ceph-fs'
|
||||
python_version = 3
|
||||
required_relations = ['ceph-mds']
|
||||
user = 'ceph'
|
||||
group = 'ceph'
|
||||
adapters_class = CephFSCharmRelationAdapters
|
||||
configuration_class = CephFSCharmConfigurationAdapter
|
||||
ceph_service_type = charms_openstack.plugins.CephCharm.CephServiceType.mds
|
||||
ceph_service_name_override = 'mds'
|
||||
ceph_key_per_unit_name = True
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.services = [
|
||||
'ceph-mds@{}'.format(self.hostname),
|
||||
]
|
||||
self.restart_map = {
|
||||
'/etc/ceph/ceph.conf': self.services,
|
||||
}
|
||||
|
||||
# NOTE(fnordahl) moved from reactive handler module, otherwise keeping
|
||||
# these as-is to make the diff managable. At some point in time we should
|
||||
# replace them in favor of common helpers that would do the same job.
|
||||
@staticmethod
|
||||
def get_networks(config_opt='ceph-public-network'):
|
||||
"""Get all configured networks from provided config option.
|
||||
|
||||
If public network(s) are provided, go through them and return those for
|
||||
which we have an address configured.
|
||||
"""
|
||||
networks = config(config_opt)
|
||||
if networks:
|
||||
networks = networks.split()
|
||||
return [n for n in networks if get_address_in_network(n)]
|
||||
|
||||
return []
|
||||
|
||||
@cached
|
||||
def get_public_addr(self):
|
||||
if config('ceph-public-network'):
|
||||
return self.get_network_addrs('ceph-public-network')[0]
|
||||
|
||||
try:
|
||||
return network_get_primary_address('public')
|
||||
except NotImplementedError:
|
||||
log("network-get not supported", DEBUG)
|
||||
|
||||
return self.get_host_ip()
|
||||
|
||||
@cached
|
||||
@staticmethod
|
||||
def get_host_ip(hostname=None):
|
||||
if config('prefer-ipv6'):
|
||||
return get_ipv6_addr()[0]
|
||||
|
||||
hostname = hostname or unit_get('private-address')
|
||||
try:
|
||||
# Test to see if already an IPv4 address
|
||||
socket.inet_aton(hostname)
|
||||
return hostname
|
||||
except socket.error:
|
||||
# This may throw an NXDOMAIN exception; in which case
|
||||
# things are badly broken so just let it kill the hook
|
||||
answers = dns.resolver.query(hostname, 'A')
|
||||
if answers:
|
||||
return answers[0].address
|
||||
|
||||
def get_network_addrs(self, config_opt):
|
||||
"""Get all configured public networks addresses.
|
||||
|
||||
If public network(s) are provided, go through them and return the
|
||||
addresses we have configured on any of those networks.
|
||||
"""
|
||||
addrs = []
|
||||
networks = config(config_opt)
|
||||
if networks:
|
||||
networks = networks.split()
|
||||
addrs = [get_address_in_network(n) for n in networks]
|
||||
addrs = [a for a in addrs if a]
|
||||
|
||||
if not addrs:
|
||||
if networks:
|
||||
msg = ("Could not find an address on any of '%s' - resolve "
|
||||
"this error to retry" % networks)
|
||||
status_set('blocked', msg)
|
||||
raise Exception(msg)
|
||||
else:
|
||||
return [self.get_host_ip()]
|
||||
|
||||
return addrs
|
||||
|
||||
|
||||
class MitakaCephFSCharm(BaseCephFSCharm):
|
||||
release = 'mitaka'
|
||||
packages = ['ceph-mds', 'gdisk', 'ntp', 'btrfs-tools', 'xfsprogs']
|
||||
|
||||
|
||||
class UssuriCephFSCharm(BaseCephFSCharm):
|
||||
release = 'ussuri'
|
||||
packages = ['ceph-mds', 'gdisk', 'ntp', 'btrfs-progs', 'xfsprogs']
|
@ -12,283 +12,36 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
import dns.resolver
|
||||
|
||||
from charms import reactive
|
||||
from charms.reactive import when, when_not, hook
|
||||
from charms.reactive.flags import set_flag, clear_flag, is_flag_set
|
||||
from charmhelpers.core import hookenv
|
||||
from charmhelpers.core import unitdata
|
||||
from charmhelpers.core.hookenv import (
|
||||
application_version_set, config, log, ERROR, cached, DEBUG, unit_get,
|
||||
network_get_primary_address, relation_ids,
|
||||
status_set)
|
||||
from charmhelpers.core.host import (
|
||||
CompareHostReleases,
|
||||
lsb_release,
|
||||
service_restart,
|
||||
service)
|
||||
from charmhelpers.contrib.network.ip import (
|
||||
get_address_in_network,
|
||||
get_ipv6_addr)
|
||||
|
||||
from charmhelpers.fetch import (
|
||||
get_upstream_version,
|
||||
import charms_openstack.bus
|
||||
import charms_openstack.charm as charm
|
||||
|
||||
|
||||
charms_openstack.bus.discover()
|
||||
|
||||
|
||||
charm.use_defaults(
|
||||
'charm.installed',
|
||||
'config.changed',
|
||||
'config.rendered',
|
||||
'upgrade-charm',
|
||||
'update-status',
|
||||
)
|
||||
import jinja2
|
||||
|
||||
from charms.apt import queue_install, add_source
|
||||
|
||||
PACKAGES = ['ceph', 'gdisk', 'ntp', 'btrfs-tools', 'xfsprogs']
|
||||
PACKAGES_FOCAL = ['ceph', 'gdisk', 'ntp', 'btrfs-progs', 'xfsprogs']
|
||||
|
||||
TEMPLATES_DIR = 'templates'
|
||||
VERSION_PACKAGE = 'ceph-common'
|
||||
|
||||
|
||||
def render_template(template_name, context, template_dir=TEMPLATES_DIR):
|
||||
templates = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(template_dir))
|
||||
template = templates.get_template(template_name)
|
||||
return template.render(context)
|
||||
|
||||
|
||||
@when_not('apt.installed.ceph')
|
||||
def install_ceph_base():
|
||||
add_source(config('source'), key=config('key'))
|
||||
release = lsb_release()['DISTRIB_CODENAME'].lower()
|
||||
if CompareHostReleases(release) >= 'focal':
|
||||
queue_install(PACKAGES_FOCAL)
|
||||
else:
|
||||
queue_install(PACKAGES)
|
||||
|
||||
|
||||
@when_not('apt.installed.ceph-mds')
|
||||
def install_cephfs():
|
||||
queue_install(['ceph-mds'])
|
||||
|
||||
|
||||
@when('cephfs.configured')
|
||||
@when('ceph-mds.pools.available')
|
||||
@when_not('cephfs.started')
|
||||
def setup_mds(relation):
|
||||
service_name = 'ceph-mds@{}'.format(socket.gethostname())
|
||||
if service_restart(service_name):
|
||||
set_flag('cephfs.started')
|
||||
service('enable', service_name)
|
||||
application_version_set(get_upstream_version(VERSION_PACKAGE))
|
||||
else:
|
||||
log(message='Error: restarting cpeh-mds', level=ERROR)
|
||||
clear_flag('cephfs.started')
|
||||
|
||||
|
||||
@when('ceph-mds.available')
|
||||
def config_changed(ceph_client):
|
||||
charm_ceph_conf = os.path.join(os.sep,
|
||||
'etc',
|
||||
'ceph',
|
||||
'ceph.conf')
|
||||
key_path = os.path.join(os.sep,
|
||||
'var',
|
||||
'lib',
|
||||
'ceph',
|
||||
'mds',
|
||||
'ceph-{}'.format(socket.gethostname())
|
||||
)
|
||||
if not os.path.exists(key_path):
|
||||
os.makedirs(key_path)
|
||||
cephx_key = os.path.join(key_path,
|
||||
'keyring')
|
||||
|
||||
ceph_context = {
|
||||
'fsid': ceph_client.fsid(),
|
||||
'auth_supported': ceph_client.auth(),
|
||||
'use_syslog': str(config('use-syslog')).lower(),
|
||||
'mon_hosts': ' '.join(ceph_client.mon_hosts()),
|
||||
'loglevel': config('loglevel'),
|
||||
'hostname': socket.gethostname(),
|
||||
'mds_name': socket.gethostname(),
|
||||
}
|
||||
|
||||
networks = get_networks('ceph-public-network')
|
||||
if networks:
|
||||
ceph_context['ceph_public_network'] = ', '.join(networks)
|
||||
elif config('prefer-ipv6'):
|
||||
dynamic_ipv6_address = get_ipv6_addr()[0]
|
||||
ceph_context['public_addr'] = dynamic_ipv6_address
|
||||
else:
|
||||
ceph_context['public_addr'] = get_public_addr()
|
||||
|
||||
try:
|
||||
with open(charm_ceph_conf, 'w') as ceph_conf:
|
||||
ceph_conf.write(render_template('ceph.conf', ceph_context))
|
||||
except IOError as err:
|
||||
log("IOError writing ceph.conf: {}".format(err))
|
||||
clear_flag('cephfs.configured')
|
||||
return
|
||||
|
||||
try:
|
||||
with open(cephx_key, 'w') as key_file:
|
||||
key_file.write("[mds.{}]\n\tkey = {}\n".format(
|
||||
socket.gethostname(),
|
||||
ceph_client.mds_key()
|
||||
))
|
||||
except IOError as err:
|
||||
log("IOError writing mds-a.keyring: {}".format(err))
|
||||
clear_flag('cephfs.configured')
|
||||
return
|
||||
set_flag('cephfs.configured')
|
||||
|
||||
|
||||
def get_networks(config_opt='ceph-public-network'):
|
||||
"""Get all configured networks from provided config option.
|
||||
|
||||
If public network(s) are provided, go through them and return those for
|
||||
which we have an address configured.
|
||||
"""
|
||||
networks = config(config_opt)
|
||||
if networks:
|
||||
networks = networks.split()
|
||||
return [n for n in networks if get_address_in_network(n)]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
@cached
|
||||
def get_public_addr():
|
||||
if config('ceph-public-network'):
|
||||
return get_network_addrs('ceph-public-network')[0]
|
||||
|
||||
try:
|
||||
return network_get_primary_address('public')
|
||||
except NotImplementedError:
|
||||
log("network-get not supported", DEBUG)
|
||||
|
||||
return get_host_ip()
|
||||
|
||||
|
||||
@cached
|
||||
def get_host_ip(hostname=None):
|
||||
if config('prefer-ipv6'):
|
||||
return get_ipv6_addr()[0]
|
||||
|
||||
hostname = hostname or unit_get('private-address')
|
||||
try:
|
||||
# Test to see if already an IPv4 address
|
||||
socket.inet_aton(hostname)
|
||||
return hostname
|
||||
except socket.error:
|
||||
# This may throw an NXDOMAIN exception; in which case
|
||||
# things are badly broken so just let it kill the hook
|
||||
answers = dns.resolver.query(hostname, 'A')
|
||||
if answers:
|
||||
return answers[0].address
|
||||
|
||||
|
||||
def get_network_addrs(config_opt):
|
||||
"""Get all configured public networks addresses.
|
||||
|
||||
If public network(s) are provided, go through them and return the
|
||||
addresses we have configured on any of those networks.
|
||||
"""
|
||||
addrs = []
|
||||
networks = config(config_opt)
|
||||
if networks:
|
||||
networks = networks.split()
|
||||
addrs = [get_address_in_network(n) for n in networks]
|
||||
addrs = [a for a in addrs if a]
|
||||
|
||||
if not addrs:
|
||||
if networks:
|
||||
msg = ("Could not find an address on any of '%s' - resolve this "
|
||||
"error to retry" % networks)
|
||||
status_set('blocked', msg)
|
||||
raise Exception(msg)
|
||||
else:
|
||||
return [get_host_ip()]
|
||||
|
||||
return addrs
|
||||
|
||||
|
||||
def assess_status():
|
||||
"""Assess status of current unit"""
|
||||
statuses = set([])
|
||||
messages = set([])
|
||||
|
||||
# Handle Series Upgrade
|
||||
if unitdata.kv().get('charm.vault.series-upgrading'):
|
||||
status_set("blocked",
|
||||
"Ready for do-release-upgrade and reboot. "
|
||||
"Set complete when finished.")
|
||||
return
|
||||
|
||||
if is_flag_set('cephfs.started'):
|
||||
(status, message) = log_mds()
|
||||
statuses.add(status)
|
||||
messages.add(message)
|
||||
if 'blocked' in statuses:
|
||||
status = 'blocked'
|
||||
elif 'waiting' in statuses:
|
||||
status = 'waiting'
|
||||
else:
|
||||
status = 'active'
|
||||
message = '; '.join(messages)
|
||||
status_set(status, message)
|
||||
|
||||
|
||||
def get_running_mds():
|
||||
"""Returns a list of the pids of the current running MDS daemons"""
|
||||
cmd = ['pgrep', 'ceph-mds']
|
||||
try:
|
||||
result = subprocess.check_output(cmd).decode('utf-8')
|
||||
return result.split()
|
||||
except subprocess.CalledProcessError:
|
||||
return []
|
||||
|
||||
|
||||
def log_mds():
|
||||
if len(relation_ids('ceph-mds')) < 1:
|
||||
return 'blocked', 'Missing relation: monitor'
|
||||
running_mds = get_running_mds()
|
||||
if not running_mds:
|
||||
return 'blocked', 'No MDS detected using current configuration'
|
||||
else:
|
||||
return 'active', 'Unit is ready ({} MDS)'.format(len(running_mds))
|
||||
|
||||
|
||||
# Per https://github.com/juju-solutions/charms.reactive/issues/33,
|
||||
# this module may be imported multiple times so ensure the
|
||||
# initialization hook is only registered once. I have to piggy back
|
||||
# onto the namespace of a module imported before reactive discovery
|
||||
# to do this.
|
||||
if not hasattr(reactive, '_ceph_log_registered'):
|
||||
# We need to register this to run every hook, not just during install
|
||||
# and config-changed, to protect against race conditions. If we don't
|
||||
# do this, then the config in the hook environment may show updates
|
||||
# to running hooks well before the config-changed hook has been invoked
|
||||
# and the intialization provided an opertunity to be run.
|
||||
hookenv.atexit(assess_status)
|
||||
reactive._ceph_log_registered = True
|
||||
|
||||
|
||||
# Series upgrade hooks are a special case and reacting to the hook directly
|
||||
# makes sense as we may not want other charm code to run
|
||||
@hook('pre-series-upgrade')
|
||||
def pre_series_upgrade():
|
||||
"""Handler for pre-series-upgrade.
|
||||
"""
|
||||
unitdata.kv().set('charm.vault.series-upgrading', True)
|
||||
|
||||
|
||||
@hook('post-series-upgrade')
|
||||
def post_series_upgrade():
|
||||
"""Handler for post-series-upgrade.
|
||||
"""
|
||||
release = lsb_release()['DISTRIB_CODENAME'].lower()
|
||||
if CompareHostReleases(release) >= 'focal':
|
||||
queue_install(PACKAGES_FOCAL)
|
||||
unitdata.kv().set('charm.vault.series-upgrading', False)
|
||||
@reactive.when_none('charm.paused', 'run-default-update-status')
|
||||
@reactive.when('ceph-mds.available')
|
||||
def config_changed():
|
||||
ceph_mds = reactive.endpoint_from_flag('ceph-mds.available')
|
||||
with charm.provide_charm_instance() as cephfs_charm:
|
||||
cephfs_charm.configure_ceph_keyring(ceph_mds.mds_key())
|
||||
cephfs_charm.render_with_interfaces([ceph_mds])
|
||||
if reactive.is_flag_set('config.changed.source'):
|
||||
# update system source configuration and check for upgrade
|
||||
cephfs_charm.install()
|
||||
cephfs_charm.upgrade_if_available([ceph_mds])
|
||||
reactive.clear_flag('config.changed.source')
|
||||
reactive.set_flag('cephfs.configured')
|
||||
reactive.set_flag('config.rendered')
|
||||
cephfs_charm.assess_status()
|
||||
|
@ -1,24 +1,24 @@
|
||||
|
||||
[global]
|
||||
auth cluster required = {{ auth_supported }}
|
||||
auth service required = {{ auth_supported }}
|
||||
auth client required = {{ auth_supported }}
|
||||
auth cluster required = {{ ceph_mds.auth }}
|
||||
auth service required = {{ ceph_mds.auth }}
|
||||
auth client required = {{ ceph_mds.auth }}
|
||||
keyring = /etc/ceph/$cluster.$name.keyring
|
||||
mon host = {{ mon_hosts }}
|
||||
fsid = {{ fsid }}
|
||||
mon host = {{ ceph_mds.monitors }}
|
||||
fsid = {{ ceph_mds.fsid }}
|
||||
|
||||
log to syslog = {{ use_syslog }}
|
||||
err to syslog = {{ use_syslog }}
|
||||
clog to syslog = {{ use_syslog }}
|
||||
mon cluster log to syslog = {{ use_syslog }}
|
||||
debug mon = {{ loglevel }}/5
|
||||
debug osd = {{ loglevel }}/5
|
||||
log to syslog = {{ options.use_syslog }}
|
||||
err to syslog = {{ options.use_syslog }}
|
||||
clog to syslog = {{ options.use_syslog }}
|
||||
mon cluster log to syslog = {{ options.use_syslog }}
|
||||
debug mon = {{ options.loglevel }}/5
|
||||
debug osd = {{ options.loglevel }}/5
|
||||
|
||||
{% if ceph_public_network %}
|
||||
public network = {{ ceph_public_network }}
|
||||
{% if options.networks %}
|
||||
public network = {{ options.networks|join(',') }}
|
||||
{%- endif %}
|
||||
{%- if public_addr %}
|
||||
public addr = {{ public_addr }}
|
||||
{%- if options.public_addr %}
|
||||
public addr = {{ options.public_addr }}
|
||||
{%- endif %}
|
||||
|
||||
[client]
|
||||
@ -27,7 +27,7 @@ log file = /var/log/ceph.log
|
||||
[mds]
|
||||
keyring = /var/lib/ceph/mds/$cluster-$id/keyring
|
||||
|
||||
[mds.{{ mds_name }}]
|
||||
host = {{ hostname }}
|
||||
[mds.{{ options.mds_name }}]
|
||||
host = {{ options.hostname }}
|
||||
|
||||
|
||||
|
@ -23,6 +23,7 @@ configure:
|
||||
- zaza.openstack.charm_tests.keystone.setup.add_demo_user
|
||||
tests:
|
||||
- zaza.openstack.charm_tests.ceph.fs.tests.CephFSTests
|
||||
- zaza.openstack.charm_tests.ceph.fs.tests.CharmOperationTest
|
||||
tests_options:
|
||||
force_deploy:
|
||||
- focal-ussuri
|
||||
|
@ -3,7 +3,7 @@
|
||||
# requirements management in charms via bot-control. Thank you.
|
||||
#
|
||||
# Lint and unit test requirements
|
||||
flake8>=2.2.4,<=2.4.1
|
||||
flake8>=2.2.4
|
||||
stestr>=2.2.0
|
||||
requests>=2.18.4
|
||||
charms.reactive
|
||||
|
2
tox.ini
2
tox.ini
@ -89,4 +89,4 @@ commands = {posargs}
|
||||
|
||||
[flake8]
|
||||
# E402 ignore necessary for path append before sys module import in actions
|
||||
ignore = E402,W504
|
||||
ignore = E402,W504
|
||||
|
@ -13,30 +13,14 @@
|
||||
# limitations under the License.
|
||||
|
||||
import sys
|
||||
import mock
|
||||
import unittest.mock as mock
|
||||
|
||||
sys.path.append('src')
|
||||
sys.path.append('src/lib')
|
||||
|
||||
apt_pkg = mock.MagicMock()
|
||||
charmhelpers = mock.MagicMock()
|
||||
sys.modules['apt_pkg'] = apt_pkg
|
||||
sys.modules['charmhelpers'] = charmhelpers
|
||||
sys.modules['charmhelpers.core'] = charmhelpers.core
|
||||
sys.modules['charmhelpers.core.hookenv'] = charmhelpers.core.hookenv
|
||||
sys.modules['charmhelpers.core.host'] = charmhelpers.core.host
|
||||
sys.modules['charmhelpers.core.unitdata'] = charmhelpers.core.unitdata
|
||||
sys.modules['charmhelpers.core.templating'] = charmhelpers.core.templating
|
||||
sys.modules['charmhelpers.contrib'] = charmhelpers.contrib
|
||||
sys.modules['charmhelpers.contrib.openstack'] = charmhelpers.contrib.openstack
|
||||
sys.modules['charmhelpers.contrib.openstack.utils'] = (
|
||||
charmhelpers.contrib.openstack.utils)
|
||||
sys.modules['charmhelpers.contrib.openstack.templating'] = (
|
||||
charmhelpers.contrib.openstack.templating)
|
||||
sys.modules['charmhelpers.contrib.network'] = charmhelpers.contrib.network
|
||||
sys.modules['charmhelpers.contrib.network.ip'] = (
|
||||
charmhelpers.contrib.network.ip)
|
||||
sys.modules['charmhelpers.fetch'] = charmhelpers.fetch
|
||||
sys.modules['charmhelpers.cli'] = charmhelpers.cli
|
||||
sys.modules['charmhelpers.contrib.hahelpers'] = charmhelpers.contrib.hahelpers
|
||||
sys.modules['charmhelpers.contrib.hahelpers.cluster'] = (
|
||||
charmhelpers.contrib.hahelpers.cluster)
|
||||
# Mock out charmhelpers so that we can test without it.
|
||||
import charms_openstack.test_mocks # noqa
|
||||
charms_openstack.test_mocks.mock_charmhelpers()
|
||||
|
||||
sys.modules['dns'] = mock.MagicMock()
|
||||
sys.modules['dns.resolver'] = mock.MagicMock()
|
||||
|
82
unit_tests/test_lib_charm_openstack_ceph_fs.py
Normal file
82
unit_tests/test_lib_charm_openstack_ceph_fs.py
Normal file
@ -0,0 +1,82 @@
|
||||
# Copyright 2020 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest.mock as mock
|
||||
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
||||
import charm.openstack.ceph_fs as ceph_fs
|
||||
|
||||
|
||||
class TestMitakaCephFsCharm(test_utils.PatchHelper):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.patch_release('mitaka')
|
||||
self.patch('socket.gethostname', name='gethostname')
|
||||
self.gethostname.return_value = 'somehost'
|
||||
self.target = ceph_fs.MitakaCephFSCharm()
|
||||
|
||||
def test_packages(self):
|
||||
# Package list is the only difference between the past version and
|
||||
# future versions of this charm, see ``TestCephFsCharm`` for the rest
|
||||
# of the tests
|
||||
self.assertEquals(self.target.packages, [
|
||||
'ceph-mds', 'gdisk', 'ntp', 'btrfs-tools', 'xfsprogs'])
|
||||
|
||||
|
||||
class TestCephFsCharm(test_utils.PatchHelper):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.patch_release('ussuri')
|
||||
self.patch('socket.gethostname', name='gethostname')
|
||||
self.gethostname.return_value = 'somehost'
|
||||
self.target = ceph_fs.UssuriCephFSCharm()
|
||||
|
||||
def patch_target(self, attr, return_value=None):
|
||||
mocked = mock.patch.object(self.target, attr)
|
||||
self._patches[attr] = mocked
|
||||
started = mocked.start()
|
||||
started.return_value = return_value
|
||||
self._patches_start[attr] = started
|
||||
setattr(self, attr, started)
|
||||
|
||||
def test___init__(self):
|
||||
self.assertEquals(self.target.services, [
|
||||
'ceph-mds@somehost'])
|
||||
self.assertDictEqual(self.target.restart_map, {
|
||||
'/etc/ceph/ceph.conf': ['ceph-mds@somehost']})
|
||||
self.assertEquals(self.target.packages, [
|
||||
'ceph-mds', 'gdisk', 'ntp', 'btrfs-progs', 'xfsprogs'])
|
||||
|
||||
def test_configuration_class(self):
|
||||
self.assertEquals(self.target.options.hostname, 'somehost')
|
||||
self.assertEquals(self.target.options.mds_name, 'somehost')
|
||||
self.patch_target('get_networks')
|
||||
self.get_networks.return_value = ['fakeaddress']
|
||||
self.assertEquals(self.target.options.networks, ['fakeaddress'])
|
||||
self.patch_object(ceph_fs.ch_core.hookenv, 'config')
|
||||
self.config.side_effect = lambda x: {'prefer-ipv6': False}.get(x)
|
||||
self.patch_object(ceph_fs, 'get_ipv6_addr')
|
||||
self.get_ipv6_addr.return_value = ['2001:db8::fake']
|
||||
self.patch_target('get_public_addr')
|
||||
self.get_public_addr.return_value = '192.0.2.42'
|
||||
self.assertEquals(
|
||||
self.target.options.public_addr,
|
||||
'192.0.2.42')
|
||||
self.config.side_effect = lambda x: {'prefer-ipv6': True}.get(x)
|
||||
self.assertEquals(
|
||||
self.target.options.public_addr,
|
||||
'2001:db8::fake')
|
81
unit_tests/test_reactive_ceph_fs.py
Normal file
81
unit_tests/test_reactive_ceph_fs.py
Normal file
@ -0,0 +1,81 @@
|
||||
# Copyright 2020 Canonical Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest.mock as mock
|
||||
|
||||
import charm.openstack.ceph_fs as ceph_fs
|
||||
import reactive.ceph_fs as handlers
|
||||
|
||||
import charms_openstack.test_utils as test_utils
|
||||
|
||||
|
||||
class TestRegisteredHooks(test_utils.TestRegisteredHooks):
|
||||
|
||||
def test_hooks(self):
|
||||
defaults = [
|
||||
'charm.installed',
|
||||
'config.changed',
|
||||
'config.rendered',
|
||||
'upgrade-charm',
|
||||
'update-status',
|
||||
]
|
||||
hook_set = {
|
||||
'when': {
|
||||
'config_changed': ('ceph-mds.available',),
|
||||
},
|
||||
'when_none': {
|
||||
'config_changed': ('charm.paused',
|
||||
'run-default-update-status',),
|
||||
},
|
||||
}
|
||||
# test that the hooks were registered via the reactive.ceph_fs module
|
||||
self.registered_hooks_test_helper(handlers, hook_set, defaults)
|
||||
|
||||
|
||||
class TestCephFSHandlers(test_utils.PatchHelper):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.patch_release(ceph_fs.UssuriCephFSCharm.release)
|
||||
self.target = mock.MagicMock()
|
||||
self.patch_object(handlers.charm, 'provide_charm_instance',
|
||||
new=mock.MagicMock())
|
||||
self.provide_charm_instance().__enter__.return_value = \
|
||||
self.target
|
||||
self.provide_charm_instance().__exit__.return_value = None
|
||||
|
||||
def test_config_changed(self):
|
||||
self.patch_object(handlers.reactive, 'endpoint_from_flag')
|
||||
self.patch_object(handlers.reactive, 'is_flag_set')
|
||||
self.patch_object(handlers.reactive, 'clear_flag')
|
||||
self.patch_object(handlers.reactive, 'set_flag')
|
||||
ceph_mds = mock.MagicMock()
|
||||
ceph_mds.mds_key.return_value = 'fakekey'
|
||||
self.endpoint_from_flag.return_value = ceph_mds
|
||||
self.is_flag_set.return_value = False
|
||||
handlers.config_changed()
|
||||
self.endpoint_from_flag.assert_called_once_with('ceph-mds.available')
|
||||
self.target.configure_ceph_keyring.assert_called_once_with('fakekey')
|
||||
self.target.render_with_interfaces.assert_called_once_with([ceph_mds])
|
||||
self.is_flag_set.assert_called_once_with('config.changed.source')
|
||||
self.set_flag.assert_has_calls([
|
||||
mock.call('cephfs.configured'),
|
||||
mock.call('config.rendered'),
|
||||
])
|
||||
self.target.install.assert_not_called()
|
||||
self.target.upgrade_if_available.assert_not_called()
|
||||
self.is_flag_set.return_value = True
|
||||
handlers.config_changed()
|
||||
self.target.install.assert_called_once_with()
|
||||
self.target.upgrade_if_available.assert_called_once_with([ceph_mds])
|
Loading…
Reference in New Issue
Block a user