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.
|
description: Mon and OSD debug level. Max is 20.
|
||||||
source:
|
source:
|
||||||
type: string
|
type: string
|
||||||
default:
|
default: distro
|
||||||
description: |
|
description: |
|
||||||
Optional configuration to support use of additional sources such as:
|
Optional configuration to support use of additional sources such as:
|
||||||
- ppa:myteam/ppa
|
- ppa:myteam/ppa
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
includes: ['layer:basic', 'layer:apt', 'interface:ceph-mds']
|
includes: ['layer:ceph', 'interface:ceph-mds']
|
||||||
options:
|
options:
|
||||||
status:
|
basic:
|
||||||
patch-hookenv: False
|
use_venv: True
|
||||||
|
include_system_packages: False
|
||||||
repo: https://git.openstack.org/openstack/charm-ceph-fs
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import dns.resolver
|
|
||||||
|
|
||||||
from charms import reactive
|
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 (
|
import charms_openstack.bus
|
||||||
get_upstream_version,
|
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):
|
@reactive.when_none('charm.paused', 'run-default-update-status')
|
||||||
templates = jinja2.Environment(
|
@reactive.when('ceph-mds.available')
|
||||||
loader=jinja2.FileSystemLoader(template_dir))
|
def config_changed():
|
||||||
template = templates.get_template(template_name)
|
ceph_mds = reactive.endpoint_from_flag('ceph-mds.available')
|
||||||
return template.render(context)
|
with charm.provide_charm_instance() as cephfs_charm:
|
||||||
|
cephfs_charm.configure_ceph_keyring(ceph_mds.mds_key())
|
||||||
|
cephfs_charm.render_with_interfaces([ceph_mds])
|
||||||
@when_not('apt.installed.ceph')
|
if reactive.is_flag_set('config.changed.source'):
|
||||||
def install_ceph_base():
|
# update system source configuration and check for upgrade
|
||||||
add_source(config('source'), key=config('key'))
|
cephfs_charm.install()
|
||||||
release = lsb_release()['DISTRIB_CODENAME'].lower()
|
cephfs_charm.upgrade_if_available([ceph_mds])
|
||||||
if CompareHostReleases(release) >= 'focal':
|
reactive.clear_flag('config.changed.source')
|
||||||
queue_install(PACKAGES_FOCAL)
|
reactive.set_flag('cephfs.configured')
|
||||||
else:
|
reactive.set_flag('config.rendered')
|
||||||
queue_install(PACKAGES)
|
cephfs_charm.assess_status()
|
||||||
|
|
||||||
|
|
||||||
@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)
|
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
|
|
||||||
[global]
|
[global]
|
||||||
auth cluster required = {{ auth_supported }}
|
auth cluster required = {{ ceph_mds.auth }}
|
||||||
auth service required = {{ auth_supported }}
|
auth service required = {{ ceph_mds.auth }}
|
||||||
auth client required = {{ auth_supported }}
|
auth client required = {{ ceph_mds.auth }}
|
||||||
keyring = /etc/ceph/$cluster.$name.keyring
|
keyring = /etc/ceph/$cluster.$name.keyring
|
||||||
mon host = {{ mon_hosts }}
|
mon host = {{ ceph_mds.monitors }}
|
||||||
fsid = {{ fsid }}
|
fsid = {{ ceph_mds.fsid }}
|
||||||
|
|
||||||
log to syslog = {{ use_syslog }}
|
log to syslog = {{ options.use_syslog }}
|
||||||
err to syslog = {{ use_syslog }}
|
err to syslog = {{ options.use_syslog }}
|
||||||
clog to syslog = {{ use_syslog }}
|
clog to syslog = {{ options.use_syslog }}
|
||||||
mon cluster log to syslog = {{ use_syslog }}
|
mon cluster log to syslog = {{ options.use_syslog }}
|
||||||
debug mon = {{ loglevel }}/5
|
debug mon = {{ options.loglevel }}/5
|
||||||
debug osd = {{ loglevel }}/5
|
debug osd = {{ options.loglevel }}/5
|
||||||
|
|
||||||
{% if ceph_public_network %}
|
{% if options.networks %}
|
||||||
public network = {{ ceph_public_network }}
|
public network = {{ options.networks|join(',') }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- if public_addr %}
|
{%- if options.public_addr %}
|
||||||
public addr = {{ public_addr }}
|
public addr = {{ options.public_addr }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
[client]
|
[client]
|
||||||
@ -27,7 +27,7 @@ log file = /var/log/ceph.log
|
|||||||
[mds]
|
[mds]
|
||||||
keyring = /var/lib/ceph/mds/$cluster-$id/keyring
|
keyring = /var/lib/ceph/mds/$cluster-$id/keyring
|
||||||
|
|
||||||
[mds.{{ mds_name }}]
|
[mds.{{ options.mds_name }}]
|
||||||
host = {{ hostname }}
|
host = {{ options.hostname }}
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ configure:
|
|||||||
- zaza.openstack.charm_tests.keystone.setup.add_demo_user
|
- zaza.openstack.charm_tests.keystone.setup.add_demo_user
|
||||||
tests:
|
tests:
|
||||||
- zaza.openstack.charm_tests.ceph.fs.tests.CephFSTests
|
- zaza.openstack.charm_tests.ceph.fs.tests.CephFSTests
|
||||||
|
- zaza.openstack.charm_tests.ceph.fs.tests.CharmOperationTest
|
||||||
tests_options:
|
tests_options:
|
||||||
force_deploy:
|
force_deploy:
|
||||||
- focal-ussuri
|
- focal-ussuri
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# requirements management in charms via bot-control. Thank you.
|
# requirements management in charms via bot-control. Thank you.
|
||||||
#
|
#
|
||||||
# Lint and unit test requirements
|
# Lint and unit test requirements
|
||||||
flake8>=2.2.4,<=2.4.1
|
flake8>=2.2.4
|
||||||
stestr>=2.2.0
|
stestr>=2.2.0
|
||||||
requests>=2.18.4
|
requests>=2.18.4
|
||||||
charms.reactive
|
charms.reactive
|
||||||
|
@ -13,30 +13,14 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import mock
|
import unittest.mock as mock
|
||||||
|
|
||||||
sys.path.append('src')
|
sys.path.append('src')
|
||||||
|
sys.path.append('src/lib')
|
||||||
|
|
||||||
apt_pkg = mock.MagicMock()
|
# Mock out charmhelpers so that we can test without it.
|
||||||
charmhelpers = mock.MagicMock()
|
import charms_openstack.test_mocks # noqa
|
||||||
sys.modules['apt_pkg'] = apt_pkg
|
charms_openstack.test_mocks.mock_charmhelpers()
|
||||||
sys.modules['charmhelpers'] = charmhelpers
|
|
||||||
sys.modules['charmhelpers.core'] = charmhelpers.core
|
sys.modules['dns'] = mock.MagicMock()
|
||||||
sys.modules['charmhelpers.core.hookenv'] = charmhelpers.core.hookenv
|
sys.modules['dns.resolver'] = mock.MagicMock()
|
||||||
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)
|
|
||||||
|
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