=== modified file 'charm-helpers.yaml'
--- charm-helpers.yaml 2014-12-11 14:48:59 +0000
+++ charm-helpers.yaml 2014-12-17 23:24:13 +0000
@@ -3,7 +3,7 @@
include:
- core
- fetch
- - contrib.openstack
+ - contrib.openstack|inc=*
- contrib.hahelpers
- contrib.storage.linux
- contrib.network.ip
=== modified file 'config.yaml'
--- config.yaml 2014-10-01 15:22:55 +0000
+++ config.yaml 2014-12-17 23:24:13 +0000
@@ -84,4 +84,24 @@
192.168.0.0/24)
.
This network will be used for public endpoints.
-
+ # HA configuration settings
+ vip:
+ type: string
+ default:
+ description: |
+ Virtual IP(s) to use to front API services in HA configuration.
+ .
+ If multiple networks are being used, a VIP should be provided for each
+ network, separated by spaces.
+ ha-bindiface:
+ type: string
+ default: eth0
+ description: |
+ Default network interface on which HA cluster will bind to communication
+ with the other members of the HA Cluster.
+ ha-mcastport:
+ type: int
+ default: 5403
+ description: |
+ Default multicast port number that will be used to communicate between
+ HA Cluster nodes.
=== modified file 'hooks/ceilometer_contexts.py'
--- hooks/ceilometer_contexts.py 2014-03-06 01:51:53 +0000
+++ hooks/ceilometer_contexts.py 2014-12-17 23:24:13 +0000
@@ -1,5 +1,3 @@
-import os
-import uuid
from charmhelpers.core.hookenv import (
relation_ids,
relation_get,
@@ -7,12 +5,19 @@
config
)
+from charmhelpers.contrib.openstack.utils import os_release
+
from charmhelpers.contrib.openstack.context import (
OSContextGenerator,
context_complete,
ApacheSSLContext as SSLContext,
)
+from charmhelpers.contrib.hahelpers.cluster import (
+ determine_apache_port,
+ determine_api_port
+)
+
CEILOMETER_DB = 'ceilometer'
@@ -25,37 +30,53 @@
interfaces = ['mongodb']
def __call__(self):
+ mongo_servers = []
+ replset = None
+ use_replset = os_release('ceilometer-api') >= 'icehouse'
+
for relid in relation_ids('shared-db'):
- for unit in related_units(relid):
+ rel_units = related_units(relid)
+ use_replset = use_replset and (len(rel_units) > 1)
+
+ for unit in rel_units:
+ host = relation_get('hostname', unit, relid)
+ port = relation_get('port', unit, relid)
+
conf = {
- "db_host": relation_get('hostname', unit, relid),
- "db_port": relation_get('port', unit, relid),
+ "db_host": host,
+ "db_port": port,
"db_name": CEILOMETER_DB
}
- if context_complete(conf):
+
+ if not context_complete(conf):
+ continue
+
+ if not use_replset:
return conf
+
+ if replset is None:
+ replset = relation_get('replset', unit, relid)
+
+ mongo_servers.append('{}:{}'.format(host, port))
+
+ if mongo_servers:
+ return {
+ 'db_mongo_servers': ','.join(mongo_servers),
+ 'db_name': CEILOMETER_DB,
+ 'db_replset': replset
+ }
+
return {}
-SHARED_SECRET = "/etc/ceilometer/secret.txt"
-
-
-def get_shared_secret():
- secret = None
- if not os.path.exists(SHARED_SECRET):
- secret = str(uuid.uuid4())
- with open(SHARED_SECRET, 'w') as secret_file:
- secret_file.write(secret)
- else:
- with open(SHARED_SECRET, 'r') as secret_file:
- secret = secret_file.read().strip()
- return secret
-
CEILOMETER_PORT = 8777
class CeilometerContext(OSContextGenerator):
def __call__(self):
+ # Lazy-import to avoid a circular dependency in the imports
+ from ceilometer_utils import get_shared_secret
+
ctxt = {
'port': CEILOMETER_PORT,
'metering_secret': get_shared_secret()
@@ -75,6 +96,24 @@
return {}
+class HAProxyContext(OSContextGenerator):
+ interfaces = ['ceilometer-haproxy']
+
+ def __call__(self):
+ '''Extends the main charmhelpers HAProxyContext with a port mapping
+ specific to this charm.
+ '''
+ haproxy_port = CEILOMETER_PORT
+ api_port = determine_api_port(CEILOMETER_PORT)
+ apache_port = determine_apache_port(CEILOMETER_PORT)
+
+ ctxt = {
+ 'service_ports': {'ceilometer_api': [haproxy_port, apache_port]},
+ 'port': api_port
+ }
+ return ctxt
+
+
class ApacheSSLContext(SSLContext):
service_namespace = "ceilometer"
=== modified file 'hooks/ceilometer_hooks.py'
--- hooks/ceilometer_hooks.py 2014-12-16 20:29:18 +0000
+++ hooks/ceilometer_hooks.py 2014-12-17 23:24:13 +0000
@@ -1,6 +1,8 @@
#!/usr/bin/python
import base64
+import os
+import shutil
import sys
from charmhelpers.fetch import (
apt_install, filter_installed_packages,
@@ -11,6 +13,7 @@
relation_get,
relation_set,
relation_ids,
+ related_units,
config,
Hooks, UnregisteredHookError,
log,
@@ -32,13 +35,23 @@
register_configs,
restart_map,
get_ceilometer_context,
- do_openstack_upgrade
+ get_shared_secret,
+ do_openstack_upgrade,
+ set_shared_secret
)
from ceilometer_contexts import CEILOMETER_PORT
from charmhelpers.contrib.openstack.ip import (
canonical_url,
PUBLIC, INTERNAL, ADMIN
)
+from charmhelpers.contrib.network.ip import (
+ get_iface_for_address,
+ get_netmask_for_address
+)
+from charmhelpers.contrib.hahelpers.cluster import (
+ get_hacluster_config,
+ is_elected_leader
+)
hooks = Hooks()
CONFIGS = register_configs()
@@ -70,6 +83,7 @@
@hooks.hook("amqp-relation-changed",
"shared-db-relation-changed",
+ "shared-db-relation-departed",
"identity-service-relation-changed")
@restart_on_change(restart_map())
def any_changed():
@@ -103,6 +117,113 @@
any_changed()
+def install_ceilometer_ocf():
+ dest_file = "/usr/lib/ocf/resource.d/openstack/ceilometer-agent-central"
+ src_file = 'ocf/openstack/ceilometer-agent-central'
+
+ if not os.path.isdir(os.path.dirname(dest_file)):
+ os.makedirs(os.path.dirname(dest_file))
+ if not os.path.exists(dest_file):
+ shutil.copy(src_file, dest_file)
+
+
+@hooks.hook('cluster-relation-joined')
+@restart_on_change(restart_map(), stopstart=True)
+def cluster_joined():
+ install_ceilometer_ocf()
+
+ # If this node is the elected leader then share our secret with other nodes
+ if is_elected_leader('grp_ceilometer_vips'):
+ relation_set(shared_secret=get_shared_secret())
+
+ CONFIGS.write_all()
+
+
+@hooks.hook('cluster-relation-changed',
+ 'cluster-relation-departed')
+@restart_on_change(restart_map(), stopstart=True)
+def cluster_changed():
+ shared_secret = relation_get('shared_secret')
+ if shared_secret is None or shared_secret.strip() == '':
+ log('waiting for shared secret to be provided by leader')
+ elif not shared_secret == get_shared_secret():
+ set_shared_secret(shared_secret)
+
+ CONFIGS.write_all()
+
+
+@hooks.hook('ha-relation-joined')
+def ha_joined():
+ cluster_config = get_hacluster_config()
+
+ resources = {
+ 'res_ceilometer_haproxy': 'lsb:haproxy',
+ 'res_ceilometer_agent_central': ('ocf:openstack:'
+ 'ceilometer-agent-central')
+ }
+
+ resource_params = {
+ 'res_ceilometer_haproxy': 'op monitor interval="5s"',
+ 'res_ceilometer_agent_central': 'op monitor interval="30s"'
+ }
+
+ amqp_ssl_port = None
+ for rel_id in relation_ids('amqp'):
+ for unit in related_units(rel_id):
+ amqp_ssl_port = relation_get('ssl_port', unit, rel_id)
+
+ if amqp_ssl_port:
+ params = ('params amqp_server_port="%s" op monitor interval="30s"' %
+ (amqp_ssl_port))
+ resource_params['res_ceilometer_agent_central'] = params
+
+ vip_group = []
+ for vip in cluster_config['vip'].split():
+ res_ceilometer_vip = 'ocf:heartbeat:IPaddr2'
+ vip_params = 'ip'
+
+ iface = get_iface_for_address(vip)
+ if iface is not None:
+ vip_key = 'res_ceilometer_{}_vip'.format(iface)
+ resources[vip_key] = res_ceilometer_vip
+ resource_params[vip_key] = (
+ 'params {ip}="{vip}" cidr_netmask="{netmask}"'
+ ' nic="{iface}"'.format(ip=vip_params,
+ vip=vip,
+ iface=iface,
+ netmask=get_netmask_for_address(vip))
+ )
+ vip_group.append(vip_key)
+
+ if len(vip_group) >= 1:
+ relation_set(groups={'grp_ceilometer_vips': ' '.join(vip_group)})
+
+ init_services = {
+ 'res_ceilometer_haproxy': 'haproxy'
+ }
+ clones = {
+ 'cl_ceilometer_haproxy': 'res_ceilometer_haproxy'
+ }
+ relation_set(init_services=init_services,
+ corosync_bindiface=cluster_config['ha-bindiface'],
+ corosync_mcastport=cluster_config['ha-mcastport'],
+ resources=resources,
+ resource_params=resource_params,
+ clones=clones)
+
+
+@hooks.hook('ha-relation-changed')
+def ha_changed():
+ clustered = relation_get('clustered')
+ if not clustered or clustered in [None, 'None', '']:
+ log('ha_changed: hacluster subordinate not fully clustered.')
+ else:
+ log('Cluster configured, notifying other services and updating '
+ 'keystone endpoint configuration')
+ for rid in relation_ids('identity-service'):
+ keystone_joined(relid=rid)
+
+
@hooks.hook("identity-service-relation-joined")
def keystone_joined(relid=None):
public_url = "{}:{}".format(
=== modified file 'hooks/ceilometer_utils.py'
--- hooks/ceilometer_utils.py 2014-10-23 16:03:49 +0000
+++ hooks/ceilometer_utils.py 2014-12-17 23:24:13 +0000
@@ -1,4 +1,5 @@
import os
+import uuid
from collections import OrderedDict
@@ -11,6 +12,7 @@
LoggingConfigContext,
MongoDBContext,
CeilometerContext,
+ HAProxyContext
)
from charmhelpers.contrib.openstack.utils import (
get_os_codename_package,
@@ -21,22 +23,28 @@
from charmhelpers.fetch import apt_update, apt_install, apt_upgrade
from copy import deepcopy
+HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
CEILOMETER_CONF_DIR = "/etc/ceilometer"
CEILOMETER_CONF = "%s/ceilometer.conf" % CEILOMETER_CONF_DIR
HTTPS_APACHE_CONF = "/etc/apache2/sites-available/openstack_https_frontend"
HTTPS_APACHE_24_CONF = "/etc/apache2/sites-available/" \
"openstack_https_frontend.conf"
+CLUSTER_RES = 'grp_ceilometer_vips'
CEILOMETER_SERVICES = [
'ceilometer-agent-central',
'ceilometer-collector',
- 'ceilometer-api'
+ 'ceilometer-api',
+ 'ceilometer-alarm-evaluator',
+ 'ceilometer-alarm-notifier',
+ 'ceilometer-agent-notification',
]
CEILOMETER_DB = "ceilometer"
CEILOMETER_SERVICE = "ceilometer"
CEILOMETER_PACKAGES = [
+ 'haproxy',
'apache2',
'ceilometer-agent-central',
'ceilometer-collector',
@@ -60,9 +68,15 @@
LoggingConfigContext(),
MongoDBContext(),
CeilometerContext(),
- context.SyslogContext()],
+ context.SyslogContext(),
+ HAProxyContext()],
'services': CEILOMETER_SERVICES
}),
+ (HAPROXY_CONF, {
+ 'hook_contexts': [context.HAProxyContext(),
+ HAProxyContext()],
+ 'services': ['haproxy'],
+ }),
(HTTPS_APACHE_CONF, {
'hook_contexts': [ApacheSSLContext()],
'services': ['apache2'],
@@ -75,6 +89,8 @@
TEMPLATES = 'templates'
+SHARED_SECRET = "/etc/ceilometer/secret.txt"
+
def register_configs():
"""
@@ -162,3 +178,28 @@
>= 'icehouse'):
packages = packages + ICEHOUSE_PACKAGES
return packages
+
+
+def get_shared_secret():
+ """
+ Returns the current shared secret for the ceilometer node. If the shared
+ secret does not exist, this method will generate one.
+ """
+ secret = None
+ if not os.path.exists(SHARED_SECRET):
+ secret = str(uuid.uuid4())
+ set_shared_secret(secret)
+ else:
+ with open(SHARED_SECRET, 'r') as secret_file:
+ secret = secret_file.read().strip()
+ return secret
+
+
+def set_shared_secret(secret):
+ """
+ Sets the shared secret which is used to sign ceilometer messages.
+
+ :param secret: the secret to set
+ """
+ with open(SHARED_SECRET, 'w') as secret_file:
+ secret_file.write(secret)
=== added file 'hooks/charmhelpers/contrib/openstack/templates/ceph.conf'
--- hooks/charmhelpers/contrib/openstack/templates/ceph.conf 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/ceph.conf 2014-12-17 23:24:13 +0000
@@ -0,0 +1,15 @@
+###############################################################################
+# [ WARNING ]
+# cinder configuration file maintained by Juju
+# local changes may be overwritten.
+###############################################################################
+[global]
+{% if auth -%}
+ auth_supported = {{ auth }}
+ keyring = /etc/ceph/$cluster.$name.keyring
+ mon host = {{ mon_hosts }}
+{% endif -%}
+ log to syslog = {{ use_syslog }}
+ err to syslog = {{ use_syslog }}
+ clog to syslog = {{ use_syslog }}
+
=== added file 'hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg'
--- hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/haproxy.cfg 2014-12-17 23:24:13 +0000
@@ -0,0 +1,54 @@
+global
+ log {{ local_host }} local0
+ log {{ local_host }} local1 notice
+ maxconn 20000
+ user haproxy
+ group haproxy
+ spread-checks 0
+
+defaults
+ log global
+ mode tcp
+ option tcplog
+ option dontlognull
+ retries 3
+ timeout queue 1000
+ timeout connect 1000
+{% if haproxy_client_timeout -%}
+ timeout client {{ haproxy_client_timeout }}
+{% else -%}
+ timeout client 30000
+{% endif -%}
+
+{% if haproxy_server_timeout -%}
+ timeout server {{ haproxy_server_timeout }}
+{% else -%}
+ timeout server 30000
+{% endif -%}
+
+listen stats {{ stat_port }}
+ mode http
+ stats enable
+ stats hide-version
+ stats realm Haproxy\ Statistics
+ stats uri /
+ stats auth admin:password
+
+{% if frontends -%}
+{% for service, ports in service_ports.iteritems() -%}
+frontend tcp-in_{{ service }}
+ bind *:{{ ports[0] }}
+ bind :::{{ ports[0] }}
+ {% for frontend in frontends -%}
+ acl net_{{ frontend }} dst {{ frontends[frontend]['network'] }}
+ use_backend {{ service }}_{{ frontend }} if net_{{ frontend }}
+ {% endfor %}
+{% for frontend in frontends -%}
+backend {{ service }}_{{ frontend }}
+ balance leastconn
+ {% for unit, address in frontends[frontend]['backends'].iteritems() -%}
+ server {{ unit }} {{ address }}:{{ ports[1] }} check
+ {% endfor %}
+{% endfor -%}
+{% endfor -%}
+{% endif -%}
=== added file 'hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend'
--- hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend 2014-12-17 23:24:13 +0000
@@ -0,0 +1,24 @@
+{% if endpoints -%}
+{% for ext_port in ext_ports -%}
+Listen {{ ext_port }}
+{% endfor -%}
+{% for address, endpoint, ext, int in endpoints -%}
+
+ ServerName {{ endpoint }}
+ SSLEngine on
+ SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
+ SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }}
+ ProxyPass / http://localhost:{{ int }}/
+ ProxyPassReverse / http://localhost:{{ int }}/
+ ProxyPreserveHost on
+
+{% endfor -%}
+
+ Order deny,allow
+ Allow from all
+
+
+ Order allow,deny
+ Allow from all
+
+{% endif -%}
=== added file 'hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf'
--- hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/openstack_https_frontend.conf 2014-12-17 23:24:13 +0000
@@ -0,0 +1,24 @@
+{% if endpoints -%}
+{% for ext_port in ext_ports -%}
+Listen {{ ext_port }}
+{% endfor -%}
+{% for address, endpoint, ext, int in endpoints -%}
+
+ ServerName {{ endpoint }}
+ SSLEngine on
+ SSLCertificateFile /etc/apache2/ssl/{{ namespace }}/cert_{{ endpoint }}
+ SSLCertificateKeyFile /etc/apache2/ssl/{{ namespace }}/key_{{ endpoint }}
+ ProxyPass / http://localhost:{{ int }}/
+ ProxyPassReverse / http://localhost:{{ int }}/
+ ProxyPreserveHost on
+
+{% endfor -%}
+
+ Order deny,allow
+ Allow from all
+
+
+ Order allow,deny
+ Allow from all
+
+{% endif -%}
=== added symlink 'hooks/cluster-relation-changed'
=== target is u'ceilometer_hooks.py'
=== added symlink 'hooks/cluster-relation-departed'
=== target is u'ceilometer_hooks.py'
=== added symlink 'hooks/cluster-relation-joined'
=== target is u'ceilometer_hooks.py'
=== added symlink 'hooks/ha-relation-changed'
=== target is u'ceilometer_hooks.py'
=== added symlink 'hooks/ha-relation-joined'
=== target is u'ceilometer_hooks.py'
=== added symlink 'hooks/shared-db-relation-departed'
=== target is u'ceilometer_hooks.py'
=== modified file 'metadata.yaml'
--- metadata.yaml 2014-12-16 20:15:53 +0000
+++ metadata.yaml 2014-12-17 23:24:13 +0000
@@ -23,3 +23,9 @@
interface: keystone
identity-notifications:
interface: keystone-notifications
+ ha:
+ interface: hacluster
+ scope: container
+peers:
+ cluster:
+ interface: ceilometer-ha
=== added directory 'ocf'
=== added directory 'ocf/openstack'
=== added file 'ocf/openstack/ceilometer-agent-central'
--- ocf/openstack/ceilometer-agent-central 1970-01-01 00:00:00 +0000
+++ ocf/openstack/ceilometer-agent-central 2014-12-17 23:24:13 +0000
@@ -0,0 +1,345 @@
+#!/bin/sh
+#
+#
+# OpenStack Ceilometer Central Agent Service (ceilometer-agent-central)
+#
+# Description: Manages an OpenStack Ceilometer Central Agent Service (ceilometer-agent-central) process as an HA resource
+#
+# Authors: Emilien Macchi
+# Mainly inspired by the Nova Scheduler resource agent written by Sebastien Han
+#
+# Support: openstack@lists.launchpad.net
+# License: Apache Software License (ASL) 2.0
+#
+#
+# See usage() function below for more details ...
+#
+# OCF instance parameters:
+# OCF_RESKEY_binary
+# OCF_RESKEY_config
+# OCF_RESKEY_user
+# OCF_RESKEY_pid
+# OCF_RESKEY_monitor_binary
+# OCF_RESKEY_amqp_server_port
+# OCF_RESKEY_additional_parameters
+#######################################################################
+# Initialization:
+
+: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/lib/heartbeat}
+. ${OCF_FUNCTIONS_DIR}/ocf-shellfuncs
+
+#######################################################################
+
+# Fill in some defaults if no values are specified
+
+OCF_RESKEY_binary_default="ceilometer-agent-central"
+OCF_RESKEY_config_default="/etc/ceilometer/ceilometer.conf"
+OCF_RESKEY_user_default="ceilometer"
+OCF_RESKEY_pid_default="$HA_RSCTMP/$OCF_RESOURCE_INSTANCE.pid"
+OCF_RESKEY_amqp_server_port_default="5672"
+
+: ${OCF_RESKEY_binary=${OCF_RESKEY_binary_default}}
+: ${OCF_RESKEY_config=${OCF_RESKEY_config_default}}
+: ${OCF_RESKEY_user=${OCF_RESKEY_user_default}}
+: ${OCF_RESKEY_pid=${OCF_RESKEY_pid_default}}
+: ${OCF_RESKEY_amqp_server_port=${OCF_RESKEY_amqp_server_port_default}}
+
+#######################################################################
+
+usage() {
+ cat <
+
+
+1.0
+
+
+Resource agent for the OpenStack Ceilometer Central Agent Service (ceilometer-agent-central)
+May manage a ceilometer-agent-central instance or a clone set that
+creates a distributed ceilometer-agent-central cluster.
+
+Manages the OpenStack Ceilometer Central Agent Service (ceilometer-agent-central)
+
+
+
+
+Location of the OpenStack Ceilometer Central Agent server binary (ceilometer-agent-central)
+
+OpenStack Ceilometer Central Agent server binary (ceilometer-agent-central)
+
+
+
+
+
+Location of the OpenStack Ceilometer Central Agent Service (ceilometer-agent-central) configuration file
+
+OpenStack Ceilometer Central Agent (ceilometer-agent-central registry) config file
+
+
+
+
+
+User running OpenStack Ceilometer Central Agent Service (ceilometer-agent-central)
+
+OpenStack Ceilometer Central Agent Service (ceilometer-agent-central) user
+
+
+
+
+
+The pid file to use for this OpenStack Ceilometer Central Agent Service (ceilometer-agent-central) instance
+
+OpenStack Ceilometer Central Agent Service (ceilometer-agent-central) pid file
+
+
+
+
+
+The listening port number of the AMQP server. Use for monitoring purposes
+
+AMQP listening port
+
+
+
+
+
+
+Additional parameters to pass on to the OpenStack Ceilometer Central Agent Service (ceilometer-agent-central)
+
+Additional parameters for ceilometer-agent-central
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+END
+}
+
+#######################################################################
+# Functions invoked by resource manager actions
+
+ceilometer_agent_central_check_port() {
+# This function has been taken from the squid RA and improved a bit
+# The length of the integer must be 4
+# Examples of valid port: "1080", "0080"
+# Examples of invalid port: "1080bad", "0", "0000", ""
+
+ local int
+ local cnt
+
+ int="$1"
+ cnt=${#int}
+ echo $int |egrep -qx '[0-9]+(:[0-9]+)?(,[0-9]+(:[0-9]+)?)*'
+
+ if [ $? -ne 0 ] || [ $cnt -ne 4 ]; then
+ ocf_log err "Invalid port number: $1"
+ exit $OCF_ERR_CONFIGURED
+ fi
+}
+
+ceilometer_agent_central_validate() {
+ local rc
+
+ check_binary $OCF_RESKEY_binary
+ check_binary netstat
+ ceilometer_agent_central_check_port $OCF_RESKEY_amqp_server_port
+
+ # A config file on shared storage that is not available
+ # during probes is OK.
+ if [ ! -f $OCF_RESKEY_config ]; then
+ if ! ocf_is_probe; then
+ ocf_log err "Config $OCF_RESKEY_config doesn't exist"
+ return $OCF_ERR_INSTALLED
+ fi
+ ocf_log_warn "Config $OCF_RESKEY_config not available during a probe"
+ fi
+
+ getent passwd $OCF_RESKEY_user >/dev/null 2>&1
+ rc=$?
+ if [ $rc -ne 0 ]; then
+ ocf_log err "User $OCF_RESKEY_user doesn't exist"
+ return $OCF_ERR_INSTALLED
+ fi
+
+ true
+}
+
+ceilometer_agent_central_status() {
+ local pid
+ local rc
+
+ if [ ! -f $OCF_RESKEY_pid ]; then
+ ocf_log info "OpenStack Ceilometer Central Agent (ceilometer-agent-central) is not running"
+ return $OCF_NOT_RUNNING
+ else
+ pid=`cat $OCF_RESKEY_pid`
+ fi
+
+ ocf_run -warn kill -s 0 $pid
+ rc=$?
+ if [ $rc -eq 0 ]; then
+ return $OCF_SUCCESS
+ else
+ ocf_log info "Old PID file found, but OpenStack Ceilometer Central Agent (ceilometer-agent-central) is not running"
+ return $OCF_NOT_RUNNING
+ fi
+}
+
+ceilometer_agent_central_monitor() {
+ local rc
+ local pid
+ local scheduler_amqp_check
+
+ ceilometer_agent_central_status
+ rc=$?
+
+ # If status returned anything but success, return that immediately
+ if [ $rc -ne $OCF_SUCCESS ]; then
+ return $rc
+ fi
+
+ # Check the connections according to the PID.
+ # We are sure to hit the scheduler process and not other Cinder process with the same connection behavior (for example cinder-api)
+ pid=`cat $OCF_RESKEY_pid`
+ scheduler_amqp_check=`netstat -punt | grep -s "$OCF_RESKEY_amqp_server_port" | grep -s "$pid" | grep -qs "ESTABLISHED"`
+ rc=$?
+ if [ $rc -ne 0 ]; then
+ ocf_log err "Central Agent is not connected to the AMQP server : $rc"
+ return $OCF_NOT_RUNNING
+ fi
+
+ ocf_log debug "OpenStack Ceilometer Central Agent (ceilometer-agent-central) monitor succeeded"
+ return $OCF_SUCCESS
+}
+
+ceilometer_agent_central_start() {
+ local rc
+
+ ceilometer_agent_central_status
+ rc=$?
+ if [ $rc -eq $OCF_SUCCESS ]; then
+ ocf_log info "OpenStack Ceilometer Central Agent (ceilometer-agent-central) already running"
+ return $OCF_SUCCESS
+ fi
+
+ # run the actual ceilometer-agent-central daemon. Don't use ocf_run as we're sending the tool's output
+ # straight to /dev/null anyway and using ocf_run would break stdout-redirection here.
+ su ${OCF_RESKEY_user} -s /bin/sh -c "${OCF_RESKEY_binary} --config-file=$OCF_RESKEY_config \
+ $OCF_RESKEY_additional_parameters"' >> /dev/null 2>&1 & echo $!' > $OCF_RESKEY_pid
+
+ # Spin waiting for the server to come up.
+ while true; do
+ ceilometer_agent_central_monitor
+ rc=$?
+ [ $rc -eq $OCF_SUCCESS ] && break
+ if [ $rc -ne $OCF_NOT_RUNNING ]; then
+ ocf_log err "OpenStack Ceilometer Central Agent (ceilometer-agent-central) start failed"
+ exit $OCF_ERR_GENERIC
+ fi
+ sleep 1
+ done
+
+ ocf_log info "OpenStack Ceilometer Central Agent (ceilometer-agent-central) started"
+ return $OCF_SUCCESS
+}
+
+ceilometer_agent_central_stop() {
+ local rc
+ local pid
+
+ ceilometer_agent_central_status
+ rc=$?
+ if [ $rc -eq $OCF_NOT_RUNNING ]; then
+ ocf_log info "OpenStack Ceilometer Central Agent (ceilometer-agent-central) already stopped"
+ return $OCF_SUCCESS
+ fi
+
+ # Try SIGTERM
+ pid=`cat $OCF_RESKEY_pid`
+ ocf_run kill -s TERM $pid
+ rc=$?
+ if [ $rc -ne 0 ]; then
+ ocf_log err "OpenStack Ceilometer Central Agent (ceilometer-agent-central) couldn't be stopped"
+ exit $OCF_ERR_GENERIC
+ fi
+
+ # stop waiting
+ shutdown_timeout=15
+ if [ -n "$OCF_RESKEY_CRM_meta_timeout" ]; then
+ shutdown_timeout=$((($OCF_RESKEY_CRM_meta_timeout/1000)-5))
+ fi
+ count=0
+ while [ $count -lt $shutdown_timeout ]; do
+ ceilometer_agent_central_status
+ rc=$?
+ if [ $rc -eq $OCF_NOT_RUNNING ]; then
+ break
+ fi
+ count=`expr $count + 1`
+ sleep 1
+ ocf_log debug "OpenStack Ceilometer Central Agent (ceilometer-agent-central) still hasn't stopped yet. Waiting ..."
+ done
+
+ ceilometer_agent_central_status
+ rc=$?
+ if [ $rc -ne $OCF_NOT_RUNNING ]; then
+ # SIGTERM didn't help either, try SIGKILL
+ ocf_log info "OpenStack Ceilometer Central Agent (ceilometer-agent-central) failed to stop after ${shutdown_timeout}s \
+ using SIGTERM. Trying SIGKILL ..."
+ ocf_run kill -s KILL $pid
+ fi
+
+ ocf_log info "OpenStack Ceilometer Central Agent (ceilometer-agent-central) stopped"
+
+ rm -f $OCF_RESKEY_pid
+
+ return $OCF_SUCCESS
+}
+
+#######################################################################
+
+case "$1" in
+ meta-data) meta_data
+ exit $OCF_SUCCESS;;
+ usage|help) usage
+ exit $OCF_SUCCESS;;
+esac
+
+# Anything except meta-data and help must pass validation
+ceilometer_agent_central_validate || exit $?
+
+# What kind of method was invoked?
+case "$1" in
+ start) ceilometer_agent_central_start;;
+ stop) ceilometer_agent_central_stop;;
+ status) ceilometer_agent_central_status;;
+ monitor) ceilometer_agent_central_monitor;;
+ validate-all) ;;
+ *) usage
+ exit $OCF_ERR_UNIMPLEMENTED;;
+esac
=== modified file 'setup.cfg'
--- setup.cfg 2013-10-20 19:32:35 +0000
+++ setup.cfg 2014-12-17 23:24:13 +0000
@@ -1,5 +1,5 @@
[nosetests]
-verbosity=1
+verbosity=2
with-coverage=1
cover-erase=1
cover-package=hooks
=== added directory 'templates/icehouse'
=== added file 'templates/icehouse/ceilometer.conf'
--- templates/icehouse/ceilometer.conf 1970-01-01 00:00:00 +0000
+++ templates/icehouse/ceilometer.conf 2014-12-17 23:24:13 +0000
@@ -0,0 +1,36 @@
+# icehouse
+###############################################################################
+# [ WARNING ]
+# ceilometer configuration file maintained by Juju
+# local changes may be overwritten.
+###############################################################################
+[DEFAULT]
+debug = {{ debug }}
+verbose = {{ verbose }}
+use_syslog = {{ use_syslog }}
+{% include "parts/rabbitmq" %}
+[api]
+port = {{ port }}
+[service_credentials]
+os_auth_url = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/v2.0
+os_tenant_name = {{ admin_tenant_name }}
+os_username = {{ admin_user }}
+os_password = {{ admin_password }}
+[database]
+{% if db_replset: -%}
+connection = mongodb://{{ db_mongo_servers }}/{{ db_name }}?readPreference=primaryPreferred&replicaSet={{ db_replset }}
+mongodb_replica_set = {{ db_replset }}
+{% else -%}
+connection = mongodb://{{ db_host }}:{{ db_port }}/{{ db_name }}
+{% endif -%}
+[publisher_rpc]
+metering_secret = {{ metering_secret }}
+[keystone_authtoken]
+auth_uri = {{ service_protocol }}://{{ service_host }}:{{ service_port }}/
+auth_host = {{ auth_host }}
+auth_port = {{ auth_port }}
+auth_protocol = {{ auth_protocol }}
+admin_tenant_name = {{ admin_tenant_name }}
+admin_user = {{ admin_user }}
+admin_password = {{ admin_password }}
+
=== modified file 'unit_tests/test_ceilometer_contexts.py'
--- unit_tests/test_ceilometer_contexts.py 2014-02-18 11:52:16 +0000
+++ unit_tests/test_ceilometer_contexts.py 2014-12-17 23:24:13 +0000
@@ -1,14 +1,16 @@
from mock import patch
import ceilometer_contexts as contexts
+import ceilometer_utils as utils
from test_utils import CharmTestCase, mock_open
TO_PATCH = [
+ 'config',
'relation_get',
'relation_ids',
'related_units',
- 'config'
+ 'os_release',
]
@@ -38,6 +40,7 @@
def test_mongodb_context_not_related(self):
self.relation_ids.return_value = []
+ self.os_release.return_value = 'icehouse'
self.assertEquals(contexts.MongoDBContext()(), {})
def test_mongodb_context_related(self):
@@ -52,7 +55,60 @@
{'db_host': 'mongodb', 'db_port': 8090,
'db_name': 'ceilometer'})
- @patch.object(contexts, 'get_shared_secret')
+ def test_mongodb_context_related_replset_single_mongo(self):
+ self.relation_ids.return_value = ['shared-db:0']
+ self.related_units.return_value = ['mongodb/0']
+ data = {
+ 'hostname': 'mongodb-0',
+ 'port': 8090,
+ 'replset': 'replset-1'
+ }
+ self.test_relation.set(data)
+ self.os_release.return_value = 'icehouse'
+ self.assertEquals(contexts.MongoDBContext()(),
+ {'db_host': 'mongodb-0', 'db_port': 8090,
+ 'db_name': 'ceilometer'})
+
+ @patch.object(contexts, 'context_complete')
+ def test_mongodb_context_related_replset_missing_values(self, mock_ctxcmp):
+ mock_ctxcmp.return_value = False
+ self.relation_ids.return_value = ['shared-db:0']
+ self.related_units.return_value = ['mongodb/0']
+ data = {
+ 'hostname': None,
+ 'port': 8090,
+ 'replset': 'replset-1'
+ }
+ self.test_relation.set(data)
+ self.os_release.return_value = 'icehouse'
+ self.assertEquals(contexts.MongoDBContext()(), {})
+
+ def test_mongodb_context_related_replset_multiple_mongo(self):
+ self.relation_ids.return_value = ['shared-db:0']
+ related_units = {
+ 'mongodb/0': {'hostname': 'mongodb-0',
+ 'port': 8090,
+ 'replset': 'replset-1'},
+ 'mongodb/1': {'hostname': 'mongodb-1',
+ 'port': 8090,
+ 'replset': 'replset-1'}
+ }
+ self.related_units.return_value = [k for k in related_units.keys()]
+
+ def relation_get(attr, unit, relid):
+ values = related_units.get(unit)
+ if attr is None:
+ return values
+ else:
+ return values.get(attr, None)
+ self.relation_get.side_effect = relation_get
+
+ self.os_release.return_value = 'icehouse'
+ self.assertEquals(contexts.MongoDBContext()(),
+ {'db_mongo_servers': 'mongodb-0:8090,mongodb-1:8090',
+ 'db_name': 'ceilometer', 'db_replset': 'replset-1'})
+
+ @patch.object(utils, 'get_shared_secret')
def test_ceilometer_context(self, secret):
secret.return_value = 'mysecret'
self.assertEquals(contexts.CeilometerContext()(),
@@ -75,8 +131,8 @@
@patch('os.path.exists')
def test_get_shared_secret_existing(self, exists):
exists.return_value = True
- with mock_open(contexts.SHARED_SECRET, u'mysecret'):
- self.assertEquals(contexts.get_shared_secret(),
+ with mock_open(utils.SHARED_SECRET, u'mysecret'):
+ self.assertEquals(utils.get_shared_secret(),
'mysecret')
@patch('uuid.uuid4')
@@ -85,5 +141,21 @@
exists.return_value = False
uuid4.return_value = 'newsecret'
with patch('__builtin__.open'):
- self.assertEquals(contexts.get_shared_secret(),
+ self.assertEquals(utils.get_shared_secret(),
'newsecret')
+
+ @patch.object(contexts, 'determine_apache_port')
+ @patch.object(contexts, 'determine_api_port')
+ def test_ha_proxy_context(self, determine_api_port, determine_apache_port):
+ determine_api_port.return_value = contexts.CEILOMETER_PORT - 10
+ determine_apache_port.return_value = contexts.CEILOMETER_PORT - 20
+
+ haproxy_port = contexts.CEILOMETER_PORT
+ api_port = haproxy_port - 10
+ apache_port = api_port - 10
+
+ expected = {
+ 'service_ports': {'ceilometer_api': [haproxy_port, apache_port]},
+ 'port': api_port
+ }
+ self.assertEquals(contexts.HAProxyContext()(), expected)
=== modified file 'unit_tests/test_ceilometer_hooks.py'
--- unit_tests/test_ceilometer_hooks.py 2014-12-16 20:29:18 +0000
+++ unit_tests/test_ceilometer_hooks.py 2014-12-17 23:24:13 +0000
@@ -13,6 +13,7 @@
from test_utils import CharmTestCase
TO_PATCH = [
+ 'relation_get',
'relation_set',
'relation_get',
'configure_installation_source',
@@ -160,3 +161,154 @@
call1 = call('ceilometer-alarm-evaluator')
call2 = call('ceilometer-alarm-notifier')
self.service_restart.assert_has_calls([call1, call2], any_order=False)
+
+ @patch('charmhelpers.core.hookenv.config')
+ @patch.object(hooks, 'install_ceilometer_ocf')
+ @patch.object(hooks, 'is_elected_leader')
+ def test_cluster_joined_not_leader(self, mock_leader, mock_install_ocf,
+ mock_config):
+ mock_leader.return_value = False
+
+ hooks.hooks.execute(['hooks/cluster-relation-joined'])
+ self.assertFalse(self.relation_set.called)
+ self.assertTrue(self.CONFIGS.write_all.called)
+
+ @patch('charmhelpers.core.hookenv.config')
+ @patch.object(hooks, 'get_shared_secret')
+ @patch.object(hooks, 'install_ceilometer_ocf')
+ @patch.object(hooks, 'is_elected_leader')
+ def test_cluster_joined_is_leader(self, mock_leader, mock_install_ocf,
+ shared_secret, mock_config):
+ mock_leader.return_value = True
+ shared_secret.return_value = 'secret'
+
+ hooks.hooks.execute(['hooks/cluster-relation-joined'])
+ self.assertTrue(self.relation_set.called)
+ self.relation_set.assert_called_with(shared_secret='secret')
+ self.assertTrue(self.CONFIGS.write_all.called)
+
+ @patch('charmhelpers.core.hookenv.config')
+ @patch.object(hooks, 'set_shared_secret')
+ def test_cluster_changed(self, shared_secret, mock_config):
+ self.relation_get.return_value = None
+ hooks.hooks.execute(['hooks/cluster-relation-changed'])
+ self.assertFalse(shared_secret.called)
+
+ @patch('charmhelpers.core.hookenv.config')
+ @patch.object(hooks, 'get_shared_secret')
+ @patch.object(hooks, 'set_shared_secret')
+ def test_cluster_changed_new_secret(self, mock_set_secret, mock_get_secret,
+ mock_config):
+ self.relation_get.return_value = "leader_secret"
+ mock_get_secret.return_value = "my_secret"
+ hooks.hooks.execute(['hooks/cluster-relation-changed'])
+ mock_set_secret.assert_called_with("leader_secret")
+
+ @patch('charmhelpers.core.hookenv.config')
+ @patch.object(hooks, 'get_shared_secret')
+ @patch.object(hooks, 'set_shared_secret')
+ def test_cluster_changed_old_secret(self, mock_set_secret, mock_get_secret,
+ mock_config):
+ self.relation_get.return_value = "leader_secret"
+ mock_get_secret.return_value = "leader_secret"
+ hooks.hooks.execute(['hooks/cluster-relation-changed'])
+ self.assertEquals(mock_set_secret.call_count, 0)
+
+ @patch('charmhelpers.core.hookenv.config')
+ @patch.object(hooks, 'get_hacluster_config')
+ @patch.object(hooks, 'get_iface_for_address')
+ @patch.object(hooks, 'get_netmask_for_address')
+ def test_ha_joined(self, mock_netmask, mock_iface, mock_cluster_config,
+ mock_config):
+ mock_cluster_config.return_value = {'vip': '10.0.5.100',
+ 'ha-bindiface': 'bnd0',
+ 'ha-mcastport': 5802}
+ mock_iface.return_value = 'eth0'
+ mock_netmask.return_value = '255.255.255.10'
+ hooks.hooks.execute(['hooks/ha-relation-joined'])
+ self.assertEquals(self.relation_set.call_count, 2)
+
+ exp_resources = {
+ 'res_ceilometer_haproxy': 'lsb:haproxy',
+ 'res_ceilometer_agent_central': ('ocf:openstack:'
+ 'ceilometer-agent-central'),
+ 'res_ceilometer_eth0_vip': 'ocf:heartbeat:IPaddr2'
+ }
+ exp_resource_params = {
+ 'res_ceilometer_haproxy': 'op monitor interval="5s"',
+ 'res_ceilometer_agent_central': 'op monitor interval="30s"',
+ 'res_ceilometer_eth0_vip': ('params ip="10.0.5.100" '
+ 'cidr_netmask="255.255.255.10" '
+ 'nic="eth0"')
+ }
+ exp_clones = {'cl_ceilometer_haproxy': 'res_ceilometer_haproxy'}
+ call1 = call(groups={'grp_ceilometer_vips': 'res_ceilometer_eth0_vip'})
+ call2 = call(init_services={'res_ceilometer_haproxy': 'haproxy'},
+ corosync_bindiface='bnd0',
+ corosync_mcastport=5802,
+ resources=exp_resources,
+ resource_params=exp_resource_params,
+ clones=exp_clones)
+ self.relation_set.assert_has_calls([call1, call2], any_order=False)
+
+ @patch('charmhelpers.core.hookenv.config')
+ @patch.object(hooks, 'get_netmask_for_address')
+ @patch.object(hooks, 'get_hacluster_config')
+ @patch.object(hooks, 'get_iface_for_address')
+ @patch.object(hooks, 'relation_ids')
+ @patch.object(hooks, 'related_units')
+ @patch.object(hooks, 'relation_get')
+ def test_ha_joined_ssl(self, mock_rel_get, mock_rel_units, mock_rel_ids,
+ mock_iface, mock_cluster_config, mock_netmask,
+ mock_config):
+ mock_rel_ids.return_value = 'amqp:0'
+ mock_rel_units.return_value = 'rabbitmq-server/0'
+ mock_rel_get.return_value = '5671'
+
+ mock_iface.return_value = 'eth0'
+ mock_netmask.return_value = '255.255.255.10'
+ mock_cluster_config.return_value = {'vip': '10.0.5.100',
+ 'ha-bindiface': 'bnd0',
+ 'ha-mcastport': 5802}
+
+ hooks.hooks.execute(['hooks/ha-relation-joined'])
+ self.assertEquals(self.relation_set.call_count, 2)
+
+ exp_resources = {
+ 'res_ceilometer_haproxy': 'lsb:haproxy',
+ 'res_ceilometer_agent_central': ('ocf:openstack:'
+ 'ceilometer-agent-central'),
+ 'res_ceilometer_eth0_vip': 'ocf:heartbeat:IPaddr2'
+ }
+ exp_resource_params = {
+ 'res_ceilometer_haproxy': 'op monitor interval="5s"',
+ 'res_ceilometer_agent_central': ('params amqp_server_port="5671" '
+ 'op monitor interval="30s"'),
+ 'res_ceilometer_eth0_vip': ('params ip="10.0.5.100" '
+ 'cidr_netmask="255.255.255.10" '
+ 'nic="eth0"')
+ }
+ exp_clones = {'cl_ceilometer_haproxy': 'res_ceilometer_haproxy'}
+ call1 = call(groups={'grp_ceilometer_vips': 'res_ceilometer_eth0_vip'})
+ call2 = call(init_services={'res_ceilometer_haproxy': 'haproxy'},
+ corosync_bindiface='bnd0',
+ corosync_mcastport=5802,
+ resources=exp_resources,
+ resource_params=exp_resource_params,
+ clones=exp_clones)
+ self.relation_set.assert_has_calls([call1, call2], any_order=False)
+
+ @patch('charmhelpers.core.hookenv.config')
+ @patch.object(hooks, 'keystone_joined')
+ def test_ha_changed_not_clustered(self, mock_keystone_joined, mock_config):
+ self.relation_get.return_value = None
+ hooks.hooks.execute(['hooks/ha-relation-changed'])
+ self.assertEquals(mock_keystone_joined.call_count, 0)
+
+ @patch('charmhelpers.core.hookenv.config')
+ @patch.object(hooks, 'keystone_joined')
+ def test_ha_changed_clustered(self, mock_keystone_joined, mock_config):
+ self.relation_get.return_value = 'yes'
+ self.relation_ids.return_value = ['identity-service/0']
+ hooks.hooks.execute(['hooks/ha-relation-changed'])
+ self.assertEquals(mock_keystone_joined.call_count, 1)
=== modified file 'unit_tests/test_ceilometer_utils.py'
--- unit_tests/test_ceilometer_utils.py 2014-04-10 14:18:08 +0000
+++ unit_tests/test_ceilometer_utils.py 2014-12-17 23:24:13 +0000
@@ -44,7 +44,11 @@
{'/etc/ceilometer/ceilometer.conf': [
'ceilometer-agent-central',
'ceilometer-collector',
- 'ceilometer-api'],
+ 'ceilometer-api',
+ 'ceilometer-alarm-evaluator',
+ 'ceilometer-alarm-notifier',
+ 'ceilometer-agent-notification'],
+ '/etc/haproxy/haproxy.cfg': ['haproxy'],
"/etc/apache2/sites-available/openstack_https_frontend": [
'apache2'],
"/etc/apache2/sites-available/openstack_https_frontend.conf": [