Python rewrite.

This commit is contained in:
Adam Gandelman 2013-01-23 13:57:08 -08:00
commit e53c196e70
16 changed files with 393 additions and 360 deletions

View File

@ -1 +1 @@
rabbitmq-relations
rabbitmq-server-relations.py

View File

@ -1 +0,0 @@
rabbitmq-relations

View File

@ -1 +1 @@
rabbitmq-relations
rabbitmq-server-relations.py

View File

@ -1 +1 @@
rabbitmq-relations
rabbitmq-server-relations.py

View File

@ -1,70 +0,0 @@
#!/bin/bash
set -eu
juju-log "rabbitmq-server: Firing config hook"
export HOME=/root # (HOME is not set on first run)
RABBIT_PLUGINS=/usr/lib/rabbitmq/lib/rabbitmq_server-*/sbin/rabbitmq-plugins
if [ "`config-get management_plugin`" == "True" ]; then
$RABBIT_PLUGINS enable rabbitmq_management
open-port 55672/tcp
else
$RABBIT_PLUGINS disable rabbitmq_management
close-port 55672/tcp
fi
ssl_enabled=`config-get ssl_enabled`
cd /etc/rabbitmq
new_config=`mktemp /etc/rabbitmq/.rabbitmq.config.XXXXXX`
chgrp rabbitmq "$new_config"
chmod g+r "$new_config"
exec 3> "$new_config"
cat >&3 <<EOF
[
{rabbit, [
EOF
ssl_key_file=/etc/rabbitmq/rabbit-server-privkey.pem
ssl_cert_file=/etc/rabbitmq/rabbit-server-cert.pem
if [ "$ssl_enabled" == "True" ]; then
umask 027
config-get ssl_key > "$ssl_key_file"
config-get ssl_cert > "$ssl_cert_file"
chgrp rabbitmq "$ssl_key_file" "$ssl_cert_file"
if [ ! -s "$ssl_key_file" ]; then
juju-log "ssl_key not set - can't configure SSL"
exit 0
fi
if [ ! -s "$ssl_cert_file" ]; then
juju-log "ssl_cert not set - can't configure SSL"
exit 0
fi
cat >&3 <<EOF
{ssl_listeners, [`config-get ssl_port`]},
{ssl_options, [
{certfile,"$ssl_cert_file"},
{keyfile,"$ssl_key_file"}
]},
open-port `config-get ssl_port`/tcp
EOF
fi
cat >&3 <<EOF
{tcp_listeners, [5672]}
]}
].
EOF
exec 3>&-
if [ -f rabbitmq.config ]; then
mv rabbitmq.config{,.bak}
fi
mv "$new_config" rabbitmq.config
/etc/init.d/rabbitmq-server restart

View File

@ -1 +1 @@
rabbitmq-relations
rabbitmq-server-relations.py

View File

@ -1 +1 @@
rabbitmq-relations
rabbitmq-server-relations.py

View File

@ -1 +1 @@
rabbitmq-relations
rabbitmq-server-relations.py

97
hooks/rabbit_utils.py Normal file
View File

