Add support for Ceph Bobtail LTS release
- Add support for keystone based authentication (use keystone charm). - Misc other updates including new cephx options. Fixes - Improve hostname -> ip address resolution - Disable 100-continue processing as requires a fork of mod_fastcgi - Resync utils.py, ceph.py across ceph charms.
This commit is contained in:
commit
661e5015e4
@ -29,15 +29,31 @@ You can then directly access the RADOS gateway by exposing the service::
|
||||
The gateway can be accessed over port 80 (as show in juju status exposed
|
||||
ports).
|
||||
|
||||
Access
|
||||
======
|
||||
|
||||
Note that you will need to login to one of the service units supporting the
|
||||
ceph charm to generate some access credentials::
|
||||
|
||||
juju ssh ceph/0 \
|
||||
'sudo radosgw-admin user create --uid="ubuntu" --display-name="Ubuntu Ceph"'
|
||||
|
||||
|
||||
For security reasons the ceph-radosgw charm is not setup with appropriate
|
||||
permissions to administer the ceph cluster.
|
||||
|
||||
Keystone Integration
|
||||
====================
|
||||
|
||||
Ceph >= 0.55 integrates with Openstack Keystone for authentication of Swift requests.
|
||||
|
||||
This is enabled by relating the ceph-radosgw service with keystone::
|
||||
|
||||
juju deploy keystone
|
||||
juju add-relation keystone ceph-radosgw
|
||||
|
||||
If you try to relate the radosgw to keystone with an earlier version of ceph the hook
|
||||
will error out to let you know.
|
||||
|
||||
Scale-out
|
||||
=========
|
||||
|
||||
@ -62,8 +78,7 @@ Location: http://jujucharms.com/charms/ceph-radosgw
|
||||
Bootnotes
|
||||
=========
|
||||
|
||||
The Ceph RADOS Gateway makes use of a multiverse package,
|
||||
libapache2-mod-fastcgi. As such it will try to automatically enable the
|
||||
multiverse pocket in /etc/apt/sources.list. Note that there is noting
|
||||
'wrong' with multiverse components - they typically have less liberal
|
||||
licensing policies or suchlike.
|
||||
The Ceph RADOS Gateway makes use of a multiverse package libapache2-mod-fastcgi.
|
||||
As such it will try to automatically enable the multiverse pocket in
|
||||
/etc/apt/sources.list. Note that there is noting 'wrong' with multiverse
|
||||
components - they typically have less liberal licensing policies or suchlike.
|
21
config.yaml
21
config.yaml
@ -20,3 +20,24 @@ options:
|
||||
description: |
|
||||
Key ID to import to the apt keyring to support use with arbitary source
|
||||
configuration from outside of Launchpad archives or PPA's.
|
||||
# Keystone integration
|
||||
operator-roles:
|
||||
default: "Member,Admin"
|
||||
type: string
|
||||
description: |
|
||||
Comma-separated list of Swift operator roles; used when integrating with
|
||||
OpenStack Keystone.
|
||||
region:
|
||||
default: RegionOne
|
||||
type: string
|
||||
description: |
|
||||
OpenStack region that the RADOS gateway supports; used when integrating with
|
||||
OpenStack Keystone.
|
||||
cache-size:
|
||||
default: 500
|
||||
type: int
|
||||
description: Number of keystone tokens to hold in local cache.
|
||||
revocation-check-interval:
|
||||
default: 600
|
||||
type: int
|
||||
description: Interval between revocation checks to keystone.
|
||||
|
117
hooks/ceph.py
117
hooks/ceph.py
@ -12,8 +12,11 @@ import subprocess
|
||||
import time
|
||||
import utils
|
||||
import os
|
||||
import apt_pkg as apt
|
||||
|
||||
QUORUM = ['leader', 'peon']
|
||||
LEADER = 'leader'
|
||||
PEON = 'peon'
|
||||
QUORUM = [LEADER, PEON]
|
||||
|
||||
|
||||
def is_quorum():
|
||||
@ -40,6 +43,30 @@ def is_quorum():
|
||||
return False
|
||||
|
||||
|
||||
def is_leader():
|
||||
asok = "/var/run/ceph/ceph-mon.{}.asok".format(utils.get_unit_hostname())
|
||||
cmd = [
|
||||
"ceph",
|
||||
"--admin-daemon",
|
||||
asok,
|
||||
"mon_status"
|
||||
]
|
||||
if os.path.exists(asok):
|
||||
try:
|
||||
result = json.loads(subprocess.check_output(cmd))
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
except ValueError:
|
||||
# Non JSON response from mon_status
|
||||
return False
|
||||
if result['state'] == LEADER:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def wait_for_quorum():
|
||||
while not is_quorum():
|
||||
time.sleep(3)
|
||||
@ -58,6 +85,12 @@ def add_bootstrap_hint(peer):
|
||||
# Ignore any errors for this call
|
||||
subprocess.call(cmd)
|
||||
|
||||
DISK_FORMATS = [
|
||||
'xfs',
|
||||
'ext4',
|
||||
'btrfs'
|
||||
]
|
||||
|
||||
|
||||
def is_osd_disk(dev):
|
||||
try:
|
||||
@ -72,9 +105,33 @@ def is_osd_disk(dev):
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def rescan_osd_devices():
|
||||
cmd = [
|
||||
'udevadm', 'trigger',
|
||||
'--subsystem-match=block', '--action=add'
|
||||
]
|
||||
|
||||
subprocess.call(cmd)
|
||||
|
||||
|
||||
def zap_disk(dev):
|
||||
cmd = ['sgdisk', '--zap-all', dev]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
_bootstrap_keyring = "/var/lib/ceph/bootstrap-osd/ceph.keyring"
|
||||
|
||||
|
||||
def is_bootstrapped():
|
||||
return os.path.exists(_bootstrap_keyring)
|
||||
|
||||
|
||||
def wait_for_bootstrap():
|
||||
while (not is_bootstrapped()):
|
||||
time.sleep(3)
|
||||
|
||||
|
||||
def import_osd_bootstrap_key(key):
|
||||
if not os.path.exists(_bootstrap_keyring):
|
||||
cmd = [
|
||||
@ -98,34 +155,7 @@ _osd_bootstrap_caps = {
|
||||
|
||||
|
||||
def get_osd_bootstrap_key():
|
||||
cmd = [
|
||||
'ceph',
|
||||
'--name', 'mon.',
|
||||
'--keyring',
|
||||
'/var/lib/ceph/mon/ceph-{}/keyring'.format(
|
||||
utils.get_unit_hostname()
|
||||
),
|
||||
'auth', 'get-or-create', 'client.bootstrap-osd',
|
||||
]
|
||||
# Add capabilities
|
||||
for subsystem, subcaps in _osd_bootstrap_caps.iteritems():
|
||||
cmd.extend([
|
||||
subsystem,
|
||||
'; '.join(subcaps),
|
||||
])
|
||||
output = subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
# get-or-create appears to have different output depending
|
||||
# on whether its 'get' or 'create'
|
||||
# 'create' just returns the key, 'get' is more verbose and
|
||||
# needs parsing
|
||||
key = None
|
||||
if len(output.splitlines()) == 1:
|
||||
key = output
|
||||
else:
|
||||
for element in output.splitlines():
|
||||
if 'key' in element:
|
||||
key = element.split(' = ')[1].strip() # IGNORE:E1103
|
||||
return key
|
||||
return get_named_key('bootstrap-osd', _osd_bootstrap_caps)
|
||||
|
||||
|
||||
_radosgw_keyring = "/etc/ceph/keyring.rados.gateway"
|
||||
@ -150,6 +180,17 @@ _radosgw_caps = {
|
||||
|
||||
|
||||
def get_radosgw_key():
|
||||
return get_named_key('radosgw.gateway', _radosgw_caps)
|
||||
|
||||
|
||||
_default_caps = {
|
||||
'mon': ['allow r'],
|
||||
'osd': ['allow rwx']
|
||||
}
|
||||
|
||||
|
||||
def get_named_key(name, caps=None):
|
||||
caps = caps or _default_caps
|
||||
cmd = [
|
||||
'ceph',
|
||||
'--name', 'mon.',
|
||||
@ -157,10 +198,10 @@ def get_radosgw_key():
|
||||
'/var/lib/ceph/mon/ceph-{}/keyring'.format(
|
||||
utils.get_unit_hostname()
|
||||
),
|
||||
'auth', 'get-or-create', 'client.radosgw.gateway',
|
||||
'auth', 'get-or-create', 'client.{}'.format(name),
|
||||
]
|
||||
# Add capabilities
|
||||
for subsystem, subcaps in _radosgw_caps.iteritems():
|
||||
for subsystem, subcaps in caps.iteritems():
|
||||
cmd.extend([
|
||||
subsystem,
|
||||
'; '.join(subcaps),
|
||||
@ -178,3 +219,17 @@ def get_radosgw_key():
|
||||
if 'key' in element:
|
||||
key = element.split(' = ')[1].strip() # IGNORE:E1103
|
||||
return key
|
||||
|
||||
|
||||
def get_ceph_version(package=None):
|
||||
apt.init()
|
||||
cache = apt.Cache()
|
||||
pkg = cache[package or 'ceph']
|
||||
if pkg.current_ver:
|
||||
return apt.upstream_version(pkg.current_ver.ver_str)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def version_compare(a, b):
|
||||
return apt.version_compare(a, b)
|
||||
|
@ -22,6 +22,9 @@ def install_www_scripts():
|
||||
shutil.copy(x, '/var/www/')
|
||||
|
||||
|
||||
NSS_DIR='/var/lib/ceph/nss'
|
||||
|
||||
|
||||
def install():
|
||||
utils.juju_log('INFO', 'Begin install hook.')
|
||||
utils.enable_pocket('multiverse')
|
||||
@ -30,6 +33,7 @@ def install():
|
||||
'libapache2-mod-fastcgi',
|
||||
'apache2',
|
||||
'ntp')
|
||||
os.makedirs(NSS_DIR)
|
||||
utils.juju_log('INFO', 'End install hook.')
|
||||
|
||||
|
||||
@ -41,8 +45,17 @@ def emit_cephconf():
|
||||
cephcontext = {
|
||||
'auth_supported': get_auth() or 'none',
|
||||
'mon_hosts': ' '.join(get_mon_hosts()),
|
||||
'hostname': utils.get_unit_hostname()
|
||||
'hostname': utils.get_unit_hostname(),
|
||||
'version': ceph.get_ceph_version('radosgw')
|
||||
}
|
||||
|
||||
# Check to ensure that correct version of ceph is
|
||||
# in use
|
||||
if ceph.get_ceph_version('radosgw') >= "0.55":
|
||||
# Add keystone configuration if found
|
||||
ks_conf = get_keystone_conf()
|
||||
if ks_conf:
|
||||
cephcontext.update(ks_conf)
|
||||
|
||||
with open('/etc/ceph/ceph.conf', 'w') as cephconf:
|
||||
cephconf.write(utils.render_template('ceph.conf', cephcontext))
|
||||
@ -108,16 +121,33 @@ def get_conf(name):
|
||||
for unit in utils.relation_list(relid):
|
||||
conf = utils.relation_get(name,
|
||||
unit, relid)
|
||||
if conf != "":
|
||||
if conf:
|
||||
return conf
|
||||
return None
|
||||
|
||||
def get_keystone_conf():
|
||||
for relid in utils.relation_ids('identity-service'):
|
||||
for unit in utils.relation_list(relid):
|
||||
ks_auth = {
|
||||
'auth_type': 'keystone',
|
||||
'auth_protocol': 'http',
|
||||
'auth_host': utils.relation_get('auth_host', unit, relid),
|
||||
'auth_port': utils.relation_get('auth_port', unit, relid),
|
||||
'admin_token': utils.relation_get('admin_token', unit, relid),
|
||||
'user_roles': utils.config_get('operator-roles'),
|
||||
'cache_size': utils.config_get('cache-size'),
|
||||
'revocation_check_interval': utils.config_get('revocation-check-interval')
|
||||
}
|
||||
if None not in ks_auth.itervalues():
|
||||
return ks_auth
|
||||
return None
|
||||
|
||||
|
||||
def mon_relation():
|
||||
utils.juju_log('INFO', 'Begin mon-relation hook.')
|
||||
emit_cephconf()
|
||||
key = utils.relation_get('radosgw_key')
|
||||
if key != "":
|
||||
if key:
|
||||
ceph.import_radosgw_key(key)
|
||||
restart() # TODO figure out a better way todo this
|
||||
utils.juju_log('INFO', 'End mon-relation hook.')
|
||||
@ -141,7 +171,7 @@ def start():
|
||||
|
||||
|
||||
def stop():
|
||||
subprocess.call(['service', 'radosgw', 'start'])
|
||||
subprocess.call(['service', 'radosgw', 'stop'])
|
||||
utils.expose(port=80)
|
||||
|
||||
|
||||
@ -150,6 +180,28 @@ def restart():
|
||||
utils.expose(port=80)
|
||||
|
||||
|
||||
def identity_joined(relid=None):
|
||||
if ceph.get_ceph_version('radosgw') < "0.55":
|
||||
utils.juju_log('ERROR',
|
||||
'Integration with keystone requires ceph >= 0.55')
|
||||
sys.exit(1)
|
||||
|
||||
hostname = utils.unit_get('private-address')
|
||||
admin_url = 'http://{}:80/swift'.format(hostname)
|
||||
internal_url = public_url = '{}/v1'.format(admin_url)
|
||||
utils.relation_set(service='swift',
|
||||
region=utils.config_get('region'),
|
||||
public_url=public_url, internal_url=internal_url,
|
||||
admin_url=admin_url,
|
||||
requested_roles=utils.config_get('operator-roles'),
|
||||
rid=relid)
|
||||
|
||||
|
||||
def identity_changed():
|
||||
emit_cephconf()
|
||||
restart()
|
||||
|
||||
|
||||
utils.do_hooks({
|
||||
'install': install,
|
||||
'config-changed': config_changed,
|
||||
@ -157,6 +209,8 @@ utils.do_hooks({
|
||||
'mon-relation-changed': mon_relation,
|
||||
'gateway-relation-joined': gateway_relation,
|
||||
'upgrade-charm': config_changed, # same function ATM
|
||||
'identity-service-relation-joined': identity_joined,
|
||||
'identity-service-relation-changed': identity_changed
|
||||
})
|
||||
|
||||
sys.exit(0)
|
||||
|
@ -18,10 +18,12 @@ def do_hooks(hooks):
|
||||
hook = os.path.basename(sys.argv[0])
|
||||
|
||||
try:
|
||||
hooks[hook]()
|
||||
hook_func = hooks[hook]
|
||||
except KeyError:
|
||||
juju_log('INFO',
|
||||
"This charm doesn't know how to handle '{}'.".format(hook))
|
||||
else:
|
||||
hook_func()
|
||||
|
||||
|
||||
def install(*pkgs):
|
||||
@ -42,6 +44,12 @@ except ImportError:
|
||||
install('python-jinja2')
|
||||
import jinja2
|
||||
|
||||
try:
|
||||
import dns.resolver
|
||||
except ImportError:
|
||||
install('python-dnspython')
|
||||
import dns.resolver
|
||||
|
||||
|
||||
def render_template(template_name, context, template_dir=TEMPLATES_DIR):
|
||||
templates = jinja2.Environment(
|
||||
@ -101,7 +109,6 @@ def enable_pocket(pocket):
|
||||
else:
|
||||
sources.write(line)
|
||||
|
||||
|
||||
# Protocols
|
||||
TCP = 'TCP'
|
||||
UDP = 'UDP'
|
||||
@ -150,15 +157,25 @@ def relation_get(attribute, unit=None, rid=None):
|
||||
cmd.append(attribute)
|
||||
if unit:
|
||||
cmd.append(unit)
|
||||
return subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
value = str(subprocess.check_output(cmd)).strip()
|
||||
if value == "":
|
||||
return None
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def relation_set(**kwargs):
|
||||
cmd = [
|
||||
'relation-set'
|
||||
]
|
||||
args = []
|
||||
for k, v in kwargs.items():
|
||||
cmd.append('{}={}'.format(k, v))
|
||||
if k == 'rid':
|
||||
cmd.append('-r')
|
||||
cmd.append(v)
|
||||
else:
|
||||
args.append('{}={}'.format(k, v))
|
||||
cmd += args
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
@ -167,7 +184,11 @@ def unit_get(attribute):
|
||||
'unit-get',
|
||||
attribute
|
||||
]
|
||||
return subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
value = str(subprocess.check_output(cmd)).strip()
|
||||
if value == "":
|
||||
return None
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def config_get(attribute):
|
||||
@ -175,7 +196,11 @@ def config_get(attribute):
|
||||
'config-get',
|
||||
attribute
|
||||
]
|
||||
return subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
value = str(subprocess.check_output(cmd)).strip()
|
||||
if value == "":
|
||||
return None
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def get_unit_hostname():
|
||||
@ -183,9 +208,13 @@ def get_unit_hostname():
|
||||
|
||||
|
||||
def get_host_ip(hostname=unit_get('private-address')):
|
||||
cmd = [
|
||||
'dig',
|
||||
'+short',
|
||||
hostname
|
||||
]
|
||||
return subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
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
|
||||
|
@ -10,6 +10,8 @@ description: |
|
||||
requires:
|
||||
mon:
|
||||
interface: ceph-radosgw
|
||||
identity-service:
|
||||
interface: keystone
|
||||
provides:
|
||||
gateway:
|
||||
interface: http
|
||||
|
@ -1,5 +1,11 @@
|
||||
[global]
|
||||
{% if version < "0.51" %}
|
||||
auth supported = {{ auth_supported }}
|
||||
{% else %}
|
||||
auth cluster required = {{ auth_supported }}
|
||||
auth service required = {{ auth_supported }}
|
||||
auth client required = {{ auth_supported }}
|
||||
{% endif %}
|
||||
mon host = {{ mon_hosts }}
|
||||
|
||||
[client.radosgw.gateway]
|
||||
@ -7,3 +13,14 @@
|
||||
keyring = /etc/ceph/keyring.rados.gateway
|
||||
rgw socket path = /tmp/radosgw.sock
|
||||
log file = /var/log/ceph/radosgw.log
|
||||
# Turn off 100-continue optimization as stock mod_fastcgi
|
||||
# does not support it
|
||||
rgw print continue = false
|
||||
{% if auth_type == 'keystone' %}
|
||||
rgw keystone url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/
|
||||
rgw keystone admin token = {{ admin_token }}
|
||||
rgw keystone accepted roles = {{ user_roles }}
|
||||
rgw keystone token cache size = {{ cache_size }}
|
||||
rgw keystone revocation interval = {{ revocation_check_interval }}
|
||||
#nss db path = /var/lib/ceph/nss
|
||||
{% endif %}
|
Loading…
x
Reference in New Issue
Block a user