summaryrefslogtreecommitdiff
diff options
authorJuan L. Negron <juan.negron@canonical.com>2012-08-18 19:31:30 -0700
committerJuan L. Negron <juan.negron@canonical.com>2012-08-18 19:31:30 -0700
commit0f4a89b37d04e610b28329d06dd3bcca965189db (patch)
treec1a954e36b3e828ee9941176df6ad11722d98669
parent211ad3cee33df8daa8821507b594bdbe6655d866 (diff)
Refactor of the existing mongodb charm. Added most of the options in mongodb.conf as config.yaml options. Added sharding.
l---------hooks/config-changed1
l---------hooks/configsvr-relation-changed1
l---------hooks/configsvr-relation-joined1
l---------hooks/database-relation-joined1
-rwxr-xr-xhooks/hooks.py1167
l---------hooks/install1
l---------hooks/mongos-relation-broken1
l---------hooks/mongos-relation-changed1
l---------hooks/mongos-relation-joined1
l---------hooks/replica-set-relation-changed1
l---------hooks/replica-set-relation-joined1
l---------hooks/start1
l---------hooks/stop1
13 files changed, 1179 insertions, 0 deletions
diff --git a/hooks/config-changed b/hooks/config-changed
new file mode 120000
index 0000000..f94593a
--- /dev/null
+++ b/hooks/config-changed
@@ -0,0 +1 @@
+./hooks.py \ No newline at end of file
diff --git a/hooks/configsvr-relation-changed b/hooks/configsvr-relation-changed
new file mode 120000
index 0000000..9416ca6
--- /dev/null
+++ b/hooks/configsvr-relation-changed
@@ -0,0 +1 @@
+hooks.py \ No newline at end of file
diff --git a/hooks/configsvr-relation-joined b/hooks/configsvr-relation-joined
new file mode 120000
index 0000000..9416ca6
--- /dev/null
+++ b/hooks/configsvr-relation-joined
@@ -0,0 +1 @@
+hooks.py \ No newline at end of file
diff --git a/hooks/database-relation-joined b/hooks/database-relation-joined
new file mode 120000
index 0000000..f94593a
--- /dev/null
+++ b/hooks/database-relation-joined
@@ -0,0 +1 @@
+./hooks.py \ No newline at end of file
diff --git a/hooks/hooks.py b/hooks/hooks.py
new file mode 100755
index 0000000..2326f02
--- /dev/null
+++ b/hooks/hooks.py
@@ -0,0 +1,1167 @@
+#!/usr/bin/env python
+'''
+Created on Aug 1, 2012
+
+@author: negronjl
+'''
+
+import os
+import sys
+import json
+import subprocess
+import re
+import signal
+import socket
+import time
+
+
+###############################################################################
+# Supporting functions
+###############################################################################
+
+
+#------------------------------------------------------------------------------
+# juju_log: calls juju-log and records the message defined by the message
+# variable
+#------------------------------------------------------------------------------
+def juju_log(message=None):
+ return (subprocess.call(['juju-log', str(message)]) == 0)
+
+
+#------------------------------------------------------------------------------
+# service: Analogous to calling service on the command line to start/stop
+# and get status of a service/daemon.
+# Parameters:
+# service_name: The name of the service to act on.
+# service_action: The action (start, stop, status, etc.)
+# Returns: True if the command was successfully executed or False on
+# error.
+#------------------------------------------------------------------------------
+def service(service_name=None, service_action=None):
+ juju_log("service: %s, action: %s" % (service_name, service_action))
+ if service_name is not None and service_action is not None:
+ retVal = subprocess.call(
+ ["service", service_name, service_action]) == 0
+ else:
+ retVal = False
+ juju_log("service %s %s returns: %s" % \
+ (service_name, service_action, retVal))
+ return(retVal)
+
+
+#------------------------------------------------------------------------------
+# unit_get: Convenience function wrapping the juju command unit-get
+# Parameter:
+# setting_name: The setting to get out of unit_get
+# Returns: The requested information or None on error
+#------------------------------------------------------------------------------
+def unit_get(setting_name=None):
+ juju_log("unit_get: %s" % setting_name)
+ try:
+ cmd_line = ['unit-get', '--format=json']
+ if setting_name is not None:
+ cmd_line.append(setting_name)
+ unit_data = json.loads(subprocess.check_output(cmd_line))
+ except Exception, e:
+ subprocess.call(['juju-log', str(e)])
+ unit_data = None
+ finally:
+ juju_log("unit_get %s returns: %s" % (setting_name, unit_data))
+ return(unit_data)
+
+
+#------------------------------------------------------------------------------
+# config_get: Returns a dictionary containing all of the config information
+# Optional parameter: scope
+# scope: limits the scope of the returned configuration to the
+# desired config item.
+#------------------------------------------------------------------------------
+def config_get(scope=None):
+ juju_log("config_get: %s" % scope)
+ try:
+ config_cmd_line = ['config-get']
+ if scope is not None:
+ config_cmd_line.append(scope)
+ config_cmd_line.append('--format=json')
+ config_data = json.loads(subprocess.check_output(config_cmd_line))
+ except Exception, e:
+ juju_log(str(e))
+ config_data = None
+ finally:
+ juju_log("config_get: %s returns: %s" % (scope, config_data))
+ return(config_data)
+
+
+#------------------------------------------------------------------------------
+# relation_get: Returns a dictionary containing the relation information
+# Optional parameters: scope, relation_id
+# scope: limits the scope of the returned data to the
+# desired item.
+# unit_name: limits the data ( and optionally the scope )
+# to the specified unit
+# relation_id: specify relation id for out of context usage.
+#------------------------------------------------------------------------------
+def relation_get(scope=None, unit_name=None, relation_id=None):
+ juju_log("relation_get: scope: %s, unit_name: %s, relation_id: %s" %
+ (scope, unit_name, relation_id))
+ try:
+ relation_cmd_line = ['relation-get', '--format=json']
+ if relation_id is not None:
+ relation_cmd_line.extend(('-r', relation_id))
+ if scope is not None:
+ relation_cmd_line.append(scope)
+ else:
+ relation_cmd_line.append('')
+ if unit_name is not None:
+ relation_cmd_line.append(unit_name)
+ relation_data = json.loads(subprocess.check_output(relation_cmd_line))
+ except Exception, e:
+ juju_log(str(e))
+ relation_data = None
+ finally:
+ juju_log("relation_get returns: %s" % relation_data)
+ return(relation_data)
+
+
+#------------------------------------------------------------------------------
+# relation_set: Convenience function wrapping the juju command relation-set
+# Parameters:
+# key_value_pairs: A dictionary containing the key/value pairs
+# to be set.
+# Optional Parameter:
+# relation_id: The relation id to use
+# Returns: True on success or False on failure
+#------------------------------------------------------------------------------
+def relation_set(key_value_pairs=None, relation_id=None):
+ juju_log("relation_set: kv: %s, relation_id: %s" %
+ (key_value_pairs, relation_id))
+ if key_value_pairs is None or type(key_value_pairs) != type({}):
+ juju_log("relation_set: Invalid key_value_pais.")
+ return(False)
+ try:
+ relation_cmd_line = ['relation-set', '--format=json']
+ if relation_id is not None:
+ relation_cmd_line.append('-r %s' % relation_id)
+ for (key, value) in key_value_pairs.items():
+ relation_cmd_line.append('%s=%s' % (key, value))
+ retVal = (subprocess.call(relation_cmd_line) == 0)
+ except Exception, e:
+ juju_log(str(e))
+ retVal = False
+ finally:
+ juju_log("relation_set returns: %s" % retVal)
+ return(retVal)
+
+
+def relation_list(relation_id=None):
+ juju_log("relation_list: relation_id: %s" % relation_id)
+ try:
+ relation_cmd_line = ['relation-list', '--format=json']
+ if relation_id is not None:
+ relation_cmd_line.append('-r %s' % relation_id)
+ relation_data = json.loads(subprocess.check_output(relation_cmd_line))
+ except Exception, e:
+ juju_log(str(e))
+ relation_data = None
+ finally:
+ juju_log("relation_id %s returns: %s" % (relation_id, relation_data))
+ return(relation_data)
+
+
+#------------------------------------------------------------------------------
+# apt_get_install( package ): Installs a package
+#------------------------------------------------------------------------------
+def apt_get_install(packages=None):
+ juju_log("apt_get_install: %s" % packages)
+ if packages is None:
+ return(False)
+ if type(packages) == type(' '):
+ packages = [packages]
+ cmd_line = ['apt-get', '-y', 'install', '-qq']
+ cmd_line.extend(packages)
+ retVal = subprocess.call(cmd_line) == 0
+ juju_log("apt_get_install %s returns: %s" % (packages, retVal))
+ return(retVal)
+
+
+#------------------------------------------------------------------------------
+# open_port: Convenience function to open a port in juju to
+# expose a service
+#------------------------------------------------------------------------------
+def open_port(port=None, protocol="TCP"):
+ juju_log("open_port: port: %d protocol: %s" % (int(port), protocol))
+ if port is None:
+ retVal = False
+ else:
+ retVal = subprocess.call(['/usr/bin/open-port', "%d/%s" % \
+ (int(port), protocol)]) == 0
+ juju_log("open_port %d/%s returns: %s" % (int(port), protocol, retVal))
+ return(retVal)
+
+
+#------------------------------------------------------------------------------
+# close_port: Convenience function to close a port in juju to
+# unexpose a service
+#------------------------------------------------------------------------------
+def close_port(port=None, protocol="TCP"):
+ juju_log("close_port: port: %d protocol: %s" % (int(port), protocol))
+ if port is None:
+ retVal = False
+ else:
+ retVal = subprocess.call(['/usr/bin/close-port', "%d/%s" % \
+ (int(port), protocol)]) == 0
+ juju_log("close_port %d/%s returns: %s" % (int(port), protocol, retVal))
+ return(retVal)
+
+
+def port_check(host=None, port=None, protocol='TCP'):
+ if host is None or port is None:
+ juju_log("port_check: host and port must be defined.")
+ return(False)
+ if protocol.upper() == 'TCP':
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ elif protocol.upper() == 'UDP':
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ else:
+ juju_log("port_check: Unrecognized protocol %s" % protocol)
+ return(False)
+ try:
+ s.connect((host, int(port)))
+ s.shutdown(socket.SHUT_RDWR)
+ juju_log("port_check: %s:%s/%s is open" % (host, port, protocol))
+ return(True)
+ except Exception, e:
+ juju_log("port_check: Unable to connect to %s:%s/%s." %
+ (host, port, protocol))
+ juju_log("port_check: Exception: %s" % str(e))
+ return(False)
+
+
+#------------------------------------------------------------------------------
+# update_service_ports: Convenience function that evaluate the old and new
+# service ports to decide which ports need to be
+# opened and which to close
+#------------------------------------------------------------------------------
+def update_service_ports(old_service_ports=None, new_service_ports=None):
+ juju_log("update_service_ports")
+ if old_service_ports is None or new_service_ports is None:
+ return(None)
+ for port in old_service_ports:
+ if port not in new_service_ports:
+ juju_log("closing port: %d" % int(port))
+ close_port(port)
+ for port in new_service_ports:
+ if port not in old_service_ports:
+ juju_log("opening port: %d" % int(port))
+ open_port(port)
+
+
+def regex_sub(pat_replace=None, data=None):
+ juju_log("regex_sub")
+ if not pat_replace or not data:
+ raise Exception("pat_replace or data not defined")
+ if type(pat_replace) != type([]):
+ raise Exception("pat_replace must be a list of pat, replace tuples")
+ new_data = data
+ for (pattern, replace) in pat_replace:
+ new_data = re.sub(pattern, replace, data, 0, re.MULTILINE)
+ return(new_data)
+
+
+def update_file(filename=None, new_data=None, old_data=None):
+ juju_log("update_file: %s" % filename)
+ if filename is None or new_data is None:
+ retVal = False
+ try:
+ if old_data != new_data:
+ with open(filename, 'w') as f:
+ f.write(new_data)
+ retVal = True
+ except Exception, e:
+ juju_log(str(e))
+ retVal = False
+ finally:
+ juju_log("update_file %s returns: %s" % (filename, retVal))
+ return(retVal)
+
+
+def process_check(pid=None):
+ try:
+ if pid is not None:
+ cmd_line = subprocess.check_output('ps -p %d -o cmd h' %
+ int(pid), shell=True)
+ retVal = (pid, cmd_line)
+ else:
+ juju_log("process_check: pid not defined.")
+ retVal = (None, None)
+ except Exception, e:
+ juju_log("process_check exception: %s" % str(e))
+ retVal = (None, None)
+ finally:
+ juju_log("process_check returs pid: %s and cmd_line: %s" %
+ retVal)
+ return(retVal)
+
+
+def process_check_pidfile(pidfile=None):
+ if pidfile is not None and os.path.exists(pidfile):
+ return(process_check(open(pidfile).read()))
+ else:
+ juju_log("process_check_pidfile: undefined or non-existant pidfile.")
+ return((None, None))
+
+
+###############################################################################
+# Global variables
+###############################################################################
+hook_name = os.path.basename(sys.argv[0])
+default_mongodb_config = "/etc/mongodb.conf"
+default_mongodb_init_config = "/etc/init/mongodb.conf"
+default_mongos_list = "/etc/mongos.list"
+default_wait_for = 20
+default_max_tries = 20
+
+
+###############################################################################
+# Charm support functions
+###############################################################################
+def mongodb_conf(config_data=None):
+ if config_data is None:
+ return(None)
+ config = []
+
+ # header
+ config.append("# mongodb.conf")
+ config.append("")
+
+ # dbpath
+ # Create the directory if not there already
+ subprocess.call(['mkdir', '-p', '%s' % config_data['dbpath']])
+ # Make sure the mongodb user has access to it
+ subprocess.call(['chown', '-R', 'mongodb:mongodb', config_data['dbpath']])
+ config.append("dbpath=%s" % config_data['dbpath'])
+ config.append("")
+
+ # logpath
+ # Create the directory if not there already
+ subprocess.call(['mkdir',
+ '-p',
+ '%s' % os.path.dirname(config_data['logpath'])])
+ subprocess.call(['chown',
+ '-R',
+ 'mongodb:mongodb', os.path.dirname(config_data['logpath'])])
+ config.append("logpath=%s" % config_data['logpath'])
+ config.append("")
+
+ # log_append
+ if config_data['logappend'] == True:
+ config.append("logappend=true")
+ config.append("")
+
+ # bind_ip
+ if config_data['bind_ip'] != "all":
+ config.append("bind_ip = %s" % config_data['bind_ip'])
+ config.append("")
+
+ # port
+ config.append("port = %d" % config_data['port'])
+ config.append("")
+
+ # journal
+ if config_data['journal'] == True:
+ config.append("journal=true")
+ config.append("")
+
+ # cpu
+ if config_data['cpu'] == True:
+ config.append("cpu = true")
+ config.append("")
+
+ # auth
+ if config_data['auth'] == True:
+ config.append("auth = true")
+ config.append("")
+
+ # verbose
+ if config_data['verbose'] == True:
+ config.append("verbose = true")
+ config.append("")
+
+ # objcheck
+ if config_data['objcheck'] == True:
+ config.append("objcheck = true")
+ config.append("")
+
+ # quota
+ if config_data['quota'] == True:
+ config.append("quota = true")
+ config.append("")
+
+ # diaglog
+ config.append("diaglog = %d" % config_data['diaglog'])
+ config.append("")
+
+ # nocursors
+ if config_data['nocursors'] == True:
+ config.append("nocursors = true")
+ config.append("")
+
+ # nohints
+ if config_data['nohints'] == True:
+ config.append("nohints = true")
+ config.append("")
+
+ # nohttpinterface
+ if config_data['web_admin_ui'] == False:
+ config.append("nohttpinterface = true")
+ config.append("")
+
+ # noscripting
+ if config_data['noscripting'] == True:
+ config.append("noscripting = true")
+ config.append("")
+
+ # notablescan
+ if config_data['notablescan'] == True:
+ config.append("notablescan = true")
+ config.append("")
+
+ # noprealloc
+ if config_data['noprealloc'] == True:
+ config.append("noprealloc = true")
+ config.append("")
+
+ # nssize
+ if config_data['nssize'] != "default":
+ config.append("nssize = %s" % config_data['nssize'])
+ config.append("")
+
+ # mms-token
+ if config_data['mms-token'] != "disabled":
+ config.append("mms-token = %s" % config_data['mms-token'])
+ config.append("")
+
+ # mms-name
+ if config_data['mms-name'] != "disabled":
+ config.append("mms-name = %s" % config_data['mms-name'])
+ config.append("")
+
+ # mms-interval
+ if config_data['mms-interval'] != "disabled":
+ config.append("mms-interval = %s" % config_data['mms-interval'])
+ config.append("")
+
+ # master/slave
+ if config_data['master'] == "self":
+ config.append("master = true")
+ config.append("")
+ else:
+ config.append("slave = true")
+ config.append("source = %s" % config_data['master'])
+ config.append("")
+
+ # arbiter
+ if config_data['arbiter'] != "disabled" and\
+ config_data['arbiter'] != "enabled":
+ config.append("arbiter = %s" % config_data['arbiter'])
+ config.append("")
+
+ # autoresync
+ if config_data['autoresync'] == True:
+ config.append("autoresync")
+ config.append("")
+
+ # oplogSize
+ if config_data['oplogSize'] != "default":
+ config.append("oplogSize = %s" % config_data['optlogSize'])
+ config.append("")
+
+ # opIdMem
+ if config_data['opIdMem'] != "default":
+ config.append("opIdMem = %s" % config_data['opIdMem'])
+ config.append("")
+
+ # extra config options
+ if config_data['extra_config_options'] != "none":
+ for config_option in config_data['extra_config_options'].split(','):
+ config.append(config_option)
+
+ return('\n'.join(config))
+
+
+def mongo_client(host=None, command=None):
+ if host is None or command is None:
+ return(False)
+ else:
+ cmd_line = '/usr/bin/mongo'
+ cmd_line += ' --host %s' % host
+ cmd_line += ' --eval \'%s\'' % command
+ juju_log("Executing: %s" % cmd_line)
+ return(subprocess.call(cmd_line, shell=True) == 0)
+
+
+def init_replset(master_node=None):
+ if master_node is None:
+ juju_log("init_replset: master_node must be defined.")
+ retVal = False
+ else:
+ retVal = mongo_client(master_node, 'rs.initiate()')
+ juju_log("init_replset returns: %s" % retVal)
+ return(retVal)
+
+
+def join_replset(master_node=None, host=None):
+ juju_log("join_replset: master_node: %s, host: %s" %
+ (master_node, host))
+ if master_node is None or host is None:
+ retVal = False
+ else:
+ retVal = mongo_client(master_node, "rs.add(\"%s\")" % host)
+ juju_log("join_replset returns: %s" % retVal)
+ return(retVal)
+
+
+def enable_replset(replicaset_name=None):
+ if replicaset_name is None:
+ retVal = False
+ try:
+ mongodb_init_config = open(default_mongodb_init_config).read()
+ if re.search(' --replSet %s ' % replicaset_name, \
+ mongodb_init_config, re.MULTILINE) is None:
+ mongodb_init_config = regex_sub([(' -- ', \
+ ' -- --replSet %s ' % replicaset_name)], \
+ mongodb_init_config)
+ retVal = update_file(default_mongodb_init_config, mongodb_init_config)
+ except Exception, e:
+ juju_log(str(e))
+ retVal = False
+ finally:
+ return(retVal)
+
+
+def update_daemon_options(daemon_options=None):
+ mongodb_init_config = open(default_mongodb_init_config).read()
+ pat_replace = []
+ if daemon_options is None or daemon_options == "none":
+ pat_replace.append(
+ (' --config /etc/mongodb.conf.*', ' --config /etc/mongodb.conf; fi'))
+ else:
+ pat_replace.append(
+ (' --config /etc/mongodb.conf.*',
+ ' --config /etc/mongodb.conf %s; fi' % daemon_options))
+ return(update_file(default_mongodb_init_config, mongodb_init_config))
+
+
+def disable_replset(replicaset_name=None):
+ if replicaset_name is None:
+ retVal = False
+ try:
+ mongodb_init_config = open(default_mongodb_init_config).read()
+ if re.search(' --replSet %s ' % replicaset_name, \
+ mongodb_init_config, re.MULTILINE) is not None:
+ mongodb_init_config = regex_sub([
+ (' --replSet %s ' % replicaset_name, ' ')
+ ], \
+ mongodb_init_config)
+ retVal = update_file(default_mongodb_init_config, mongodb_init_config)
+ except Exception, e:
+ juju_log(str(e))
+ retVal = False
+ finally:
+ return(retVal)
+
+
+def enable_web_admin_ui(port=None):
+ if port is None:
+ juju_log("enable_web_admin_ui: port not defined.")
+ return(False)
+ try:
+ mongodb_init_config = open(default_mongodb_init_config).read()
+ if re.search(' --rest ', mongodb_init_config, re.MULTILINE) is None:
+ mongodb_init_config = regex_sub([(' -- ', ' -- --rest ')], \
+ mongodb_init_config)
+ retVal = update_file(default_mongodb_init_config, mongodb_init_config)
+ except Exception, e:
+ juju_log(str(e))
+ retVal = False
+ finally:
+ if retVal == True:
+ open_port(port)
+ return(retVal)
+
+
+def disable_web_admin_ui(port=None):
+ if port is None:
+ juju_log("disable_web_admin_ui: port not defined.")
+ return(False)
+ try:
+ mongodb_init_config = open(default_mongodb_init_config).read()
+ if re.search(' --rest ',
+ mongodb_init_config,
+ re.MULTILINE) is not None:
+ mongodb_init_config = regex_sub([(' --rest ', ' ')], \
+ mongodb_init_config)
+ retVal = update_file(default_mongodb_init_config, mongodb_init_config)
+ except Exception, e:
+ juju_log(str(e))
+ retVal = False
+ finally:
+ if retVal == True:
+ close_port(port)
+ return(retVal)
+
+
+def enable_arbiter(master_node=None, host=None):
+ juju_log("enable_arbiter: master_node: %s, host: %s" %
+ (master_node, host))
+ if master_node is None or host is None:
+ retVal = False
+ else:
+ retVal = mongo_client(master_node, "rs.addArb(\"%s\")" % host)
+ juju_log("enable_arbiter returns: %s" % retVal)
+ return(retVal)
+
+
+def configsvr_status(wait_for=default_wait_for, max_tries=default_max_tries):
+ config_data = config_get()
+ current_try = 0
+ while (process_check_pidfile('/var/run/mongodb/configsvr.pid') != \
+ (None, None)) and port_check(
+ unit_get('public-address'),
+ config_data['config_server_port']) == False and \
+ current_try < max_tries:
+ juju_log("configsvr_status: Waiting for Config Server to be ready ...")
+ time.sleep(wait_for)
+ current_try += 1
+ retVal = (
+ process_check_pidfile('/var/run/mongodb/configsvr.pid') != (None, None)
+ ) == \
+ port_check(unit_get('public-address'),
+ config_data['config_server_port']) == True
+ if retVal == True:
+ return(process_check_pidfile('/var/run/mongodb/configsvr.pid'))
+ else:
+ return((None, None))
+
+
+def configsvr_ready(wait_for=default_wait_for, max_tries=default_max_tries):
+ return(configsvr_status(wait_for, max_tries) != (None, None))
+
+
+def disable_configsvr(port=None):
+ if port is None:
+ juju_log("disable_configsvr: port not defined.")
+ return(False)
+ try:
+ config_server_port = config_get('config_server_port')
+ pid = open('/var/run/mongodb/configsvr.pid').read()
+ os.kill(int(pid), signal.SIGTERM)
+ os.unlink('/var/run/mongodb/configsvr.pid')
+ retVal = True
+ except Exception, e:
+ juju_log('no config server running ...')
+ juju_log("Exception: %s" % str(e))
+ retVal = False
+ finally:
+ juju_log("disable_configsvr returns %s" % retVal)
+ close_port(config_server_port)
+ return(retVal)
+
+
+def enable_configsvr(config_data, wait_for=default_wait_for,
+max_tries=default_max_tries):
+ if config_data is None:
+ juju_log("enable_configsvr: config_data not defined.")
+ return(False)
+
+ # Stop any running config servers
+ disable_configsvr()
+
+ # Make sure dbpath and logpath exist
+ subprocess.call(
+ [
+ 'mkdir',
+ '-p',
+ '%s' % config_data['config_server_dbpath']
+ ]
+ )
+ subprocess.call(
+ [
+ 'mkdir',
+ '-p',
+ '%s' % os.path.dirname(config_data['config_server_logpath'])
+ ]
+ )
+
+ # Start the config server
+ juju_log("enable_configsvr: Starting the config server")
+ cmd_line = "/usr/bin/mongod"
+ cmd_line += " --configsvr"
+ cmd_line += " --port %d" % config_data['config_server_port']
+ cmd_line += " --dbpath %s" % config_data['config_server_dbpath']
+ cmd_line += " --logpath %s" % config_data['config_server_logpath']
+ cmd_line += " --pidfilepath /var/run/mongodb/configsvr.pid"
+ cmd_line += " --fork"
+ subprocess.call(cmd_line, shell=True)
+
+ retVal = configsvr_ready(wait_for, max_tries)
+ if retVal == True:
+ open_port(config_data['config_server_port'])
+ if config_data['web_admin_ui'] == True:
+ port = int(config_data['config_server_port']) + 1000
+ open_port(port)
+ juju_log("enable_configsvr returns: %s" % retVal)
+ return(retVal)
+
+
+def mongos_status(wait_for=default_wait_for, max_tries=default_max_tries):
+ config_data = config_get()
+ current_try = 0
+ while (process_check_pidfile('/var/run/mongodb/mongos.pid') != \
+ (None, None)) and port_check(
+ unit_get('public-address'),
+ config_data['mongos_port']) == False and \
+ current_try < max_tries:
+ juju_log("mongos_status: Waiting for Mongo shell to be ready ...")
+ time.sleep(wait_for)
+ current_try += 1
+ retVal = \
+ (process_check_pidfile('/var/run/mongodb/mongos.pid') != \
+ (None, None)) == \
+ port_check(unit_get('public-address'),
+ config_data['mongos_port']) == True
+ if retVal == True:
+ return(process_check_pidfile('/var/run/mongodb/mongos.pid'))
+ else:
+ return((None, None))
+
+
+def mongos_ready(wait_for=default_wait_for, max_tries=default_max_tries):
+ return(mongos_status(wait_for, max_tries) != (None, None))
+
+
+def disable_mongos(port=None):
+ if port is None:
+ juju_log("disable_mongos: port not defined")
+ return(False)
+ try:
+ pid = open('/var/run/mongodb/mongos.pid').read()
+ os.kill(int(pid), signal.SIGTERM)
+ os.unlink('/var/run/mongodb/mongos.pid')
+ retVal = True
+ except Exception, e:
+ juju_log('no mongo router running ...')
+ juju_log("Exception: %s" % str(e))
+ retVal = False
+ finally:
+ juju_log("disable_mongos returns %s" % retVal)
+ close_port(port)
+ return(retVal)
+
+
+def enable_mongos(config_data=None, config_servers=None,
+ wait_for=default_wait_for, max_tries=default_max_tries):
+ juju_log("enable_mongos")
+ if config_data is None or config_servers is None:
+ juju_log("enable_mongos: config_data and config_servers are mandatory")
+ return(False)
+ if type(config_servers) != type([]):
+ juju_log("enable_mongos: config_servers must be a list")
+ return(False)
+ disable_mongos()
+ # Make sure logpath exist
+ subprocess.call(
+ [
+ 'mkdir',
+ '-p',
+ '%s' % os.path.dirname(config_data['mongos_logpath'])
+ ]
+ )
+ cmd_line = "/usr/bin/mongos"
+ cmd_line += " --logpath %s" % config_data['mongos_logpath']
+ cmd_line += " --pidfilepath /var/run/mongodb/mongos.pid"
+ cmd_line += " --port %d" % config_data['mongos_port']
+ cmd_line += " --fork"
+ if len(config_servers) > 0:
+ cmd_line += ' --configdb %s' % ','.join(config_servers)
+ subprocess.call(cmd_line, shell=True)
+ retVal = mongos_ready(wait_for, max_tries)
+ if retVal == True:
+ open_port(config_data['mongos_port'])
+ juju_log("enable_mongos returns: %s" % retVal)
+ return(retVal)
+
+
+def load_config_servers(mongos_list=None):
+ if os.path.exists(mongos_list):
+ retVal = [line.strip() for line in open(mongos_list).readlines()]
+ else:
+ retVal = []
+ return(retVal)
+
+
+def restart_mongod(wait_for=default_wait_for, max_tries=default_max_tries):
+ my_hostname = unit_get('public-address')
+ my_port = config_get('port')
+ current_try = 0
+
+ service('mongodb', 'stop')
+ if os.path.exists('/var/lib/mongodb/mongod.lock'):
+ os.remove('/var/lib/mongodb/mongod.lock')
+
+ service('mongodb', 'start')
+
+ while service('mongodb', 'status') == True and \
+ port_check(my_hostname, my_port) == False and \
+ current_try < max_tries:
+ juju_log("restart_mongod: Waiting for MongoDB to be ready ...")
+ time.sleep(wait_for)
+ current_try += 1
+
+ return(
+ service('mongodb', 'status') == \
+ port_check(my_hostname, my_port) == True
+ )
+
+
+###############################################################################
+# Hook functions
+###############################################################################
+def install_hook():
+ juju_log("Installing mongodb")
+ if not apt_get_install('mongodb'):
+ juju_log("Installation of mongodb failed.")
+ return(False)
+ else:
+ return(True)
+
+
+def config_changed():
+ juju_log("Entering config_changed")
+ print "Entering config_changed"
+ config_data = config_get()
+ print "config_data: ", config_data
+ mongodb_config = open(default_mongodb_config).read()
+
+ # current ports
+ current_mongodb_port = re.search('^#*port\s+=\s+(\w+)',
+ mongodb_config,
+ re.MULTILINE).group(1)
+ current_web_admin_ui_port = int(current_mongodb_port) + 1000
+ new_web_admin_ui_port = int(config_data['port']) + 1000
+
+ print "current_mongodb_port: ", current_mongodb_port
+ public_address = unit_get('public-address')
+ print "public_address: ", public_address
+
+ # Update mongodb configuration file
+ mongodb_config = mongodb_conf(config_data)
+ update_file(default_mongodb_config, mongodb_config)
+
+ # web_admin_ui
+ if config_data['web_admin_ui'] == True:
+ enable_web_admin_ui(new_web_admin_ui_port)
+ else:
+ disable_web_admin_ui(current_web_admin_ui_port)
+
+ # replicaset_master
+ if config_data['replicaset_master'] != "auto":
+ enable_replset(config_data['replicaset'])
+ join_replset(config_data['replicaset_master'])
+
+ # extra demon options
+ update_daemon_options(config_data['extra_daemon_options'])
+
+ # restart mongodb
+ restart_mongod()
+
+ # attach to replSet ( if needed )
+ if config_data['replicaset_master'] != "auto":
+ join_replset(config_data['replicaset_master'], public_address)
+
+ # arbiter
+ if config_data['replicaset_master'] != 'auto':
+ if config_data['arbiter'] != "disabled" and\
+ config_data['replicaset_master'] != "auto":
+ if config_data['arbiter'] == 'enable':
+ enable_arbiter(config_data['replicaset_master'],\
+ "%s:%s" % (public_address, config_data['port']))
+ else:
+ enable_arbiter(config_data['replicaset_master'],\
+ config_data['arbiter'])
+
+ # expose necessary ports
+ update_service_ports([current_mongodb_port], [config_data['port']])
+
+ if config_data['web_admin_ui'] == True:
+ current_web_admin_ui_port = int(current_mongodb_port) + 1000
+ new_web_admin_ui_port = int(config_data['port']) + 1000
+ close_port(current_web_admin_ui_port)
+ open_port(new_web_admin_ui_port)
+
+ # update config-server information and port
+ try:
+ (configsvr_pid, configsvr_cmd_line) = configsvr_status()
+ except Exception, e:
+ configsvr_pid = None
+ configsvr_cmd_line = None
+ juju_log("config_changed: configsvr_status failed.")
+ juju_log("config_changed: Exception: %s" % str(e))
+
+ if configsvr_pid is not None:
+ configsvr_port = re.search('--port (\w+)', configsvr_cmd_line).group(2)
+ disable_configsvr(configsvr_port)
+ enable_configsvr(config_data['config_server_port'])
+ else:
+ open_port(config_data['config_server_port'])
+
+ # update mongos information and port
+ try:
+ (mongos_pid, mongos_cmd_line) = mongos_status()
+ except Exception, e:
+ mongos_pid = None
+ mongos_cmd_line = None
+ juju_log("config_changed: mongos_status failed.")
+ juju_log("config_changed: Exceptions: %s" % str(e))
+
+ if mongos_pid is not None:
+ mongos_port = re.search('--port (\w+)', mongos_cmd_line).group(2)
+ disable_mongos(mongos_port)
+ enable_mongos(config_data['mongos_port'])
+ else:
+ open_port(config_data['mongos_port'])
+
+ print "About to leave config_changed"
+ return(True)
+
+
+def start_hook():
+ juju_log("start_hook")
+ retVal = restart_mongod()
+ juju_log("start_hook returns: %s" % retVal)
+ return(retVal)
+
+
+def stop_hook():
+ juju_log("stop_hook")
+ try:
+ retVal = service('mongodb', 'stop')
+ os.remove('/var/lib/mongodb/mongod.lock')
+ except Exception, e:
+ juju_log(str(e))
+ retVal = False
+ finally:
+ juju_log("stop_hook returns: %s" % retVal)
+ return(retVal)
+
+
+def database_relation_joined():
+ juju_log("database_relation_joined")
+ my_hostname = unit_get('public-address')
+ my_port = config_get('port')
+ my_replset = config_get('replicaset')
+ juju_log("my_hostname: %s" % my_hostname)
+ juju_log("my_port: %s" % my_port)
+ juju_log("my_replset: %s" % my_replset)
+ return(relation_set(
+ {
+ 'hostname': my_hostname,
+ 'port': my_port,
+ 'replset': my_replset,
+ 'type': 'database',
+ }))
+
+
+def replica_set_relation_joined():
+ juju_log("replica_set_relation_joined")
+ my_hostname = unit_get('public-address')
+ my_port = config_get('port')
+ my_replset = config_get('replicaset')
+ my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
+ juju_log("my_hostname: %s" % my_hostname)
+ juju_log("my_port: %s" % my_port)
+ juju_log("my_replset: %s" % my_replset)
+ juju_log("my_install_order: %s" % my_install_order)
+ return(enable_replset(my_replset) == \
+ restart_mongod() == \
+ relation_set(
+ {
+ 'hostname': my_hostname,
+ 'port': my_port,
+ 'replset': my_replset,
+ 'install-order': my_install_order,
+ 'type': 'replset',
+ }))
+
+
+def replica_set_relation_changed():
+ juju_log("replica_set_relation_changed")
+ my_hostname = unit_get('public-address')
+ my_port = config_get('port')
+ my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
+ my_replicaset_master = config_get('replicaset_master')
+
+ # If we are joining an existing replicaset cluster, just join and leave.
+ if my_replicaset_master != "auto":
+ return(join_replset(my_replicaset_master, my_hostname))
+
+ # Default to this node being the master
+ master_hostname = my_hostname
+ master_port = my_port
+ master_install_order = my_install_order
+
+ # Check the nodes in the relation to find the master
+ for member in relation_list():
+ hostname = relation_get('hostname', member)
+ port = relation_get('port', member)
+ install_order = relation_get('install-order', member)
+ if int(install_order) < int(master_install_order):
+ master_hostname = hostname
+ master_port = port
+ master_install_order = install_order
+
+ # Initiate the replset
+ init_replset("%s:%s" % (master_hostname, master_port))
+
+ # Add the rest of the nodes to the replset
+ for member in relation_list():
+ hostname = relation_get('hostname', member)
+ port = relation_get('port', member)
+ if master_hostname != hostname:
+ if hostname == my_hostname:
+ subprocess.call(['mongo',
+ '--eval',
+ "rs.add(\"%s\")" % hostname])
+ else:
+ join_replset("%s:%s" % (master_hostname, master_port),
+ "%s:%s" % (hostname, port))
+
+ # Add this node to the replset ( if needed )
+ if master_hostname != my_hostname:
+ join_replset("%s:%s" % (master_hostname, master_port),
+ "%s:%s" % (my_hostname, my_port))
+
+ return(True)
+
+
+def configsvr_relation_joined():
+ juju_log("configsvr_relation_joined")
+ my_hostname = unit_get('public-address')
+ my_port = config_get('config_server_port')
+ my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
+ return(relation_set(
+ {
+ 'hostname': my_hostname,
+ 'port': my_port,
+ 'install-order': my_install_order,
+ 'type': 'configsvr',
+ }))
+
+
+def configsvr_relation_changed():
+ juju_log("configsvr_relation_changed")
+ config_data = config_get()
+ my_port = config_data['config_server_port']
+ disable_configsvr(my_port)
+ retVal = enable_configsvr(config_data)
+ juju_log("configsvr_relation_changed returns: %s" % retVal)
+ return(retVal)
+
+
+def mongos_relation_joined():
+ juju_log("mongos_relation_joined")
+ config_data = config_get()
+ my_hostname = unit_get('public-address')
+ my_port = config_data['mongos_port']
+ my_install_order = os.environ['JUJU_UNIT_NAME'].split('/')[1]
+ return(relation_set(
+ {
+ 'hostname': my_hostname,
+ 'port': my_port,
+ 'install-order': my_install_order,
+ 'type': 'mongos'
+ }))
+
+
+def mongos_relation_changed():
+ juju_log("mongos_relation_changed")
+ config_servers = load_config_servers(default_mongos_list)
+ print "Loaded config_servers: ", config_servers
+ config_data = config_get()
+ for member in relation_list():
+ hostname = relation_get('hostname', member)
+ port = relation_get('port', member)
+ rel_type = relation_get('type', member)
+ if rel_type == 'configsvr':
+ print "Adding config server: %s:%s" % (hostname, port)
+ juju_log("Adding config server: %s:%s" % (hostname, port))
+ if hostname is not None and \
+ port is not None and \
+ hostname != '' and \
+ port != '' and \
+ "%s:%s" % (hostname, port) not in config_servers:
+ config_servers.append("%s:%s" % (hostname, port))
+ disable_mongos(config_data['mongos_port'])
+ retVal = enable_mongos(config_data, config_servers)
+ if retVal == True:
+ update_file(default_mongos_list, '\n'.join(config_servers))
+ elif rel_type == 'database':
+ if mongos_ready() == True:
+ mongos_host = "%s:%s" % (
+ unit_get('public-address'),
+ config_get('mongos_port'))
+ shard_command = "sh.addShard(\"%s:%s\")" % (hostname, port)
+ retVal = mongo_client(mongos_host, shard_command)
+ else:
+ juju_log("mongos_relation_change: undefined rel_type: %s" %
+ rel_type)
+ return(False)
+ juju_log("mongos_relation_changed returns: %s" % retVal)
+ return(retVal)
+
+
+def mongos_relation_broken():
+# config_servers = load_config_servers(default_mongos_list)
+# for member in relation_list():
+# hostname = relation_get('hostname', member)
+# port = relation_get('port', member)
+# if '%s:%s' % (hostname, port) in config_servers:
+# config_servers.remove('%s:%s' % (hostname, port))
+# return(update_file(default_mongos_list, '\n'.join(config_servers)))
+ return(True)
+
+###############################################################################
+# Main section
+###############################################################################
+if hook_name == "install":
+ retVal = install_hook()
+elif hook_name == "config-changed":
+ retVal = config_changed()
+elif hook_name == "start":
+ retVal = start_hook()
+elif hook_name == "stop":
+ retVal = stop_hook()
+elif hook_name == "database-relation-joined":
+ retVal = database_relation_joined()
+elif hook_name == "replica-set-relation-joined":
+ retVal = replica_set_relation_joined()
+elif hook_name == "replica-set-relation-changed":
+ retVal = replica_set_relation_changed()
+elif hook_name == "configsvr-relation-joined":
+ retVal = configsvr_relation_joined()
+elif hook_name == "configsvr-relation-changed":
+ retVal = configsvr_relation_changed()
+elif hook_name == "mongos-relation-joined":
+ retVal = mongos_relation_joined()
+elif hook_name == "mongos-relation-changed":
+ retVal = mongos_relation_changed()
+elif hook_name == "mongos-relation-broken":
+ retVal = mongos_relation_broken()
+else:
+ print "Unknown hook"
+ retVal = False
+
+if retVal == True:
+ sys.exit(0)
+else:
+ sys.exit(1)
diff --git a/hooks/install b/hooks/install
new file mode 120000
index 0000000..f94593a
--- /dev/null
+++ b/hooks/install
@@ -0,0 +1 @@
+./hooks.py \ No newline at end of file
diff --git a/hooks/mongos-relation-broken b/hooks/mongos-relation-broken
new file mode 120000
index 0000000..f94593a
--- /dev/null
+++ b/hooks/mongos-relation-broken
@@ -0,0 +1 @@
+./hooks.py \ No newline at end of file
diff --git a/hooks/mongos-relation-changed b/hooks/mongos-relation-changed
new file mode 120000
index 0000000..9416ca6
--- /dev/null
+++ b/hooks/mongos-relation-changed
@@ -0,0 +1 @@
+hooks.py \ No newline at end of file
diff --git a/hooks/mongos-relation-joined b/hooks/mongos-relation-joined
new file mode 120000
index 0000000..9416ca6
--- /dev/null
+++ b/hooks/mongos-relation-joined
@@ -0,0 +1 @@
+hooks.py \ No newline at end of file
diff --git a/hooks/replica-set-relation-changed b/hooks/replica-set-relation-changed
new file mode 120000
index 0000000..f94593a
--- /dev/null
+++ b/hooks/replica-set-relation-changed
@@ -0,0 +1 @@
+./hooks.py \ No newline at end of file
diff --git a/hooks/replica-set-relation-joined b/hooks/replica-set-relation-joined
new file mode 120000
index 0000000..f94593a
--- /dev/null
+++ b/hooks/replica-set-relation-joined
@@ -0,0 +1 @@
+./hooks.py \ No newline at end of file
diff --git a/hooks/start b/hooks/start
new file mode 120000
index 0000000..f94593a
--- /dev/null
+++ b/hooks/start
@@ -0,0 +1 @@
+./hooks.py \ No newline at end of file
diff --git a/hooks/stop b/hooks/stop
new file mode 120000
index 0000000..f94593a
--- /dev/null
+++ b/hooks/stop
@@ -0,0 +1 @@
+./hooks.py \ No newline at end of file