@ -0,0 +1,97 @@
import re
import subprocess
import utils
import apt_pkg as apt
PACKAGES = ['pwgen', 'rabbitmq-server']
RABBITMQ_CTL = '/usr/sbin/rabbitmqctl'
COOKIE_PATH = '/var/lib/rabbitmq/.erlang.cookie'
def vhost_exists(vhost):
cmd = [RABBITMQ_CTL, 'list_vhosts']
out = subprocess.check_output(cmd)
for line in out.split('\n')[1:]:
if line == vhost:
utils.juju_log('INFO', 'vhost (%s) already exists.' % vhost)
return True
return False
def create_vhost(vhost):
if vhost_exists(vhost):
return
cmd = [RABBITMQ_CTL, 'add_vhost', vhost]
subprocess.check_call(cmd)
utils.juju_log('INFO', 'Created new vhost (%s).' % vhost)
def user_exists(user):
cmd = [RABBITMQ_CTL, 'list_users']
out = subprocess.check_output(cmd)
for line in out.split('\n')[1:]:
_user = line.split('\t')[0]
if _user == user:
admin = line.split('\t')[1]
return True, (admin == '[administrator]')
return False, False
def create_user(user, password, admin=False):
exists, is_admin = user_exists(user)
if not exists:
cmd = [RABBITMQ_CTL, 'add_user', user, password]
subprocess.check_call(cmd)
utils.juju_log('INFO', 'Created new user (%s).' % user)
if admin == is_admin:
return
if admin:
cmd = [RABBITMQ_CTL, 'set_user_tags', user, 'administrator']
utils.juju_log('INFO', 'Granting user (%s) admin access.')
else:
cmd = [RABBITMQ_CTL, 'set_user_tags', user]
utils.juju_log('INFO', 'Revoking user (%s) admin access.')
def grant_permissions(user, vhost):
cmd = [RABBITMQ_CTL, 'set_permissions', '-p',
vhost, user, '.*', '.*', '.*']
subprocess.check_call(cmd)
def service(action):
cmd = ['service', 'rabbitmq-server', action]
subprocess.check_call(cmd)
def rabbit_version():
apt.init()
cache = apt.Cache()
pkg = cache['rabbitmq-server']
if pkg.current_ver:
return apt.upstream_version(pkg.current_ver.ver_str)
else:
return None
def cluster_with(host):
utils.juju_log('INFO', 'Clustering with remote rabbit host (%s).' % host)
vers = rabbit_version()
if vers >= '3.0.1-1':
cluster_cmd = 'join_cluster'
else:
cluster_cmd = 'cluster'
out = subprocess.check_output([RABBITMQ_CTL, 'cluster_status'])
for line in out.split('\n'):
if re.search(host, line):
utils.juju_log('INFO', 'Host already clustered with %s.' % host)
return
cmd = [RABBITMQ_CTL, 'stop_app']
subprocess.check_call(cmd)
cmd = [RABBITMQ_CTL, cluster_cmd, 'rabbit@%s' % host]
subprocess.check_call(cmd)
cmd = [RABBITMQ_CTL, 'start_app']
subprocess.check_call(cmd)

View File

@ -1,82 +0,0 @@
#!/bin/bash
#
# rabbitmq-common - common formula shell functions and config variables
#
# Copyright (C) 2011 Canonical Ltd.
# Author: Adam Gandelman <adam.gandelman@canonical.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set -e
RABBIT_CTL='rabbitmqctl'
ERLANG_COOKIE="/var/lib/rabbitmq/.erlang.cookie"
function user_exists {
$RABBIT_CTL list_users | grep -wq "^$1"
}
function user_is_admin {
$RABBIT_CTL list_users | grep -w "^$1" | grep -q "administrator"
}
function vhost_exists {
$RABBIT_CTL list_vhosts | grep "^$1\$" >/dev/null
}
function create_vhost {
juju-log "Creating vhost: $1"
$RABBIT_CTL add_vhost "$1"
}
function user_create {
local user="$1"
local passwd="$2"
local vhost="$3"
local admin="$4"
juju-log "rabbitmq: Creating user $1 (vhost: $3"
$RABBIT_CTL add_user "$user" "$passwd" || return 1
# grant the user all permissions on the default vhost /
# TODO: investigate sane permissions
juju-log "rabbitmq: Granting permission to $1 on vhost /"
$RABBIT_CTL set_permissions -p "$vhost" "$user" ".*" ".*" ".*"
if [[ "$admin" == "admin" ]] ; then
user_is_admin "$user" && return 0
juju-log "rabbitmq: Granting user $user admin access"
$RABBIT_CTL set_user_tags "$user" administrator || return 1
fi
}
function rabbit_version {
echo "$(dpkg -l | grep rabbitmq-server | awk '{ print $3 }')"
}
##########################################################################
# Description: Query HA interface to determine is cluster is configured
# Returns: 0 if configured, 1 if not configured
##########################################################################
is_clustered() {
for r_id in `relation-ids ha`; do
for unit in `relation-list -r $r_id`; do
clustered=`relation-get -r $r_id clustered $unit`
if [ -n "$clustered" ]; then
return 0
fi
done
done
return 1
}

