Add support for user-provided ceph config
Adds a new config-flags option to the charm that supports setting a dictionary of ceph configuration settings that will be applied to ceph.conf. This implementation supports config sections so that settings can be applied to any section supported by the ceph.conf template in the charm. Change-Id: I306fd138820746c565f8c7cd83d3ffcc388b9735 Closes-Bug: 1522375
This commit is contained in:
parent
7733790748
commit
8f0347d692
@ -4,10 +4,13 @@ include:
|
|||||||
- core
|
- core
|
||||||
- cli
|
- cli
|
||||||
- fetch
|
- fetch
|
||||||
- contrib.storage.linux:
|
- contrib.python.packages
|
||||||
- ceph
|
- contrib.storage.linux
|
||||||
- utils
|
|
||||||
- contrib.openstack.alternatives
|
- contrib.openstack.alternatives
|
||||||
- contrib.network.ip
|
- contrib.network.ip
|
||||||
|
- contrib.openstack:
|
||||||
|
- alternatives
|
||||||
|
- exceptions
|
||||||
|
- utils
|
||||||
- contrib.charmsupport
|
- contrib.charmsupport
|
||||||
- contrib.hardening|inc=*
|
- contrib.hardening|inc=*
|
||||||
|
19
config.yaml
19
config.yaml
@ -3,6 +3,25 @@ options:
|
|||||||
default: 1
|
default: 1
|
||||||
type: int
|
type: int
|
||||||
description: OSD debug level. Max is 20.
|
description: OSD debug level. Max is 20.
|
||||||
|
config-flags:
|
||||||
|
type: string
|
||||||
|
default:
|
||||||
|
description: |
|
||||||
|
User provided Ceph configuration. Supports a string representation of
|
||||||
|
a python dictionary where each top-level key represents a section in
|
||||||
|
the ceph.conf template. You may only use sections supported in the
|
||||||
|
template.
|
||||||
|
.
|
||||||
|
WARNING: this is not the recommended way to configure the underlying
|
||||||
|
services that this charm installs and is used at the user's own risk.
|
||||||
|
This option is mainly provided as a stop-gap for users that either
|
||||||
|
want to test the effect of modifying some config or who have found
|
||||||
|
a critical bug in the way the charm has configured their services
|
||||||
|
and need it fixed immediately. We ask that whenever this is used,
|
||||||
|
that the user consider opening a bug on this charm at
|
||||||
|
http://bugs.launchpad.net/charms providing an explanation of why the
|
||||||
|
config was needed so that we may consider it for inclusion as a
|
||||||
|
natively supported config in the the charm.
|
||||||
osd-devices:
|
osd-devices:
|
||||||
type: string
|
type: string
|
||||||
default: /dev/vdb
|
default: /dev/vdb
|
||||||
|
@ -66,6 +66,7 @@ from charmhelpers.contrib.network.ip import (
|
|||||||
format_ipv6_addr,
|
format_ipv6_addr,
|
||||||
)
|
)
|
||||||
from charmhelpers.contrib.storage.linux.ceph import (
|
from charmhelpers.contrib.storage.linux.ceph import (
|
||||||
|
CephConfContext,
|
||||||
monitor_key_set,
|
monitor_key_set,
|
||||||
monitor_key_exists,
|
monitor_key_exists,
|
||||||
monitor_key_get)
|
monitor_key_get)
|
||||||
@ -304,7 +305,7 @@ def use_short_objects():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def emit_cephconf():
|
def get_ceph_context():
|
||||||
mon_hosts = get_mon_hosts()
|
mon_hosts = get_mon_hosts()
|
||||||
log('Monitor hosts are ' + repr(mon_hosts))
|
log('Monitor hosts are ' + repr(mon_hosts))
|
||||||
|
|
||||||
@ -348,13 +349,21 @@ def emit_cephconf():
|
|||||||
"have support for Availability Zones"
|
"have support for Availability Zones"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# NOTE(dosaboy): these sections must correspond to what is supported in the
|
||||||
|
# config template.
|
||||||
|
sections = ['global', 'osd']
|
||||||
|
cephcontext.update(CephConfContext(permitted_sections=sections)())
|
||||||
|
return cephcontext
|
||||||
|
|
||||||
|
|
||||||
|
def emit_cephconf():
|
||||||
# Install ceph.conf as an alternative to support
|
# Install ceph.conf as an alternative to support
|
||||||
# co-existence with other charms that write this file
|
# co-existence with other charms that write this file
|
||||||
charm_ceph_conf = "/var/lib/charm/{}/ceph.conf".format(service_name())
|
charm_ceph_conf = "/var/lib/charm/{}/ceph.conf".format(service_name())
|
||||||
mkdir(os.path.dirname(charm_ceph_conf), owner=ceph.ceph_user(),
|
mkdir(os.path.dirname(charm_ceph_conf), owner=ceph.ceph_user(),
|
||||||
group=ceph.ceph_user())
|
group=ceph.ceph_user())
|
||||||
with open(charm_ceph_conf, 'w') as cephconf:
|
with open(charm_ceph_conf, 'w') as cephconf:
|
||||||
cephconf.write(render_template('ceph.conf', cephcontext))
|
cephconf.write(render_template('ceph.conf', get_ceph_context()))
|
||||||
install_alternative('ceph.conf', '/etc/ceph/ceph.conf',
|
install_alternative('ceph.conf', '/etc/ceph/ceph.conf',
|
||||||
charm_ceph_conf, 90)
|
charm_ceph_conf, 90)
|
||||||
|
|
||||||
|
@ -405,10 +405,10 @@ def is_ip(address):
|
|||||||
Returns True if address is a valid IP address.
|
Returns True if address is a valid IP address.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Test to see if already an IPv4 address
|
# Test to see if already an IPv4/IPv6 address
|
||||||
socket.inet_aton(address)
|
address = netaddr.IPAddress(address)
|
||||||
return True
|
return True
|
||||||
except socket.error:
|
except netaddr.AddrFormatError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
6
hooks/charmhelpers/contrib/openstack/exceptions.py
Normal file
6
hooks/charmhelpers/contrib/openstack/exceptions.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
class OSContextError(Exception):
|
||||||
|
"""Raised when an error occurs during context generation.
|
||||||
|
|
||||||
|
This exception is principally used in contrib.openstack.context
|
||||||
|
"""
|
||||||
|
pass
|
1738
hooks/charmhelpers/contrib/openstack/utils.py
Normal file
1738
hooks/charmhelpers/contrib/openstack/utils.py
Normal file
File diff suppressed because it is too large
Load Diff
15
hooks/charmhelpers/contrib/python/__init__.py
Normal file
15
hooks/charmhelpers/contrib/python/__init__.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
145
hooks/charmhelpers/contrib/python/packages.py
Normal file
145
hooks/charmhelpers/contrib/python/packages.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding: utf-8
|
||||||
|
|
||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from charmhelpers.fetch import apt_install, apt_update
|
||||||
|
from charmhelpers.core.hookenv import charm_dir, log
|
||||||
|
|
||||||
|
__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
|
||||||
|
|
||||||
|
|
||||||
|
def pip_execute(*args, **kwargs):
|
||||||
|
"""Overriden pip_execute() to stop sys.path being changed.
|
||||||
|
|
||||||
|
The act of importing main from the pip module seems to cause add wheels
|
||||||
|
from the /usr/share/python-wheels which are installed by various tools.
|
||||||
|
This function ensures that sys.path remains the same after the call is
|
||||||
|
executed.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
_path = sys.path
|
||||||
|
try:
|
||||||
|
from pip import main as _pip_execute
|
||||||
|
except ImportError:
|
||||||
|
apt_update()
|
||||||
|
apt_install('python-pip')
|
||||||
|
from pip import main as _pip_execute
|
||||||
|
_pip_execute(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
sys.path = _path
|
||||||
|
|
||||||
|
|
||||||
|
def parse_options(given, available):
|
||||||
|
"""Given a set of options, check if available"""
|
||||||
|
for key, value in sorted(given.items()):
|
||||||
|
if not value:
|
||||||
|
continue
|
||||||
|
if key in available:
|
||||||
|
yield "--{0}={1}".format(key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def pip_install_requirements(requirements, constraints=None, **options):
|
||||||
|
"""Install a requirements file.
|
||||||
|
|
||||||
|
:param constraints: Path to pip constraints file.
|
||||||
|
http://pip.readthedocs.org/en/stable/user_guide/#constraints-files
|
||||||
|
"""
|
||||||
|
command = ["install"]
|
||||||
|
|
||||||
|
available_options = ('proxy', 'src', 'log', )
|
||||||
|
for option in parse_options(options, available_options):
|
||||||
|
command.append(option)
|
||||||
|
|
||||||
|
command.append("-r {0}".format(requirements))
|
||||||
|
if constraints:
|
||||||
|
command.append("-c {0}".format(constraints))
|
||||||
|
log("Installing from file: {} with constraints {} "
|
||||||
|
"and options: {}".format(requirements, constraints, command))
|
||||||
|
else:
|
||||||
|
log("Installing from file: {} with options: {}".format(requirements,
|
||||||
|
command))
|
||||||
|
pip_execute(command)
|
||||||
|
|
||||||
|
|
||||||
|
def pip_install(package, fatal=False, upgrade=False, venv=None, **options):
|
||||||
|
"""Install a python package"""
|
||||||
|
if venv:
|
||||||
|
venv_python = os.path.join(venv, 'bin/pip')
|
||||||
|
command = [venv_python, "install"]
|
||||||
|
else:
|
||||||
|
command = ["install"]
|
||||||
|
|
||||||
|
available_options = ('proxy', 'src', 'log', 'index-url', )
|
||||||
|
for option in parse_options(options, available_options):
|
||||||
|
command.append(option)
|
||||||
|
|
||||||
|
if upgrade:
|
||||||
|
command.append('--upgrade')
|
||||||
|
|
||||||
|
if isinstance(package, list):
|
||||||
|
command.extend(package)
|
||||||
|
else:
|
||||||
|
command.append(package)
|
||||||
|
|
||||||
|
log("Installing {} package with options: {}".format(package,
|
||||||
|
command))
|
||||||
|
if venv:
|
||||||
|
subprocess.check_call(command)
|
||||||
|
else:
|
||||||
|
pip_execute(command)
|
||||||
|
|
||||||
|
|
||||||
|
def pip_uninstall(package, **options):
|
||||||
|
"""Uninstall a python package"""
|
||||||
|
command = ["uninstall", "-q", "-y"]
|
||||||
|
|
||||||
|
available_options = ('proxy', 'log', )
|
||||||
|
for option in parse_options(options, available_options):
|
||||||
|
command.append(option)
|
||||||
|
|
||||||
|
if isinstance(package, list):
|
||||||
|
command.extend(package)
|
||||||
|
else:
|
||||||
|
command.append(package)
|
||||||
|
|
||||||
|
log("Uninstalling {} package with options: {}".format(package,
|
||||||
|
command))
|
||||||
|
pip_execute(command)
|
||||||
|
|
||||||
|
|
||||||
|
def pip_list():
|
||||||
|
"""Returns the list of current python installed packages
|
||||||
|
"""
|
||||||
|
return pip_execute(["list"])
|
||||||
|
|
||||||
|
|
||||||
|
def pip_create_virtualenv(path=None):
|
||||||
|
"""Create an isolated Python environment."""
|
||||||
|
apt_install('python-virtualenv')
|
||||||
|
|
||||||
|
if path:
|
||||||
|
venv_path = path
|
||||||
|
else:
|
||||||
|
venv_path = os.path.join(charm_dir(), 'venv')
|
||||||
|
|
||||||
|
if not os.path.exists(venv_path):
|
||||||
|
subprocess.check_call(['virtualenv', venv_path])
|
@ -40,6 +40,7 @@ from subprocess import (
|
|||||||
CalledProcessError,
|
CalledProcessError,
|
||||||
)
|
)
|
||||||
from charmhelpers.core.hookenv import (
|
from charmhelpers.core.hookenv import (
|
||||||
|
config,
|
||||||
local_unit,
|
local_unit,
|
||||||
relation_get,
|
relation_get,
|
||||||
relation_ids,
|
relation_ids,
|
||||||
@ -64,6 +65,7 @@ from charmhelpers.fetch import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from charmhelpers.core.kernel import modprobe
|
from charmhelpers.core.kernel import modprobe
|
||||||
|
from charmhelpers.contrib.openstack.utils import config_flags_parser
|
||||||
|
|
||||||
KEYRING = '/etc/ceph/ceph.client.{}.keyring'
|
KEYRING = '/etc/ceph/ceph.client.{}.keyring'
|
||||||
KEYFILE = '/etc/ceph/ceph.client.{}.key'
|
KEYFILE = '/etc/ceph/ceph.client.{}.key'
|
||||||
@ -1204,3 +1206,42 @@ def send_request_if_needed(request, relation='ceph'):
|
|||||||
for rid in relation_ids(relation):
|
for rid in relation_ids(relation):
|
||||||
log('Sending request {}'.format(request.request_id), level=DEBUG)
|
log('Sending request {}'.format(request.request_id), level=DEBUG)
|
||||||
relation_set(relation_id=rid, broker_req=request.request)
|
relation_set(relation_id=rid, broker_req=request.request)
|
||||||
|
|
||||||
|
|
||||||
|
class CephConfContext(object):
|
||||||
|
"""Ceph config (ceph.conf) context.
|
||||||
|
|
||||||
|
Supports user-provided Ceph configuration settings. Use can provide a
|
||||||
|
dictionary as the value for the config-flags charm option containing
|
||||||
|
Ceph configuration settings keyede by their section in ceph.conf.
|
||||||
|
"""
|
||||||
|
def __init__(self, permitted_sections=None):
|
||||||
|
self.permitted_sections = permitted_sections or []
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
conf = config('config-flags')
|
||||||
|
if not conf:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
conf = config_flags_parser(conf)
|
||||||
|
if type(conf) != dict:
|
||||||
|
log("Provided config-flags is not a dictionary - ignoring",
|
||||||
|
level=WARNING)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
permitted = self.permitted_sections
|
||||||
|
if permitted:
|
||||||
|
diff = set(conf.keys()).difference(set(permitted))
|
||||||
|
if diff:
|
||||||
|
log("Config-flags contains invalid keys '%s' - they will be "
|
||||||
|
"ignored" % (', '.join(diff)), level=WARNING)
|
||||||
|
|
||||||
|
ceph_conf = {}
|
||||||
|
for key in conf:
|
||||||
|
if permitted and key not in permitted:
|
||||||
|
log("Ignoring key '%s'" % key, level=WARNING)
|
||||||
|
continue
|
||||||
|
|
||||||
|
ceph_conf[key] = conf[key]
|
||||||
|
|
||||||
|
return ceph_conf
|
||||||
|
88
hooks/charmhelpers/contrib/storage/linux/loopback.py
Normal file
88
hooks/charmhelpers/contrib/storage/linux/loopback.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from subprocess import (
|
||||||
|
check_call,
|
||||||
|
check_output,
|
||||||
|
)
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
# loopback device helpers.
|
||||||
|
##################################################
|
||||||
|
def loopback_devices():
|
||||||
|
'''
|
||||||
|
Parse through 'losetup -a' output to determine currently mapped
|
||||||
|
loopback devices. Output is expected to look like:
|
||||||
|
|
||||||
|
/dev/loop0: [0807]:961814 (/tmp/my.img)
|
||||||
|
|
||||||
|
:returns: dict: a dict mapping {loopback_dev: backing_file}
|
||||||
|
'''
|
||||||
|
loopbacks = {}
|
||||||
|
cmd = ['losetup', '-a']
|
||||||
|
devs = [d.strip().split(' ') for d in
|
||||||
|
check_output(cmd).splitlines() if d != '']
|
||||||
|
for dev, _, f in devs:
|
||||||
|
loopbacks[dev.replace(':', '')] = re.search('\((\S+)\)', f).groups()[0]
|
||||||
|
return loopbacks
|
||||||
|
|
||||||
|
|
||||||
|
def create_loopback(file_path):
|
||||||
|
'''
|
||||||
|
Create a loopback device for a given backing file.
|
||||||
|
|
||||||
|
:returns: str: Full path to new loopback device (eg, /dev/loop0)
|
||||||
|
'''
|
||||||
|
file_path = os.path.abspath(file_path)
|
||||||
|
check_call(['losetup', '--find', file_path])
|
||||||
|
for d, f in six.iteritems(loopback_devices()):
|
||||||
|
if f == file_path:
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_loopback_device(path, size):
|
||||||
|
'''
|
||||||
|
Ensure a loopback device exists for a given backing file path and size.
|
||||||
|
If it a loopback device is not mapped to file, a new one will be created.
|
||||||
|
|
||||||
|
TODO: Confirm size of found loopback device.
|
||||||
|
|
||||||
|
:returns: str: Full path to the ensured loopback device (eg, /dev/loop0)
|
||||||
|
'''
|
||||||
|
for d, f in six.iteritems(loopback_devices()):
|
||||||
|
if f == path:
|
||||||
|
return d
|
||||||
|
|
||||||
|
if not os.path.exists(path):
|
||||||
|
cmd = ['truncate', '--size', size, path]
|
||||||
|
check_call(cmd)
|
||||||
|
|
||||||
|
return create_loopback(path)
|
||||||
|
|
||||||
|
|
||||||
|
def is_mapped_loopback_device(device):
|
||||||
|
"""
|
||||||
|
Checks if a given device name is an existing/mapped loopback device.
|
||||||
|
:param device: str: Full path to the device (eg, /dev/loop1).
|
||||||
|
:returns: str: Path to the backing file if is a loopback device
|
||||||
|
empty string otherwise
|
||||||
|
"""
|
||||||
|
return loopback_devices().get(device, "")
|
105
hooks/charmhelpers/contrib/storage/linux/lvm.py
Normal file
105
hooks/charmhelpers/contrib/storage/linux/lvm.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Copyright 2014-2015 Canonical Limited.
|
||||||
|
#
|
||||||
|
# This file is part of charm-helpers.
|
||||||
|
#
|
||||||
|
# charm-helpers is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||||
|
# published by the Free Software Foundation.
|
||||||
|
#
|
||||||
|
# charm-helpers is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
|
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from subprocess import (
|
||||||
|
CalledProcessError,
|
||||||
|
check_call,
|
||||||
|
check_output,
|
||||||
|
Popen,
|
||||||
|
PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
# LVM helpers.
|
||||||
|
##################################################
|
||||||
|
def deactivate_lvm_volume_group(block_device):
|
||||||
|
'''
|
||||||
|
Deactivate any volume gruop associated with an LVM physical volume.
|
||||||
|
|
||||||
|
:param block_device: str: Full path to LVM physical volume
|
||||||
|
'''
|
||||||
|
vg = list_lvm_volume_group(block_device)
|
||||||
|
if vg:
|
||||||
|
cmd = ['vgchange', '-an', vg]
|
||||||
|
check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def is_lvm_physical_volume(block_device):
|
||||||
|
'''
|
||||||
|
Determine whether a block device is initialized as an LVM PV.
|
||||||
|
|
||||||
|
:param block_device: str: Full path of block device to inspect.
|
||||||
|
|
||||||
|
:returns: boolean: True if block device is a PV, False if not.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
check_output(['pvdisplay', block_device])
|
||||||
|
return True
|
||||||
|
except CalledProcessError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def remove_lvm_physical_volume(block_device):
|
||||||
|
'''
|
||||||
|
Remove LVM PV signatures from a given block device.
|
||||||
|
|
||||||
|
:param block_device: str: Full path of block device to scrub.
|
||||||
|
'''
|
||||||
|
p = Popen(['pvremove', '-ff', block_device],
|
||||||
|
stdin=PIPE)
|
||||||
|
p.communicate(input='y\n')
|
||||||
|
|
||||||
|
|
||||||
|
def list_lvm_volume_group(block_device):
|
||||||
|
'''
|
||||||
|
List LVM volume group associated with a given block device.
|
||||||
|
|
||||||
|
Assumes block device is a valid LVM PV.
|
||||||
|
|
||||||
|
:param block_device: str: Full path of block device to inspect.
|
||||||
|
|
||||||
|
:returns: str: Name of volume group associated with block device or None
|
||||||
|
'''
|
||||||
|
vg = None
|
||||||
|
pvd = check_output(['pvdisplay', block_device]).splitlines()
|
||||||
|
for l in pvd:
|
||||||
|
l = l.decode('UTF-8')
|
||||||
|
if l.strip().startswith('VG Name'):
|
||||||
|
vg = ' '.join(l.strip().split()[2:])
|
||||||
|
return vg
|
||||||
|
|
||||||
|
|
||||||
|
def create_lvm_physical_volume(block_device):
|
||||||
|
'''
|
||||||
|
Initialize a block device as an LVM physical volume.
|
||||||
|
|
||||||
|
:param block_device: str: Full path of block device to initialize.
|
||||||
|
|
||||||
|
'''
|
||||||
|
check_call(['pvcreate', block_device])
|
||||||
|
|
||||||
|
|
||||||
|
def create_lvm_volume_group(volume_group, block_device):
|
||||||
|
'''
|
||||||
|
Create an LVM volume group backed by a given block device.
|
||||||
|
|
||||||
|
Assumes block device has already been initialized as an LVM PV.
|
||||||
|
|
||||||
|
:param volume_group: str: Name of volume group to create.
|
||||||
|
:block_device: str: Full path of PV-initialized block device.
|
||||||
|
'''
|
||||||
|
check_call(['vgcreate', volume_group, block_device])
|
@ -106,6 +106,14 @@ CLOUD_ARCHIVE_POCKETS = {
|
|||||||
'mitaka/proposed': 'trusty-proposed/mitaka',
|
'mitaka/proposed': 'trusty-proposed/mitaka',
|
||||||
'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
|
'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
|
||||||
'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
|
'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
|
||||||
|
# Newton
|
||||||
|
'newton': 'xenial-updates/newton',
|
||||||
|
'xenial-newton': 'xenial-updates/newton',
|
||||||
|
'xenial-newton/updates': 'xenial-updates/newton',
|
||||||
|
'xenial-updates/newton': 'xenial-updates/newton',
|
||||||
|
'newton/proposed': 'xenial-proposed/newton',
|
||||||
|
'xenial-newton/proposed': 'xenial-proposed/newton',
|
||||||
|
'xenial-proposed/newton': 'xenial-proposed/newton',
|
||||||
}
|
}
|
||||||
|
|
||||||
# The order of this list is very important. Handlers should be listed in from
|
# The order of this list is very important. Handlers should be listed in from
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
[global]
|
[global]
|
||||||
{% if old_auth %}
|
{%- if old_auth %}
|
||||||
auth supported = {{ auth_supported }}
|
auth supported = {{ auth_supported }}
|
||||||
{% else %}
|
{%- else %}
|
||||||
auth cluster required = {{ auth_supported }}
|
auth cluster required = {{ auth_supported }}
|
||||||
auth service required = {{ auth_supported }}
|
auth service required = {{ auth_supported }}
|
||||||
auth client required = {{ auth_supported }}
|
auth client required = {{ auth_supported }}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
keyring = /etc/ceph/$cluster.$name.keyring
|
keyring = /etc/ceph/$cluster.$name.keyring
|
||||||
mon host = {{ mon_hosts }}
|
mon host = {{ mon_hosts }}
|
||||||
fsid = {{ fsid }}
|
fsid = {{ fsid }}
|
||||||
@ -15,22 +15,27 @@ err to syslog = {{ use_syslog }}
|
|||||||
clog to syslog = {{ use_syslog }}
|
clog to syslog = {{ use_syslog }}
|
||||||
debug osd = {{ loglevel }}/5
|
debug osd = {{ loglevel }}/5
|
||||||
|
|
||||||
{%- if ceph_public_network is string %}
|
{% if ceph_public_network is string %}
|
||||||
public network = {{ ceph_public_network }}
|
public network = {{ ceph_public_network }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- if ceph_cluster_network is string %}
|
{%- if ceph_cluster_network is string %}
|
||||||
cluster network = {{ ceph_cluster_network }}
|
cluster network = {{ ceph_cluster_network }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
{%- if public_addr %}
|
||||||
{% if public_addr %}
|
|
||||||
public addr = {{ public_addr }}
|
public addr = {{ public_addr }}
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
{% if cluster_addr %}
|
{%- if cluster_addr %}
|
||||||
cluster addr = {{ cluster_addr }}
|
cluster addr = {{ cluster_addr }}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
{%- if crush_location %}
|
||||||
{% if crush_location %}
|
|
||||||
osd crush location = {{crush_location}}
|
osd crush location = {{crush_location}}
|
||||||
|
{%- endif %}
|
||||||
|
{% if global -%}
|
||||||
|
# The following are user-provided options provided via the config-flags charm option.
|
||||||
|
# User-provided [global] section config
|
||||||
|
{% for key in global -%}
|
||||||
|
{{ key }} = {{ global[key] }}
|
||||||
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
[client.osd-upgrade]
|
[client.osd-upgrade]
|
||||||
@ -47,9 +52,13 @@ keyring = /var/lib/ceph/osd/$cluster-$id/keyring
|
|||||||
osd journal size = {{ osd_journal_size }}
|
osd journal size = {{ osd_journal_size }}
|
||||||
filestore xattr use omap = true
|
filestore xattr use omap = true
|
||||||
journal dio = {{ dio }}
|
journal dio = {{ dio }}
|
||||||
|
|
||||||
{%- if short_object_len %}
|
{%- if short_object_len %}
|
||||||
osd max object name len = 256
|
osd max object name len = 256
|
||||||
osd max object namespace len = 64
|
osd max object namespace len = 64
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if osd -%}
|
||||||
|
# The following are user-provided options provided via the config-flags charm option.
|
||||||
|
{% for key in osd -%}
|
||||||
|
{{ key }} = {{ osd[key] }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
118
unit_tests/test_ceph_hooks.py
Normal file
118
unit_tests/test_ceph_hooks.py
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import copy
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
import charmhelpers.contrib.storage.linux.ceph as ceph
|
||||||
|
import ceph_hooks
|
||||||
|
|
||||||
|
|
||||||
|
CHARM_CONFIG = {'config-flags': '',
|
||||||
|
'loglevel': 1,
|
||||||
|
'use-syslog': True,
|
||||||
|
'osd-journal-size': 1024,
|
||||||
|
'use-direct-io': True,
|
||||||
|
'osd-format': 'ext4',
|
||||||
|
'prefer-ipv6': False,
|
||||||
|
'customize-failure-domain': False}
|
||||||
|
|
||||||
|
|
||||||
|
class CephHooksTestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(CephHooksTestCase, self).setUp()
|
||||||
|
|
||||||
|
@patch.object(ceph_hooks, 'get_fsid', lambda *args: '1234')
|
||||||
|
@patch.object(ceph_hooks, 'get_auth', lambda *args: False)
|
||||||
|
@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, '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,
|
||||||
|
'osd_journal_size': 1024,
|
||||||
|
'public_addr': '10.0.0.1',
|
||||||
|
'short_object_len': True,
|
||||||
|
'use_syslog': 'true'}
|
||||||
|
self.assertEqual(ctxt, expected)
|
||||||
|
|
||||||
|
@patch.object(ceph_hooks, 'get_fsid', lambda *args: '1234')
|
||||||
|
@patch.object(ceph_hooks, 'get_auth', lambda *args: False)
|
||||||
|
@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, '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'] = '{"osd": {"osd max write size": 1024}}'
|
||||||
|
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,
|
||||||
|
'osd': {'osd max write size': 1024},
|
||||||
|
'osd_journal_size': 1024,
|
||||||
|
'public_addr': '10.0.0.1',
|
||||||
|
'short_object_len': True,
|
||||||
|
'use_syslog': 'true'}
|
||||||
|
self.assertEqual(ctxt, expected)
|
||||||
|
|
||||||
|
@patch.object(ceph_hooks, 'get_fsid', lambda *args: '1234')
|
||||||
|
@patch.object(ceph_hooks, 'get_auth', lambda *args: False)
|
||||||
|
@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, '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'] = ('{"osd": {"osd max write size": 1024},'
|
||||||
|
'"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,
|
||||||
|
'osd': {'osd max write size': 1024},
|
||||||
|
'osd_journal_size': 1024,
|
||||||
|
'public_addr': '10.0.0.1',
|
||||||
|
'short_object_len': True,
|
||||||
|
'use_syslog': 'true'}
|
||||||
|
self.assertEqual(ctxt, expected)
|
@ -132,6 +132,7 @@ class UpgradeRollingTestCase(test_utils.CharmTestCase):
|
|||||||
'Waiting on ip-192-168-1-2 to finish upgrading')
|
'Waiting on ip-192-168-1-2 to finish upgrading')
|
||||||
lock_and_roll.assert_called_with(my_name="ip-192-168-1-3")
|
lock_and_roll.assert_called_with(my_name="ip-192-168-1-3")
|
||||||
|
|
||||||
|
@patch('time.time', lambda *args: previous_node_start_time + 10 * 60 + 1)
|
||||||
@patch('ceph_hooks.monitor_key_get')
|
@patch('ceph_hooks.monitor_key_get')
|
||||||
@patch('ceph_hooks.monitor_key_exists')
|
@patch('ceph_hooks.monitor_key_exists')
|
||||||
def test_wait_on_previous_node(self,
|
def test_wait_on_previous_node(self,
|
||||||
|
Loading…
Reference in New Issue
Block a user