added postgresql support
This commit is contained in:
parent
57ee8728c0
commit
593ebdd59e
@ -147,7 +147,8 @@ class SharedDBContext(OSContextGenerator):
|
||||
'database_host': rdata.get('db_host'),
|
||||
'database': self.database,
|
||||
'database_user': self.user,
|
||||
'database_password': rdata.get(password_setting)
|
||||
'database_password': rdata.get(password_setting),
|
||||
'database_type': 'mysql',
|
||||
}
|
||||
if context_complete(ctxt):
|
||||
db_ssl(rdata, ctxt, self.ssl_dir)
|
||||
@ -155,6 +156,35 @@ class SharedDBContext(OSContextGenerator):
|
||||
return {}
|
||||
|
||||
|
||||
class PostgresqlDBContext(OSContextGenerator):
|
||||
interfaces = ['pgsql-db']
|
||||
|
||||
def __init__(self, database=None):
|
||||
self.database = database
|
||||
|
||||
def __call__(self):
|
||||
self.database = self.database or config('database')
|
||||
if self.database is None:
|
||||
log('Could not generate postgresql_db context. '
|
||||
'Missing required charm config options. '
|
||||
'(database name)')
|
||||
raise OSContextError
|
||||
ctxt = {}
|
||||
|
||||
for rid in relation_ids(self.interfaces[0]):
|
||||
for unit in related_units(rid):
|
||||
ctxt = {
|
||||
'database_host': relation_get('host', rid=rid, unit=unit),
|
||||
'database': self.database,
|
||||
'database_user': relation_get('user', rid=rid, unit=unit),
|
||||
'database_password': relation_get('password', rid=rid, unit=unit),
|
||||
'database_type': 'postgresql',
|
||||
}
|
||||
if context_complete(ctxt):
|
||||
return ctxt
|
||||
return {}
|
||||
|
||||
|
||||
def db_ssl(rdata, ctxt, ssl_dir):
|
||||
if 'ssl_ca' in rdata and ssl_dir:
|
||||
ca_path = os.path.join(ssl_dir, 'db-client.ca')
|
||||
|
BIN
hooks/keystone_context.pyc
Normal file
BIN
hooks/keystone_context.pyc
Normal file
Binary file not shown.
@ -12,7 +12,9 @@ from charmhelpers.core.hookenv import (
|
||||
Hooks,
|
||||
UnregisteredHookError,
|
||||
config,
|
||||
is_relation_made,
|
||||
log,
|
||||
ERROR,
|
||||
relation_get,
|
||||
relation_ids,
|
||||
relation_set,
|
||||
@ -102,11 +104,30 @@ def config_changed():
|
||||
|
||||
@hooks.hook('shared-db-relation-joined')
|
||||
def db_joined():
|
||||
if is_relation_made('pgsql-db'):
|
||||
# error, postgresql is used
|
||||
e = ('Attempting to associate a mysql database when there is already '
|
||||
'associated a postgresql one')
|
||||
log(e, level=ERROR)
|
||||
raise Exception(e)
|
||||
|
||||
relation_set(database=config('database'),
|
||||
username=config('database-user'),
|
||||
hostname=unit_get('private-address'))
|
||||
|
||||
|
||||
@hooks.hook('pgsql-db-relation-joined')
|
||||
def pgsql_db_joined():
|
||||
if is_relation_made('shared-db'):
|
||||
# raise error
|
||||
e = ('Attempting to associate a postgresql database when there is already '
|
||||
'associated a mysql one')
|
||||
log(e, level=ERROR)
|
||||
raise Exception(e)
|
||||
|
||||
relation_set(database=config('database'))
|
||||
|
||||
|
||||
@hooks.hook('shared-db-relation-changed')
|
||||
@restart_on_change(restart_map())
|
||||
def db_changed():
|
||||
@ -124,6 +145,23 @@ def db_changed():
|
||||
identity_changed(relation_id=rid, remote_unit=unit)
|
||||
|
||||
|
||||
@hooks.hook('pgsql-db-relation-changed')
|
||||
@restart_on_change(restart_map())
|
||||
def pgsql_db_changed():
|
||||
if 'pgsql-db' not in CONFIGS.complete_contexts():
|
||||
log('pgsql-db relation incomplete. Peer not ready?')
|
||||
else:
|
||||
CONFIGS.write(KEYSTONE_CONF)
|
||||
if eligible_leader(CLUSTER_RES):
|
||||
migrate_database()
|
||||
ensure_initial_admin(config)
|
||||
# Ensure any existing service entries are updated in the
|
||||
# new database backend
|
||||
for rid in relation_ids('identity-service'):
|
||||
for unit in related_units(rid):
|
||||
identity_changed(relation_id=rid, remote_unit=unit)
|
||||
|
||||
|
||||
@hooks.hook('identity-service-relation-joined')
|
||||
def identity_joined():
|
||||
""" Do nothing until we get information about requested service """
|
||||
|
BIN
hooks/keystone_hooks.pyc
Normal file
BIN
hooks/keystone_hooks.pyc
Normal file
Binary file not shown.
@ -1,12 +1,10 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import base64
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
CA_EXPIRY = '365'
|
||||
ORG_NAME = 'Ubuntu'
|
||||
|
BIN
hooks/keystone_ssl.pyc
Normal file
BIN
hooks/keystone_ssl.pyc
Normal file
Binary file not shown.
@ -64,6 +64,7 @@ BASE_PACKAGES = [
|
||||
'openssl',
|
||||
'python-keystoneclient',
|
||||
'python-mysqldb',
|
||||
'python-psycopg2',
|
||||
'pwgen',
|
||||
'unison',
|
||||
'uuid',
|
||||
@ -98,6 +99,7 @@ BASE_RESOURCE_MAP = OrderedDict([
|
||||
'services': BASE_SERVICES,
|
||||
'contexts': [keystone_context.KeystoneContext(),
|
||||
context.SharedDBContext(ssl_dir=KEYSTONE_CONF_DIR),
|
||||
context.PostgresqlDBContext(),
|
||||
context.SyslogContext(),
|
||||
keystone_context.HAProxyContext()],
|
||||
}),
|
||||
|
BIN
hooks/keystone_utils.pyc
Normal file
BIN
hooks/keystone_utils.pyc
Normal file
Binary file not shown.
@ -17,8 +17,7 @@ from lib.utils import (
|
||||
config_get,
|
||||
install,
|
||||
get_host_ip,
|
||||
restart
|
||||
)
|
||||
restart)
|
||||
from lib.cluster_utils import https
|
||||
|
||||
import os
|
||||
@ -136,8 +135,7 @@ def enable_https(port_maps, namespace, cert, key, ca_cert=None):
|
||||
"ext": ext_port,
|
||||
"int": int_port,
|
||||
"namespace": namespace,
|
||||
"private_address": get_host_ip()
|
||||
}
|
||||
"private_address": get_host_ip()}
|
||||
fsite.write(render_template(SITE_TEMPLATE,
|
||||
context))
|
||||
|
||||
|
@ -14,8 +14,7 @@ from lib.utils import (
|
||||
relation_list,
|
||||
relation_get,
|
||||
get_unit_hostname,
|
||||
config_get
|
||||
)
|
||||
config_get)
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
@ -34,8 +33,7 @@ def is_clustered():
|
||||
def is_leader(resource):
|
||||
cmd = [
|
||||
"crm", "resource",
|
||||
"show", resource
|
||||
]
|
||||
"show", resource]
|
||||
try:
|
||||
status = subprocess.check_output(cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
|
@ -14,8 +14,7 @@ from lib.utils import (
|
||||
relation_get,
|
||||
unit_get,
|
||||
reload,
|
||||
render_template
|
||||
)
|
||||
render_template)
|
||||
import os
|
||||
|
||||
HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
|
||||
@ -44,8 +43,7 @@ def configure_haproxy(service_ports):
|
||||
unit=unit)
|
||||
context = {
|
||||
'units': cluster_hosts,
|
||||
'service_ports': service_ports
|
||||
}
|
||||
'service_ports': service_ports}
|
||||
with open(HAPROXY_CONF, 'w') as f:
|
||||
f.write(render_template(os.path.basename(HAPROXY_CONF),
|
||||
context))
|
||||
|
@ -73,15 +73,14 @@ def get_keypair(user):
|
||||
|
||||
pub_key = '%s.pub' % priv_key
|
||||
if not os.path.isfile(pub_key):
|
||||
utils.juju_log('INFO', 'Generatring missing ssh public key @ %s.' % \
|
||||
utils.juju_log('INFO', 'Generatring missing ssh public key @ %s.' %
|
||||
pub_key)
|
||||
cmd = ['ssh-keygen', '-y', '-f', priv_key]
|
||||
p = subprocess.check_output(cmd).strip()
|
||||
with open(pub_key, 'wb') as out:
|
||||
out.write(p)
|
||||
subprocess.check_call(['chown', '-R', user, ssh_dir])
|
||||
return open(priv_key, 'r').read().strip(), \
|
||||
open(pub_key, 'r').read().strip()
|
||||
return open(priv_key, 'r').read().strip(), open(pub_key, 'r').read().strip()
|
||||
|
||||
|
||||
def write_authorized_keys(user, keys):
|
||||
@ -149,7 +148,7 @@ def ssh_authorized_peers(peer_interface, user, group=None, ensure_local_user=Fal
|
||||
hosts.append(settings['private-address'])
|
||||
else:
|
||||
utils.juju_log('INFO',
|
||||
'ssh_authorized_peers(): ssh_pub_key '\
|
||||
'ssh_authorized_peers(): ssh_pub_key '
|
||||
'missing for unit %s, skipping.' % unit)
|
||||
write_authorized_keys(user, keys)
|
||||
write_known_hosts(user, hosts)
|
||||
@ -204,8 +203,7 @@ def sync_to_peers(peer_interface, user, paths=[], verbose=False):
|
||||
hosts.append(settings['private-address'])
|
||||
else:
|
||||
print 'unison sync_to_peers: peer (%s) has not authorized '\
|
||||
'*this* host yet, skipping.' %\
|
||||
settings['private-address']
|
||||
'*this* host yet, skipping.' % settings['private-address']
|
||||
|
||||
for path in paths:
|
||||
# removing trailing slash from directory paths, unison
|
||||
@ -214,7 +212,6 @@ def sync_to_peers(peer_interface, user, paths=[], verbose=False):
|
||||
path = path[:(len(path) - 1)]
|
||||
for host in hosts:
|
||||
cmd = base_cmd + [path, 'ssh://%s@%s/%s' % (user, host, path)]
|
||||
utils.juju_log('INFO', 'Syncing local path %s to %s@%s:%s' %\
|
||||
utils.juju_log('INFO', 'Syncing local path %s to %s@%s:%s' %
|
||||
(path, user, host, path))
|
||||
print ' '.join(cmd)
|
||||
run_as_user(user, cmd)
|
||||
|
@ -32,8 +32,7 @@ def install(*pkgs):
|
||||
cmd = [
|
||||
'apt-get',
|
||||
'-y',
|
||||
'install'
|
||||
]
|
||||
'install']
|
||||
for pkg in pkgs:
|
||||
cmd.append(pkg)
|
||||
subprocess.check_call(cmd)
|
||||
@ -54,16 +53,14 @@ except ImportError:
|
||||
|
||||
|
||||
def render_template(template_name, context, template_dir=TEMPLATES_DIR):
|
||||
templates = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(template_dir)
|
||||
)
|
||||
templates = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir))
|
||||
template = templates.get_template(template_name)
|
||||
return template.render(context)
|
||||
|
||||
CLOUD_ARCHIVE = \
|
||||
""" # Ubuntu Cloud Archive
|
||||
deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
|
||||
"""
|
||||
""" # Ubuntu Cloud Archive
|
||||
deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
|
||||
"""
|
||||
|
||||
CLOUD_ARCHIVE_POCKETS = {
|
||||
'folsom': 'precise-updates/folsom',
|
||||
@ -77,8 +74,7 @@ CLOUD_ARCHIVE_POCKETS = {
|
||||
'havana/proposed': 'precise-proposed/havana',
|
||||
'icehouse': 'precise-updates/icehouse',
|
||||
'icehouse/updates': 'precise-updates/icehouse',
|
||||
'icehouse/proposed': 'precise-proposed/icehouse',
|
||||
}
|
||||
'icehouse/proposed': 'precise-proposed/icehouse'}
|
||||
|
||||
|
||||
def configure_source():
|
||||
@ -88,8 +84,7 @@ def configure_source():
|
||||
if source.startswith('ppa:'):
|
||||
cmd = [
|
||||
'add-apt-repository',
|
||||
source
|
||||
]
|
||||
source]
|
||||
subprocess.check_call(cmd)
|
||||
if source.startswith('cloud:'):
|
||||
# CA values should be formatted as cloud:ubuntu-openstack/pocket, eg:
|
||||
@ -106,8 +101,7 @@ def configure_source():
|
||||
cmd = [
|
||||
'apt-key',
|
||||
'adv', '--keyserver keyserver.ubuntu.com',
|
||||
'--recv-keys', key
|
||||
]
|
||||
'--recv-keys', key]
|
||||
subprocess.check_call(cmd)
|
||||
elif l == 1:
|
||||
apt_line = source
|
||||
@ -116,8 +110,7 @@ def configure_source():
|
||||
apt.write(apt_line + "\n")
|
||||
cmd = [
|
||||
'apt-get',
|
||||
'update'
|
||||
]
|
||||
'update']
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
# Protocols
|
||||
@ -128,8 +121,7 @@ UDP = 'UDP'
|
||||
def expose(port, protocol='TCP'):
|
||||
cmd = [
|
||||
'open-port',
|
||||
'{}/{}'.format(port, protocol)
|
||||
]
|
||||
'{}/{}'.format(port, protocol)]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
@ -137,8 +129,7 @@ def juju_log(severity, message):
|
||||
cmd = [
|
||||
'juju-log',
|
||||
'--log-level', severity,
|
||||
message
|
||||
]
|
||||
message]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
@ -162,8 +153,7 @@ def cached(func):
|
||||
def relation_ids(relation):
|
||||
cmd = [
|
||||
'relation-ids',
|
||||
relation
|
||||
]
|
||||
relation]
|
||||
result = str(subprocess.check_output(cmd)).split()
|
||||
if result == "":
|
||||
return None
|
||||
@ -175,8 +165,7 @@ def relation_ids(relation):
|
||||
def relation_list(rid):
|
||||
cmd = [
|
||||
'relation-list',
|
||||
'-r', rid,
|
||||
]
|
||||
'-r', rid]
|
||||
result = str(subprocess.check_output(cmd)).split()
|
||||
if result == "":
|
||||
return None
|
||||
@ -187,8 +176,7 @@ def relation_list(rid):
|
||||
@cached
|
||||
def relation_get(attribute, unit=None, rid=None):
|
||||
cmd = [
|
||||
'relation-get',
|
||||
]
|
||||
'relation-get']
|
||||
if rid:
|
||||
cmd.append('-r')
|
||||
cmd.append(rid)
|
||||
@ -206,8 +194,7 @@ def relation_get(attribute, unit=None, rid=None):
|
||||
def relation_get_dict(relation_id=None, remote_unit=None):
|
||||
"""Obtain all relation data as dict by way of JSON"""
|
||||
cmd = [
|
||||
'relation-get', '--format=json'
|
||||
]
|
||||
'relation-get', '--format=json']
|
||||
if relation_id:
|
||||
cmd.append('-r')
|
||||
cmd.append(relation_id)
|
||||
@ -225,8 +212,7 @@ def relation_get_dict(relation_id=None, remote_unit=None):
|
||||
|
||||
def relation_set(**kwargs):
|
||||
cmd = [
|
||||
'relation-set'
|
||||
]
|
||||
'relation-set']
|
||||
args = []
|
||||
for k, v in kwargs.items():
|
||||
if k == 'rid':
|
||||
@ -243,8 +229,7 @@ def relation_set(**kwargs):
|
||||
def unit_get(attribute):
|
||||
cmd = [
|
||||
'unit-get',
|
||||
attribute
|
||||
]
|
||||
attribute]
|
||||
value = subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
if value == "":
|
||||
return None
|
||||
@ -257,8 +242,7 @@ def config_get(attribute):
|
||||
cmd = [
|
||||
'config-get',
|
||||
'--format',
|
||||
'json',
|
||||
]
|
||||
'json']
|
||||
out = subprocess.check_output(cmd).strip() # IGNORE:E1103
|
||||
cfg = json.loads(out)
|
||||
|
||||
@ -321,8 +305,7 @@ def running(service):
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
else:
|
||||
if ("start/running" in output or
|
||||
"is running" in output):
|
||||
if ("start/running" in output or "is running" in output):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
1
hooks/pgsql-db-relation-changed
Symbolic link
1
hooks/pgsql-db-relation-changed
Symbolic link
@ -0,0 +1 @@
|
||||
keystone_hooks.py
|
1
hooks/pgsql-db-relation-joined
Symbolic link
1
hooks/pgsql-db-relation-joined
Symbolic link
@ -0,0 +1 @@
|
||||
keystone_hooks.py
|
@ -12,6 +12,8 @@ provides:
|
||||
requires:
|
||||
shared-db:
|
||||
interface: mysql-shared
|
||||
pgsql-db:
|
||||
interface: pgsql
|
||||
ha:
|
||||
interface: hacluster
|
||||
scope: container
|
||||
|
@ -14,7 +14,7 @@ verbose = {{ verbose }}
|
||||
|
||||
[sql]
|
||||
{% if database_host -%}
|
||||
connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
|
||||
connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
|
||||
{% else -%}
|
||||
connection = sqlite:////var/lib/keystone/keystone.db
|
||||
{% endif -%}
|
||||
|
@ -14,7 +14,7 @@ verbose = {{ verbose }}
|
||||
|
||||
[sql]
|
||||
{% if database_host -%}
|
||||
connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
|
||||
connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
|
||||
{% else -%}
|
||||
connection = sqlite:////var/lib/keystone/keystone.db
|
||||
{% endif -%}
|
||||
|
@ -14,7 +14,7 @@ verbose = {{ verbose }}
|
||||
|
||||
[sql]
|
||||
{% if database_host -%}
|
||||
connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
|
||||
connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
|
||||
{% else -%}
|
||||
connection = sqlite:////var/lib/keystone/keystone.db
|
||||
{% endif -%}
|
||||
|
@ -14,7 +14,7 @@ verbose = {{ verbose }}
|
||||
|
||||
[sql]
|
||||
{% if database_host -%}
|
||||
connection = mysql://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
|
||||
connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %}
|
||||
{% else -%}
|
||||
connection = sqlite:////var/lib/keystone/keystone.db
|
||||
{% endif -%}
|
||||
|
0
unit_tests/__init__.py
Normal file
0
unit_tests/__init__.py
Normal file
120
unit_tests/test_keystone_hooks.py
Normal file
120
unit_tests/test_keystone_hooks.py
Normal file
@ -0,0 +1,120 @@
|
||||
from mock import call, patch, MagicMock
|
||||
import os
|
||||
|
||||
from test_utils import CharmTestCase
|
||||
|
||||
os.environ['JUJU_UNIT_NAME'] = 'keystone'
|
||||
with patch('charmhelpers.core.hookenv.config') as config:
|
||||
config.return_value = 'keystone'
|
||||
import keystone_utils as utils
|
||||
|
||||
_reg = utils.register_configs
|
||||
_map = utils.restart_map
|
||||
|
||||
utils.register_configs = MagicMock()
|
||||
utils.restart_map = MagicMock()
|
||||
|
||||
import keystone_hooks as hooks
|
||||
|
||||
utils.register_configs = _reg
|
||||
utils.restart_map = _map
|
||||
|
||||
TO_PATCH = [
|
||||
# charmhelpers.core.hookenv
|
||||
'Hooks',
|
||||
'config',
|
||||
'is_relation_made',
|
||||
'log',
|
||||
'relation_ids',
|
||||
'relation_set',
|
||||
'relation_get',
|
||||
'unit_get',
|
||||
# charmhelpers.core.host
|
||||
'apt_install',
|
||||
'apt_update',
|
||||
'restart_on_change',
|
||||
# charmhelpers.contrib.openstack.utils
|
||||
'configure_installation_source',
|
||||
# charmhelpers.contrib.hahelpers.cluster_utils
|
||||
'eligible_leader',
|
||||
# keystone_utils
|
||||
'restart_map',
|
||||
'register_configs',
|
||||
'do_openstack_upgrade',
|
||||
'migrate_database',
|
||||
# other
|
||||
'check_call',
|
||||
'execd_preinstall',
|
||||
'mkdir'
|
||||
]
|
||||
|
||||
|
||||
class KeystoneRelationTests(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(KeystoneRelationTests, self).setUp(hooks, TO_PATCH)
|
||||
self.config.side_effect = self.test_config.get
|
||||
|
||||
|
||||
def test_db_joined(self):
|
||||
self.unit_get.return_value = 'keystone.foohost.com'
|
||||
self.is_relation_made.return_value = False
|
||||
hooks.db_joined()
|
||||
self.relation_set.assert_called_with(database='keystone',
|
||||
username='keystone',
|
||||
hostname='keystone.foohost.com')
|
||||
self.unit_get.assert_called_with('private-address')
|
||||
|
||||
def test_postgresql_db_joined(self):
|
||||
self.unit_get.return_value = 'keystone.foohost.com'
|
||||
self.is_relation_made.return_value = False
|
||||
hooks.pgsql_db_joined()
|
||||
self.relation_set.assert_called_with(database='keystone'),
|
||||
|
||||
def test_db_joined_with_postgresql(self):
|
||||
self.is_relation_made.return_value = True
|
||||
|
||||
with self.assertRaises(Exception) as context:
|
||||
hooks.db_joined()
|
||||
self.assertEqual(context.exception.message,
|
||||
'Attempting to associate a mysql database when there '
|
||||
'is already associated a postgresql one')
|
||||
|
||||
def test_postgresql_joined_with_db(self):
|
||||
self.is_relation_made.return_value = True
|
||||
|
||||
with self.assertRaises(Exception) as context:
|
||||
hooks.pgsql_db_joined()
|
||||
self.assertEqual(context.exception.message,
|
||||
'Attempting to associate a postgresql database when there '
|
||||
'is already associated a mysql one')
|
||||
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
def test_db_changed_missing_relation_data(self, configs):
|
||||
configs.complete_contexts = MagicMock()
|
||||
configs.complete_contexts.return_value = []
|
||||
hooks.db_changed()
|
||||
self.log.assert_called_with(
|
||||
'shared-db relation incomplete. Peer not ready?'
|
||||
)
|
||||
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
def test_postgresql_db_changed_missing_relation_data(self, configs):
|
||||
configs.complete_contexts = MagicMock()
|
||||
configs.complete_contexts.return_value = []
|
||||
hooks.pgsql_db_changed()
|
||||
self.log.assert_called_with(
|
||||
'pgsql-db relation incomplete. Peer not ready?'
|
||||
)
|
||||
|
||||
def _shared_db_test(self, configs):
|
||||
configs.complete_contexts = MagicMock()
|
||||
configs.complete_contexts.return_value = ['shared-db']
|
||||
configs.write = MagicMock()
|
||||
hooks.db_changed()
|
||||
|
||||
def _postgresql_db_test(self, configs):
|
||||
configs.complete_contexts = MagicMock()
|
||||
configs.complete_contexts.return_value = ['pgsql-db']
|
||||
configs.write = MagicMock()
|
||||
hooks.pgsql_db_changed()
|
119
unit_tests/test_utils.py
Normal file
119
unit_tests/test_utils.py
Normal file
@ -0,0 +1,119 @@
|
||||
import logging
|
||||
import os
|
||||
import unittest
|
||||
import yaml
|
||||
|
||||
from contextlib import contextmanager
|
||||
from mock import patch, MagicMock
|
||||
|
||||
|
||||
def load_config():
|
||||
'''Walk backwords from __file__ looking for config.yaml,
|
||||
load and return the 'options' section'
|
||||
'''
|
||||
config = None
|
||||
f = __file__
|
||||
while config is None:
|
||||
d = os.path.dirname(f)
|
||||
if os.path.isfile(os.path.join(d, 'config.yaml')):
|
||||
config = os.path.join(d, 'config.yaml')
|
||||
break
|
||||
f = d
|
||||
|
||||
if not config:
|
||||
logging.error('Could not find config.yaml in any parent directory '
|
||||
'of %s. ' % file)
|
||||
raise Exception
|
||||
|
||||
return yaml.safe_load(open(config).read())['options']
|
||||
|
||||
|
||||
def get_default_config():
|
||||
'''Load default charm config from config.yaml return as a dict.
|
||||
If no default is set in config.yaml, its value is None.
|
||||
'''
|
||||
default_config = {}
|
||||
config = load_config()
|
||||
for k, v in config.iteritems():
|
||||
if 'default' in v:
|
||||
default_config[k] = v['default']
|
||||
else:
|
||||
default_config[k] = None
|
||||
return default_config
|
||||
|
||||
|
||||
class CharmTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self, obj, patches):
|
||||
super(CharmTestCase, self).setUp()
|
||||
self.patches = patches
|
||||
self.obj = obj
|
||||
self.test_config = TestConfig()
|
||||
self.test_relation = TestRelation()
|
||||
self.patch_all()
|
||||
|
||||
def patch(self, method):
|
||||
_m = patch.object(self.obj, method)
|
||||
mock = _m.start()
|
||||
self.addCleanup(_m.stop)
|
||||
return mock
|
||||
|
||||
def patch_all(self):
|
||||
for method in self.patches:
|
||||
setattr(self, method, self.patch(method))
|
||||
|
||||
|
||||
class TestConfig(object):
|
||||
|
||||
def __init__(self):
|
||||
self.config = get_default_config()
|
||||
|
||||
def get(self, attr=None):
|
||||
if not attr:
|
||||
return self.get_all()
|
||||
try:
|
||||
return self.config[attr]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def get_all(self):
|
||||
return self.config
|
||||
|
||||
def set(self, attr, value):
|
||||
if attr not in self.config:
|
||||
raise KeyError
|
||||
self.config[attr] = value
|
||||
|
||||
|
||||
class TestRelation(object):
|
||||
|
||||
def __init__(self, relation_data={}):
|
||||
self.relation_data = relation_data
|
||||
|
||||
def set(self, relation_data):
|
||||
self.relation_data = relation_data
|
||||
|
||||
def get(self, attr=None, unit=None, rid=None):
|
||||
if attr is None:
|
||||
return self.relation_data
|
||||
elif attr in self.relation_data:
|
||||
return self.relation_data[attr]
|
||||
return None
|
||||
|
||||
|
||||
@contextmanager
|
||||
def patch_open():
|
||||
'''Patch open() to allow mocking both open() itself and the file that is
|
||||
yielded.
|
||||
Yields the mock for "open" and "file", respectively.
|
||||
'''
|
||||
mock_open = MagicMock(spec=open)
|
||||
mock_file = MagicMock(spec=file)
|
||||
|
||||
@contextmanager
|
||||
def stub_open(*args, **kwargs):
|
||||
mock_open(*args, **kwargs)
|
||||
yield mock_file
|
||||
|
||||
with patch('__builtin__.open', stub_open):
|
||||
yield mock_open, mock_file
|
Loading…
Reference in New Issue
Block a user