View File

@ -1,198 +0,0 @@
#!/bin/bash
#
# rabbitmq-relations - relations to be used by formula, referenced
# via symlink
#
# Copyright (C) 2011 Canonical Ltd.
# Author: Adam Gandelman <adam.gandelman@canonical.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set -e
CHARM_DIR=$(dirname $0)
ARG0=${0##*/}
if [[ -e $CHARM_DIR/rabbitmq-common ]] ; then
. $CHARM_DIR/rabbitmq-common
else
juju-log "rabbitmq-server: ERROR Could not load $CHARM_DIR/rabbitmq-common"
exit 1
fi
juju-log "rabbitmq-server: Firing hook $ARG0."
function install_hook() {
[[ ! `which pwgen` ]] && apt-get -y install pwgen
DEBIAN_FRONTEND=noninteractive apt-get -qqy \
install --no-install-recommends rabbitmq-server
rc=$?
service rabbitmq-server stop
open-port 5672/tcp
}
function amqp_changed() {
# Connecting clients should request a username and vhost.
# In reponse, we generate a password for new users,
# grant the user access on the default vhost "/",
# and tell it where to reach us.
# Skip managing rabbit queue config unless we are the
# lowest numbered unit in the service cluster.
local local_unit_id=$(echo $JUJU_UNIT_NAME | cut -d/ -f2)
local remote_unit_id=""
for relid in $(relation-ids cluster) ; do
for unit in $(relation-list -r "$relid") ; do
remote_unit_id="$(echo $unit | cut -d/ -f2)"
if [[ "$local_unit_id" -gt "$remote_unit_id" ]]; then
juju-log "amqp_changed(): Deferring amqp_changed to leader."
exit 0
fi
done
done
local rabbit_user=`relation-get username`
local vhost=`relation-get vhost`
if [[ -z "$rabbit_user" ]] || [[ -z "$vhost" ]] ; then
juju-log "rabbitmq-server: rabbit_user||vhost not yet received from peer."
exit 0
fi
local passwd_file="/var/lib/juju/$rabbit_user.passwd"
local password=""
if [[ -e $passwd_file ]] ; then
password=$(cat $passwd_file)
else
password=$(pwgen 10 1)
echo $password >$passwd_file
chmod 0400 $passwd_file
fi
if ! vhost_exists "$vhost" ; then
juju-log "rabbitmq-server: Creating vhost $vhost"
create_vhost "$vhost"
fi
if ! user_exists "$rabbit_user" ; then
juju-log "rabbitmq-server: Creating user $rabbit_user"
user_create "$rabbit_user" "$password" "$vhost" admin || exit 1
else
juju-log "rabbitmq-server: user $rabbit_user already exists."
fi
local remote_host="$(relation-get private-address)"
juju-log "rabbitmq-server: Returning credentials for $rabbit_user@$remote_host"
relation-set password="$password"
if is_clustered ; then
relation-set clustered="true" vip="$(config-get vip)"
fi
}
function cluster_joined {
local remote_unit_id=$(echo $JUJU_REMOTE_UNIT | cut -d/ -f2)
local local_unit_id=$(echo $JUJU_UNIT_NAME | cut -d/ -f2)
[[ $local_unit_id -gt $remote_unit_id ]] && echo "Relation greater" && exit 0
if [[ ! -e $ERLANG_COOKIE ]] ; then
juju-log "rabbitmq-server: ERROR Could not find cookie at $ERLANG_COOKIE"
exit 1
fi
relation-set cookie="$(cat $ERLANG_COOKIE)" host="$(hostname)"
}
function cluster_changed {
local remote_unit_id=$(echo $JUJU_REMOTE_UNIT | cut -d/ -f2)
local local_unit_id=$(echo $JUJU_UNIT_NAME | cut -d/ -f2)
[[ $local_unit_id -lt $remote_unit_id ]] && echo "Relation lesser" && exit 0
local remote_host=$(relation-get host)
local cookie_value=$(relation-get cookie)
[[ -z "$remote_host" ]] || [[ -z "$cookie_value" ]] && \
juju-log "rabbimtq-server: remote_host||cookie_value not yet set." &&
exit 0
# Sync the erlang cookie to that of remote host.
service rabbitmq-server stop
echo -n "$cookie_value" > $ERLANG_COOKIE
service rabbitmq-server start
# Configure clustering.
# rabbitmq apparently does not like FQDNs.
local short_host=$(echo $remote_host | sed -e 's/\./ /g' | awk '{ print $1 }')
local cur_vers=$(rabbit_version)
local cluster_cmd="cluster"
if dpkg --compare-versions "$cur_vers" ge "3.0.1-1" ; then
cluster_cmd="join_cluster"
fi
if ! rabbitmqctl cluster_status | grep -q "rabbit@$short_host" ; then
juju-log "Clustering with new rabbitmq peer @ $short_host."
rabbitmqctl stop_app
rabbitmqctl $cluster_cmd rabbit@$short_host
rabbitmqctl start_app
else
juju-log "Already clustered with rabbitmq peer @ $short_host."
fi
}
function ha_joined() {
local corosync_bindiface=`config-get ha-bindiface`
local corosync_mcastport=`config-get ha-mcastport`
local vip=`config-get vip`
local vip_iface=`config-get vip_iface`
local vip_cidr=`config-get vip_cidr`
if [ -n "$vip" ] && [ -n "$vip_iface" ] && \
[ -n "$vip_cidr" ] && [ -n "$corosync_bindiface" ] && \
[ -n "$corosync_mcastport" ]; then
# TODO: This feels horrible but the data required by the hacluster
# charm is quite complex and is python ast parsed.
resources="{
'res_rabbitmq_vip':'ocf:heartbeat:IPaddr2'
}"
resource_params="{
'res_rabbitmq_vip': 'params ip=\"$vip\" cidr_netmask=\"$vip_cidr\" nic=\"$vip_iface\"'
}"
relation-set corosync_bindiface=$corosync_bindiface \
corosync_mcastport=$corosync_mcastport \
resources="$resources" resource_params="$resource_params"
else
juju-log "Insufficient configuration data to configure hacluster"
exit 1
fi
}
function ha_changed() {
# we may now be clustered, advertise our vip to clients if so.
if is_clustered ; then
local vip="$(config-get vip)"
juju-log "$CHARM - ha_changed(): We are now HA clustered. "\
"Advertising our VIP ($vip) to all AMQP clients."
for rid in $(relation-ids amqp) ; do
relation-set -r $rid clustered="true" vip="$vip"
done
fi
}
case $ARG0 in
"install") install_hook ;;
"start") service rabbitmq-server status || service rabbitmq-server start ;;
"stop") service rabbitmq-server status && service rabbitmq-server stop ;;
"amqp-relation-joined") exit 0 ;;
"amqp-relation-changed") amqp_changed ;;
"cluster-relation-joined") cluster_joined ;;
"cluster-relation-changed") cluster_changed ;;
"ha-relation-joined") ha_joined ;;
"ha-relation-changed") ha_changed ;;
esac
rc=$?
juju-log "rabbitmq-server: Hook $ARG0 complete. Exiting $rc"
exit $rc

View File

@ -0,0 +1,142 @@
#!/usr/bin/python
import rabbit_utils as rabbit
import utils
import os
import sys
import subprocess
def install():
utils.install(*rabbit.PACKAGES)
utils.expose(5672)
def amqp_changed():
l_unit_no=os.getenv('JUJU_UNIT_NAME').split('/')[1]
r_unit_no=None
for rid in utils.relation_ids('cluster'):
for unit in utils.relation_list(rid):
r_unit_no = unit.split('/')[1]
if l_unit_no > r_unit_no:
msg = 'amqp_changed(): Deferring amqp_changed to leader.'
utils.juju_log('INFO', msg)
return
rabbit_user=utils.relation_get('username')
vhost=utils.relation_get('vhost')
if None in [rabbit_user, vhost]:
utils.juju_log('INFO', 'amqp_changed(): Relation not ready.')
return
password_file = '/var/lib/juju/%s.passwd' % rabbit_user
if os.path.exists(password_file):
password = open(password_file).read().strip()
else:
cmd = ['pwgen', '64', '1']
password = subprocess.check_output(cmd).strip()
with open(password_file, 'wb') as out:
out.write(password)
rabbit.create_vhost(vhost)
rabbit.create_user(rabbit_user, password)
rabbit.grant_permissions(rabbit_user, vhost)
relation_settings = {
'password': password
}
if utils.is_clustered():
relation_settings['clustered'] = 'true'
relation_settings['vip'] = utils.config_get('vip')
utils.relation_set(**relation_settings)
def cluster_joined():
l_unit_no = os.getenv('JUJU_UNIT_NAME').split('/')[1]
r_unit_no = os.getenv('JUJU_REMOTE_UNIT').split('/')[1]
if l_unit_no > r_unit_no:
utils.juju_log('INFO', 'cluster_joined: Relation greater.')
return
rabbit.COOKIE_PATH = '/var/lib/rabbitmq/.erlang.cookie'
if not os.path.isfile(rabbit.COOKIE_PATH):
utils.juju_log('ERROR', 'erlang cookie missing from %s' %\
rabbit.COOKIE_PATH)
cookie = open(rabbit.COOKIE_PATH, 'r').read().strip()
local_hostname = subprocess.check_output(['hostname']).strip()
utils.relation_set(cookie=cookie, host=local_hostname)
def cluster_changed():
l_unit_no = os.getenv('JUJU_UNIT_NAME').split('/')[1]
r_unit_no = os.getenv('JUJU_REMOTE_UNIT').split('/')[1]
if l_unit_no < r_unit_no:
utils.juju_log('INFO', 'cluster_joined: Relation lesser.')
return
remote_host = utils.relation_get('host')
cookie = utils.relation_get('cookie')
if None in [remote_host, cookie]:
utils.juju_log('INFO',
'cluster_joined: remote_host|cookie not yet set.')
return
if open(rabbit.COOKIE_PATH, 'r').read().strip() == cookie:
utils.juju_log('INFO', 'Cookie already synchronized with peer.')
return
utils.juju_log('INFO', 'Synchronizing erlang cookie from peer.')
rabbit.service('stop')
with open(rabbit.COOKIE_PATH, 'wb') as out:
out.write(cookie)
rabbit.service('start')
rabbit.cluster_with(remote_host)
def ha_joined():
config = {}
corosync_bindiface = utils.config_get('ha-bindiface')
corosync_mcastport = utils.config_get('ha-mcastport')
vip = utils.config_get('vip')
vip_iface = utils.config_get('vip_iface')
vip_cidr = utils.config_get('vip_cidr')
if None in [corosync_bindiface, corosync_mcastport, vip, vip_iface,
vip_cidr]:
utils.juju_log('ERROR', 'Insufficient configuration data to '\
'configure hacluster.')
sys.exit(1)
relation_settings = {}
relation_settings['corosync_bindiface'] = corosync_bindiface
relation_settings['corosync_mcastport'] = corosync_mcastport
relation_settings['resources'] = {
'res_rabbitmq_vip': 'ocf:heartbeat:IPaddr2'
}
relation_settings['resource_params'] = {
'res_rabbitmq_vip': ('params ip="%s" cider_netmask="%s" nic="%s"' %\
(vip, vip_cidr, vip_iface))
}
utils.relation_set(**relation_settings)
def ha_changed():
if not utils.is_clustered:
return
vip = utils.config_get('vip')
utils.juju_log('INFO', 'ha_changed(): We are now HA clustered. '\
'Advertising our VIP (%s) to all AMQP clients.' %\
vip)
relation_settings = {'vip': vip, 'clustered': 'true'}
for rid in utils.relation_ids('amqp'):
relation_settings['rid'] = rid
utils.relation_set(**relation_settings)
hooks = {
'install': install,
'amqp-relation-changed': amqp_changed,
'cluster-relation-joined': cluster_joined,
'cluster-relation-changed': cluster_changed,
'ha-relation-joined': ha_joined,
'ha-relation-changed': ha_changed,
}
utils.do_hooks(hooks)

View File

@ -1 +0,0 @@
rabbitmq-relations

View File

@ -1 +0,0 @@
rabbitmq-relations

147
hooks/utils.py Normal file
View File

@ -0,0 +1,147 @@
#
# Copyright 2012 Canonical Ltd.
#
# Authors:
# James Page <james.page@ubuntu.com>
# Paul Collins <paul.collins@canonical.com>
#
import os
import subprocess
import socket
import sys
def do_hooks(hooks):
hook = os.path.basename(sys.argv[0])
try:
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):
cmd = [
'apt-get',
'-y',
'install'
]
for pkg in pkgs:
cmd.append(pkg)
subprocess.check_call(cmd)
TEMPLATES_DIR = 'templates'
def expose(port, protocol='TCP'):
cmd = [
'open-port',
'{}/{}'.format(port, protocol)
]
subprocess.check_call(cmd)
def juju_log(severity, message):
cmd = [
'juju-log',
'--log-level', severity,
message
]
subprocess.check_call(cmd)
def relation_ids(relation):
cmd = [
'relation-ids',
relation
]
return subprocess.check_output(cmd).split() # IGNORE:E1103
def relation_list(rid):
cmd = [
'relation-list',
'-r', rid,
]
return subprocess.check_output(cmd).split() # IGNORE:E1103
def relation_get(attribute, unit=None, rid=None):
cmd = [
'relation-get',
]
if rid:
cmd.append('-r')
cmd.append(rid)
cmd.append(attribute)
if unit:
cmd.append(unit)
value = subprocess.check_output(cmd).strip() # IGNORE:E1103
if value == "":
return None
else:
return value
def relation_set(**kwargs):
cmd = [
'relation-set'
]
args = []
for k, v in kwargs.items():
if k == 'rid':
cmd.append('-r')
cmd.append(v)
else:
args.append('{}={}'.format(k, v))
cmd += args
subprocess.check_call(cmd)
def unit_get(attribute):
cmd = [
'unit-get',
attribute
]
value = subprocess.check_output(cmd).strip() # IGNORE:E1103
if value == "":
return None
else:
return value
def config_get(attribute):
cmd = [
'config-get',
attribute
]
value = subprocess.check_output(cmd).strip() # IGNORE:E1103
if value == "":
return None
else:
return value
def is_clustered():
for r_id in (relation_ids('ha') or []):
for unit in (relation_list(r_id) or []):
relation_data = \
relation_get_dict(relation_id=r_id,
remote_unit=unit)
if 'clustered' in relation_data:
return True
return False
def is_leader():
status = execute('crm resource show res_ks_vip', echo=True)[0].strip()
hostname = execute('hostname', echo=True)[0].strip()
if hostname in status:
return True
else:
return False

View File

@ -1 +1 @@
59
60