[hopem,r=]
Implement PKI token signing. Closes-Bug: 1309667
This commit is contained in:
commit
d0993565ca
@ -48,6 +48,9 @@ from charmhelpers.core.hookenv import (
|
||||
from charmhelpers.core.decorators import (
|
||||
retry_on_exception,
|
||||
)
|
||||
from charmhelpers.core.strutils import (
|
||||
bool_from_string,
|
||||
)
|
||||
|
||||
|
||||
class HAIncompleteConfig(Exception):
|
||||
@ -164,7 +167,8 @@ def https():
|
||||
.
|
||||
returns: boolean
|
||||
'''
|
||||
if config_get('use-https') == "yes":
|
||||
use_https = config_get('use-https')
|
||||
if use_https and bool_from_string(use_https):
|
||||
return True
|
||||
if config_get('ssl_cert') and config_get('ssl_key'):
|
||||
return True
|
||||
|
@ -71,16 +71,19 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
||||
services.append(this_service)
|
||||
use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
|
||||
'ceph-osd', 'ceph-radosgw']
|
||||
# Openstack subordinate charms do not expose an origin option as that
|
||||
# is controlled by the principle
|
||||
ignore = ['neutron-openvswitch']
|
||||
|
||||
if self.openstack:
|
||||
for svc in services:
|
||||
if svc['name'] not in use_source:
|
||||
if svc['name'] not in use_source + ignore:
|
||||
config = {'openstack-origin': self.openstack}
|
||||
self.d.configure(svc['name'], config)
|
||||
|
||||
if self.source:
|
||||
for svc in services:
|
||||
if svc['name'] in use_source:
|
||||
if svc['name'] in use_source and svc['name'] not in ignore:
|
||||
config = {'source': self.source}
|
||||
self.d.configure(svc['name'], config)
|
||||
|
||||
|
@ -17,8 +17,6 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
|
||||
|
||||
from charmhelpers.fetch import apt_install, apt_update
|
||||
from charmhelpers.core.hookenv import log
|
||||
|
||||
@ -29,6 +27,8 @@ except ImportError:
|
||||
apt_install('python-pip')
|
||||
from pip import main as pip_execute
|
||||
|
||||
__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
|
||||
|
||||
|
||||
def parse_options(given, available):
|
||||
"""Given a set of options, check if available"""
|
||||
|
@ -17,11 +17,11 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
|
||||
|
||||
import io
|
||||
import os
|
||||
|
||||
__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
|
||||
|
||||
|
||||
class Fstab(io.FileIO):
|
||||
"""This class extends file in order to implement a file reader/writer
|
||||
|
@ -191,11 +191,11 @@ def mkdir(path, owner='root', group='root', perms=0o555, force=False):
|
||||
|
||||
|
||||
def write_file(path, content, owner='root', group='root', perms=0o444):
|
||||
"""Create or overwrite a file with the contents of a string"""
|
||||
"""Create or overwrite a file with the contents of a byte string."""
|
||||
log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
|
||||
uid = pwd.getpwnam(owner).pw_uid
|
||||
gid = grp.getgrnam(group).gr_gid
|
||||
with open(path, 'w') as target:
|
||||
with open(path, 'wb') as target:
|
||||
os.fchown(target.fileno(), uid, gid)
|
||||
os.fchmod(target.fileno(), perms)
|
||||
target.write(content)
|
||||
@ -305,11 +305,11 @@ def restart_on_change(restart_map, stopstart=False):
|
||||
ceph_client_changed function.
|
||||
"""
|
||||
def wrap(f):
|
||||
def wrapped_f(*args):
|
||||
def wrapped_f(*args, **kwargs):
|
||||
checksums = {}
|
||||
for path in restart_map:
|
||||
checksums[path] = file_hash(path)
|
||||
f(*args)
|
||||
f(*args, **kwargs)
|
||||
restarts = []
|
||||
for path in restart_map:
|
||||
if checksums[path] != file_hash(path):
|
||||
@ -361,7 +361,7 @@ def list_nics(nic_type):
|
||||
ip_output = (line for line in ip_output if line)
|
||||
for line in ip_output:
|
||||
if line.split()[1].startswith(int_type):
|
||||
matched = re.search('.*: (bond[0-9]+\.[0-9]+)@.*', line)
|
||||
matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line)
|
||||
if matched:
|
||||
interface = matched.groups()[0]
|
||||
else:
|
||||
|
42
hooks/charmhelpers/core/strutils.py
Normal file
42
hooks/charmhelpers/core/strutils.py
Normal file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2014-2015 Canonical Limited.
|
||||
#
|
||||
# This file is part of charm-helpers.
|
||||
#
|
||||
# charm-helpers is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# charm-helpers is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import six
|
||||
|
||||
|
||||
def bool_from_string(value):
|
||||
"""Interpret string value as boolean.
|
||||
|
||||
Returns True if value translates to True otherwise False.
|
||||
"""
|
||||
if isinstance(value, six.string_types):
|
||||
value = six.text_type(value)
|
||||
else:
|
||||
msg = "Unable to interpret non-string value '%s' as boolean" % (value)
|
||||
raise ValueError(msg)
|
||||
|
||||
value = value.strip().lower()
|
||||
|
||||
if value in ['y', 'yes', 'true', 't']:
|
||||
return True
|
||||
elif value in ['n', 'no', 'false', 'f']:
|
||||
return False
|
||||
|
||||
msg = "Unable to interpret string value '%s' as boolean" % (value)
|
||||
raise ValueError(msg)
|
@ -17,8 +17,6 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
|
||||
|
||||
import yaml
|
||||
|
||||
from subprocess import check_call
|
||||
@ -29,6 +27,8 @@ from charmhelpers.core.hookenv import (
|
||||
ERROR,
|
||||
)
|
||||
|
||||
__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
|
||||
|
||||
|
||||
def create(sysctl_dict, sysctl_file):
|
||||
"""Creates a sysctl.conf file from a YAML associative array
|
||||
|
@ -21,7 +21,7 @@ from charmhelpers.core import hookenv
|
||||
|
||||
|
||||
def render(source, target, context, owner='root', group='root',
|
||||
perms=0o444, templates_dir=None):
|
||||
perms=0o444, templates_dir=None, encoding='UTF-8'):
|
||||
"""
|
||||
Render a template.
|
||||
|
||||
@ -64,5 +64,5 @@ def render(source, target, context, owner='root', group='root',
|
||||
level=hookenv.ERROR)
|
||||
raise e
|
||||
content = template.render(context)
|
||||
host.mkdir(os.path.dirname(target), owner, group)
|
||||
host.write_file(target, content, owner, group, perms)
|
||||
host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
|
||||
host.write_file(target, content.encode(encoding), owner, group, perms)
|
||||
|
477
hooks/charmhelpers/core/unitdata.py
Normal file
477
hooks/charmhelpers/core/unitdata.py
Normal file
@ -0,0 +1,477 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2014-2015 Canonical Limited.
|
||||
#
|
||||
# This file is part of charm-helpers.
|
||||
#
|
||||
# charm-helpers is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License version 3 as
|
||||
# published by the Free Software Foundation.
|
||||
#
|
||||
# charm-helpers is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
#
|
||||
# Authors:
|
||||
# Kapil Thangavelu <kapil.foss@gmail.com>
|
||||
#
|
||||
"""
|
||||
Intro
|
||||
-----
|
||||
|
||||
A simple way to store state in units. This provides a key value
|
||||
storage with support for versioned, transactional operation,
|
||||
and can calculate deltas from previous values to simplify unit logic
|
||||
when processing changes.
|
||||
|
||||
|
||||
Hook Integration
|
||||
----------------
|
||||
|
||||
There are several extant frameworks for hook execution, including
|
||||
|
||||
- charmhelpers.core.hookenv.Hooks
|
||||
- charmhelpers.core.services.ServiceManager
|
||||
|
||||
The storage classes are framework agnostic, one simple integration is
|
||||
via the HookData contextmanager. It will record the current hook
|
||||
execution environment (including relation data, config data, etc.),
|
||||
setup a transaction and allow easy access to the changes from
|
||||
previously seen values. One consequence of the integration is the
|
||||
reservation of particular keys ('rels', 'unit', 'env', 'config',
|
||||
'charm_revisions') for their respective values.
|
||||
|
||||
Here's a fully worked integration example using hookenv.Hooks::
|
||||
|
||||
from charmhelper.core import hookenv, unitdata
|
||||
|
||||
hook_data = unitdata.HookData()
|
||||
db = unitdata.kv()
|
||||
hooks = hookenv.Hooks()
|
||||
|
||||
@hooks.hook
|
||||
def config_changed():
|
||||
# Print all changes to configuration from previously seen
|
||||
# values.
|
||||
for changed, (prev, cur) in hook_data.conf.items():
|
||||
print('config changed', changed,
|
||||
'previous value', prev,
|
||||
'current value', cur)
|
||||
|
||||
# Get some unit specific bookeeping
|
||||
if not db.get('pkg_key'):
|
||||
key = urllib.urlopen('https://example.com/pkg_key').read()
|
||||
db.set('pkg_key', key)
|
||||
|
||||
# Directly access all charm config as a mapping.
|
||||
conf = db.getrange('config', True)
|
||||
|
||||
# Directly access all relation data as a mapping
|
||||
rels = db.getrange('rels', True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
with hook_data():
|
||||
hook.execute()
|
||||
|
||||
|
||||
A more basic integration is via the hook_scope context manager which simply
|
||||
manages transaction scope (and records hook name, and timestamp)::
|
||||
|
||||
>>> from unitdata import kv
|
||||
>>> db = kv()
|
||||
>>> with db.hook_scope('install'):
|
||||
... # do work, in transactional scope.
|
||||
... db.set('x', 1)
|
||||
>>> db.get('x')
|
||||
1
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Values are automatically json de/serialized to preserve basic typing
|
||||
and complex data struct capabilities (dicts, lists, ints, booleans, etc).
|
||||
|
||||
Individual values can be manipulated via get/set::
|
||||
|
||||
>>> kv.set('y', True)
|
||||
>>> kv.get('y')
|
||||
True
|
||||
|
||||
# We can set complex values (dicts, lists) as a single key.
|
||||
>>> kv.set('config', {'a': 1, 'b': True'})
|
||||
|
||||
# Also supports returning dictionaries as a record which
|
||||
# provides attribute access.
|
||||
>>> config = kv.get('config', record=True)
|
||||
>>> config.b
|
||||
True
|
||||
|
||||
|
||||
Groups of keys can be manipulated with update/getrange::
|
||||
|
||||
>>> kv.update({'z': 1, 'y': 2}, prefix="gui.")
|
||||
>>> kv.getrange('gui.', strip=True)
|
||||
{'z': 1, 'y': 2}
|
||||
|
||||
When updating values, its very helpful to understand which values
|
||||
have actually changed and how have they changed. The storage
|
||||
provides a delta method to provide for this::
|
||||
|
||||
>>> data = {'debug': True, 'option': 2}
|
||||
>>> delta = kv.delta(data, 'config.')
|
||||
>>> delta.debug.previous
|
||||
None
|
||||
>>> delta.debug.current
|
||||
True
|
||||
>>> delta
|
||||
{'debug': (None, True), 'option': (None, 2)}
|
||||
|
||||
Note the delta method does not persist the actual change, it needs to
|
||||
be explicitly saved via 'update' method::
|
||||
|
||||
>>> kv.update(data, 'config.')
|
||||
|
||||
Values modified in the context of a hook scope retain historical values
|
||||
associated to the hookname.
|
||||
|
||||
>>> with db.hook_scope('config-changed'):
|
||||
... db.set('x', 42)
|
||||
>>> db.gethistory('x')
|
||||
[(1, u'x', 1, u'install', u'2015-01-21T16:49:30.038372'),
|
||||
(2, u'x', 42, u'config-changed', u'2015-01-21T16:49:30.038786')]
|
||||
|
||||
"""
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import pprint
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
__author__ = 'Kapil Thangavelu <kapil.foss@gmail.com>'
|
||||
|
||||
|
||||
class Storage(object):
|
||||
"""Simple key value database for local unit state within charms.
|
||||
|
||||
Modifications are automatically committed at hook exit. That's
|
||||
currently regardless of exit code.
|
||||
|
||||
To support dicts, lists, integer, floats, and booleans values
|
||||
are automatically json encoded/decoded.
|
||||
"""
|
||||
def __init__(self, path=None):
|
||||
self.db_path = path
|
||||
if path is None:
|
||||
self.db_path = os.path.join(
|
||||
os.environ.get('CHARM_DIR', ''), '.unit-state.db')
|
||||
self.conn = sqlite3.connect('%s' % self.db_path)
|
||||
self.cursor = self.conn.cursor()
|
||||
self.revision = None
|
||||
self._closed = False
|
||||
self._init()
|
||||
|
||||
def close(self):
|
||||
if self._closed:
|
||||
return
|
||||
self.flush(False)
|
||||
self.cursor.close()
|
||||
self.conn.close()
|
||||
self._closed = True
|
||||
|
||||
def _scoped_query(self, stmt, params=None):
|
||||
if params is None:
|
||||
params = []
|
||||
return stmt, params
|
||||
|
||||
def get(self, key, default=None, record=False):
|
||||
self.cursor.execute(
|
||||
*self._scoped_query(
|
||||
'select data from kv where key=?', [key]))
|
||||
result = self.cursor.fetchone()
|
||||
if not result:
|
||||
return default
|
||||
if record:
|
||||
return Record(json.loads(result[0]))
|
||||
return json.loads(result[0])
|
||||
|
||||
def getrange(self, key_prefix, strip=False):
|
||||
stmt = "select key, data from kv where key like '%s%%'" % key_prefix
|
||||
self.cursor.execute(*self._scoped_query(stmt))
|
||||
result = self.cursor.fetchall()
|
||||
|
||||
if not result:
|
||||
return None
|
||||
if not strip:
|
||||
key_prefix = ''
|
||||
return dict([
|
||||
(k[len(key_prefix):], json.loads(v)) for k, v in result])
|
||||
|
||||
def update(self, mapping, prefix=""):
|
||||
for k, v in mapping.items():
|
||||
self.set("%s%s" % (prefix, k), v)
|
||||
|
||||
def unset(self, key):
|
||||
self.cursor.execute('delete from kv where key=?', [key])
|
||||
if self.revision and self.cursor.rowcount:
|
||||
self.cursor.execute(
|
||||
'insert into kv_revisions values (?, ?, ?)',
|
||||
[key, self.revision, json.dumps('DELETED')])
|
||||
|
||||
def set(self, key, value):
|
||||
serialized = json.dumps(value)
|
||||
|
||||
self.cursor.execute(
|
||||
'select data from kv where key=?', [key])
|
||||
exists = self.cursor.fetchone()
|
||||
|
||||
# Skip mutations to the same value
|
||||
if exists:
|
||||
if exists[0] == serialized:
|
||||
return value
|
||||
|
||||
if not exists:
|
||||
self.cursor.execute(
|
||||
'insert into kv (key, data) values (?, ?)',
|
||||
(key, serialized))
|
||||
else:
|
||||
self.cursor.execute('''
|
||||
update kv
|
||||
set data = ?
|
||||
where key = ?''', [serialized, key])
|
||||
|
||||
# Save
|
||||
if not self.revision:
|
||||
return value
|
||||
|
||||
self.cursor.execute(
|
||||
'select 1 from kv_revisions where key=? and revision=?',
|
||||
[key, self.revision])
|
||||
exists = self.cursor.fetchone()
|
||||
|
||||
if not exists:
|
||||
self.cursor.execute(
|
||||
'''insert into kv_revisions (
|
||||
revision, key, data) values (?, ?, ?)''',
|
||||
(self.revision, key, serialized))
|
||||
else:
|
||||
self.cursor.execute(
|
||||
'''
|
||||
update kv_revisions
|
||||
set data = ?
|
||||
where key = ?
|
||||
and revision = ?''',
|
||||
[serialized, key, self.revision])
|
||||
|
||||
return value
|
||||
|
||||
def delta(self, mapping, prefix):
|
||||
"""
|
||||
return a delta containing values that have changed.
|
||||
"""
|
||||
previous = self.getrange(prefix, strip=True)
|
||||
if not previous:
|
||||
pk = set()
|
||||
else:
|
||||
pk = set(previous.keys())
|
||||
ck = set(mapping.keys())
|
||||
delta = DeltaSet()
|
||||
|
||||
# added
|
||||
for k in ck.difference(pk):
|
||||
delta[k] = Delta(None, mapping[k])
|
||||
|
||||
# removed
|
||||
for k in pk.difference(ck):
|
||||
delta[k] = Delta(previous[k], None)
|
||||
|
||||
# changed
|
||||
for k in pk.intersection(ck):
|
||||
c = mapping[k]
|
||||
p = previous[k]
|
||||
if c != p:
|
||||
delta[k] = Delta(p, c)
|
||||
|
||||
return delta
|
||||
|
||||
@contextlib.contextmanager
|
||||
def hook_scope(self, name=""):
|
||||
"""Scope all future interactions to the current hook execution
|
||||
revision."""
|
||||
assert not self.revision
|
||||
self.cursor.execute(
|
||||
'insert into hooks (hook, date) values (?, ?)',
|
||||
(name or sys.argv[0],
|
||||
datetime.datetime.utcnow().isoformat()))
|
||||
self.revision = self.cursor.lastrowid
|
||||
try:
|
||||
yield self.revision
|
||||
self.revision = None
|
||||
except:
|
||||
self.flush(False)
|
||||
self.revision = None
|
||||
raise
|
||||
else:
|
||||
self.flush()
|
||||
|
||||
def flush(self, save=True):
|
||||
if save:
|
||||
self.conn.commit()
|
||||
elif self._closed:
|
||||
return
|
||||
else:
|
||||
self.conn.rollback()
|
||||
|
||||
def _init(self):
|
||||
self.cursor.execute('''
|
||||
create table if not exists kv (
|
||||
key text,
|
||||
data text,
|
||||
primary key (key)
|
||||
)''')
|
||||
self.cursor.execute('''
|
||||
create table if not exists kv_revisions (
|
||||
key text,
|
||||
revision integer,
|
||||
data text,
|
||||
primary key (key, revision)
|
||||
)''')
|
||||
self.cursor.execute('''
|
||||
create table if not exists hooks (
|
||||
version integer primary key autoincrement,
|
||||
hook text,
|
||||
date text
|
||||
)''')
|
||||
self.conn.commit()
|
||||
|
||||
def gethistory(self, key, deserialize=False):
|
||||
self.cursor.execute(
|
||||
'''
|
||||
select kv.revision, kv.key, kv.data, h.hook, h.date
|
||||
from kv_revisions kv,
|
||||
hooks h
|
||||
where kv.key=?
|
||||
and kv.revision = h.version
|
||||
''', [key])
|
||||
if deserialize is False:
|
||||
return self.cursor.fetchall()
|
||||
return map(_parse_history, self.cursor.fetchall())
|
||||
|
||||
def debug(self, fh=sys.stderr):
|
||||
self.cursor.execute('select * from kv')
|
||||
pprint.pprint(self.cursor.fetchall(), stream=fh)
|
||||
self.cursor.execute('select * from kv_revisions')
|
||||
pprint.pprint(self.cursor.fetchall(), stream=fh)
|
||||
|
||||
|
||||
def _parse_history(d):
|
||||
return (d[0], d[1], json.loads(d[2]), d[3],
|
||||
datetime.datetime.strptime(d[-1], "%Y-%m-%dT%H:%M:%S.%f"))
|
||||
|
||||
|
||||
class HookData(object):
|
||||
"""Simple integration for existing hook exec frameworks.
|
||||
|
||||
Records all unit information, and stores deltas for processing
|
||||
by the hook.
|
||||
|
||||
Sample::
|
||||
|
||||
from charmhelper.core import hookenv, unitdata
|
||||
|
||||
changes = unitdata.HookData()
|
||||
db = unitdata.kv()
|
||||
hooks = hookenv.Hooks()
|
||||
|
||||
@hooks.hook
|
||||
def config_changed():
|
||||
# View all changes to configuration
|
||||
for changed, (prev, cur) in changes.conf.items():
|
||||
print('config changed', changed,
|
||||
'previous value', prev,
|
||||
'current value', cur)
|
||||
|
||||
# Get some unit specific bookeeping
|
||||
if not db.get('pkg_key'):
|
||||
key = urllib.urlopen('https://example.com/pkg_key').read()
|
||||
db.set('pkg_key', key)
|
||||
|
||||
if __name__ == '__main__':
|
||||
with changes():
|
||||
hook.execute()
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.kv = kv()
|
||||
self.conf = None
|
||||
self.rels = None
|
||||
|
||||
@contextlib.contextmanager
|
||||
def __call__(self):
|
||||
from charmhelpers.core import hookenv
|
||||
hook_name = hookenv.hook_name()
|
||||
|
||||
with self.kv.hook_scope(hook_name):
|
||||
self._record_charm_version(hookenv.charm_dir())
|
||||
delta_config, delta_relation = self._record_hook(hookenv)
|
||||
yield self.kv, delta_config, delta_relation
|
||||
|
||||
def _record_charm_version(self, charm_dir):
|
||||
# Record revisions.. charm revisions are meaningless
|
||||
# to charm authors as they don't control the revision.
|
||||
# so logic dependnent on revision is not particularly
|
||||
# useful, however it is useful for debugging analysis.
|
||||
charm_rev = open(
|
||||
os.path.join(charm_dir, 'revision')).read().strip()
|
||||
charm_rev = charm_rev or '0'
|
||||
revs = self.kv.get('charm_revisions', [])
|
||||
if charm_rev not in revs:
|
||||
revs.append(charm_rev.strip() or '0')
|
||||
self.kv.set('charm_revisions', revs)
|
||||
|
||||
def _record_hook(self, hookenv):
|
||||
data = hookenv.execution_environment()
|
||||
self.conf = conf_delta = self.kv.delta(data['conf'], 'config')
|
||||
self.rels = rels_delta = self.kv.delta(data['rels'], 'rels')
|
||||
self.kv.set('env', data['env'])
|
||||
self.kv.set('unit', data['unit'])
|
||||
self.kv.set('relid', data.get('relid'))
|
||||
return conf_delta, rels_delta
|
||||
|
||||
|
||||
class Record(dict):
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __getattr__(self, k):
|
||||
if k in self:
|
||||
return self[k]
|
||||
raise AttributeError(k)
|
||||
|
||||
|
||||
class DeltaSet(Record):
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
Delta = collections.namedtuple('Delta', ['previous', 'current'])
|
||||
|
||||
|
||||
_KV = None
|
||||
|
||||
|
||||
def kv():
|
||||
global _KV
|
||||
if _KV is None:
|
||||
_KV = Storage()
|
||||
return _KV
|
@ -18,6 +18,16 @@ import os
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
from charmhelpers.fetch import (
|
||||
BaseFetchHandler,
|
||||
UnhandledSource
|
||||
)
|
||||
from charmhelpers.payload.archive import (
|
||||
get_archive_handler,
|
||||
extract,
|
||||
)
|
||||
from charmhelpers.core.host import mkdir, check_hash
|
||||
|
||||
import six
|
||||
if six.PY3:
|
||||
from urllib.request import (
|
||||
@ -35,16 +45,6 @@ else:
|
||||
)
|
||||
from urlparse import urlparse, urlunparse, parse_qs
|
||||
|
||||
from charmhelpers.fetch import (
|
||||
BaseFetchHandler,
|
||||
UnhandledSource
|
||||
)
|
||||
from charmhelpers.payload.archive import (
|
||||
get_archive_handler,
|
||||
extract,
|
||||
)
|
||||
from charmhelpers.core.host import mkdir, check_hash
|
||||
|
||||
|
||||
def splituser(host):
|
||||
'''urllib.splituser(), but six's support of this seems broken'''
|
||||
|
@ -32,7 +32,7 @@ except ImportError:
|
||||
apt_install("python-git")
|
||||
from git import Repo
|
||||
|
||||
from git.exc import GitCommandError
|
||||
from git.exc import GitCommandError # noqa E402
|
||||
|
||||
|
||||
class GitUrlFetchHandler(BaseFetchHandler):
|
||||
|
@ -18,9 +18,14 @@ from charmhelpers.contrib.hahelpers.cluster import (
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
log,
|
||||
DEBUG,
|
||||
INFO,
|
||||
)
|
||||
|
||||
from charmhelpers.core.strutils import (
|
||||
bool_from_string,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.hahelpers.apache import install_ca_cert
|
||||
|
||||
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
|
||||
@ -169,9 +174,8 @@ class KeystoneContext(context.OSContextGenerator):
|
||||
|
||||
def __call__(self):
|
||||
from keystone_utils import (
|
||||
api_port, set_admin_token,
|
||||
endpoint_url, resolve_address,
|
||||
PUBLIC, ADMIN
|
||||
api_port, set_admin_token, endpoint_url, resolve_address,
|
||||
PUBLIC, ADMIN, PKI_CERTS_DIR, SSH_USER, ensure_permissions,
|
||||
)
|
||||
ctxt = {}
|
||||
ctxt['token'] = set_admin_token(config('admin-token'))
|
||||
@ -179,8 +183,12 @@ class KeystoneContext(context.OSContextGenerator):
|
||||
singlenode_mode=True)
|
||||
ctxt['public_port'] = determine_api_port(api_port('keystone-public'),
|
||||
singlenode_mode=True)
|
||||
ctxt['debug'] = config('debug') in ['yes', 'true', 'True']
|
||||
ctxt['verbose'] = config('verbose') in ['yes', 'true', 'True']
|
||||
|
||||
debug = config('debug')
|
||||
ctxt['debug'] = debug and bool_from_string(debug)
|
||||
verbose = config('verbose')
|
||||
ctxt['verbose'] = verbose and bool_from_string(verbose)
|
||||
|
||||
ctxt['identity_backend'] = config('identity-backend')
|
||||
ctxt['assignment_backend'] = config('assignment-backend')
|
||||
if config('identity-backend') == 'ldap':
|
||||
@ -194,8 +202,37 @@ class KeystoneContext(context.OSContextGenerator):
|
||||
flags = context.config_flags_parser(ldap_flags)
|
||||
ctxt['ldap_config_flags'] = flags
|
||||
|
||||
if config('enable-pki') not in ['false', 'False', 'no', 'No']:
|
||||
enable_pki = config('enable-pki')
|
||||
enable_pkiz = config('enable-pkiz')
|
||||
if enable_pki and bool_from_string(enable_pki):
|
||||
ctxt['signing'] = True
|
||||
ctxt['token_provider'] = 'pki'
|
||||
elif enable_pkiz and bool_from_string(enable_pkiz):
|
||||
ctxt['token_provider'] = 'pkiz'
|
||||
|
||||
if 'token_provider' in ctxt:
|
||||
log("Configuring PKI token cert paths", level=DEBUG)
|
||||
certs = os.path.join(PKI_CERTS_DIR, 'certs')
|
||||
privates = os.path.join(PKI_CERTS_DIR, 'privates')
|
||||
for path in [PKI_CERTS_DIR, certs, privates]:
|
||||
perms = 0o755
|
||||
if not os.path.isdir(path):
|
||||
mkdir(path=path, owner=SSH_USER, group='keystone',
|
||||
perms=perms)
|
||||
else:
|
||||
# Ensure accessible by ssh user and group (for sync).
|
||||
ensure_permissions(path, user=SSH_USER,
|
||||
group='keystone', perms=perms)
|
||||
|
||||
signing_paths = {'certfile': os.path.join(certs,
|
||||
'signing_cert.pem'),
|
||||
'keyfile': os.path.join(privates,
|
||||
'signing_key.pem'),
|
||||
'ca_certs': os.path.join(certs, 'ca.pem'),
|
||||
'ca_key': os.path.join(certs, 'ca_key.pem')}
|
||||
|
||||
for key, val in signing_paths.iteritems():
|
||||
ctxt[key] = val
|
||||
|
||||
# Base endpoint URL's which are used in keystone responses
|
||||
# to unauthenticated requests to redirect clients to the
|
||||
@ -214,7 +251,7 @@ class KeystoneLoggingContext(context.OSContextGenerator):
|
||||
def __call__(self):
|
||||
ctxt = {}
|
||||
debug = config('debug')
|
||||
if debug and debug.lower() in ['yes', 'true']:
|
||||
if debug and bool_from_string(debug):
|
||||
ctxt['root_level'] = 'DEBUG'
|
||||
|
||||
return ctxt
|
||||
|
@ -4,7 +4,6 @@ import json
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import time
|
||||
|
||||
from subprocess import check_call
|
||||
|
||||
@ -18,6 +17,7 @@ from charmhelpers.core.hookenv import (
|
||||
log,
|
||||
local_unit,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
relation_get,
|
||||
@ -32,6 +32,10 @@ from charmhelpers.core.host import (
|
||||
restart_on_change,
|
||||
)
|
||||
|
||||
from charmhelpers.core.strutils import (
|
||||
bool_from_string,
|
||||
)
|
||||
|
||||
from charmhelpers.fetch import (
|
||||
apt_install, apt_update,
|
||||
filter_installed_packages
|
||||
@ -64,9 +68,12 @@ from keystone_utils import (
|
||||
CA_CERT_PATH,
|
||||
ensure_permissions,
|
||||
get_ssl_sync_request_units,
|
||||
is_str_true,
|
||||
is_ssl_cert_master,
|
||||
is_db_ready,
|
||||
clear_ssl_synced_units,
|
||||
is_db_initialised,
|
||||
is_pki_enabled,
|
||||
ensure_pki_cert_permissions,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.hahelpers.cluster import (
|
||||
@ -110,7 +117,7 @@ def install():
|
||||
|
||||
@hooks.hook('config-changed')
|
||||
@restart_on_change(restart_map())
|
||||
@synchronize_ca_if_changed()
|
||||
@synchronize_ca_if_changed(fatal=True)
|
||||
def config_changed():
|
||||
if config('prefer-ipv6'):
|
||||
setup_ipv6()
|
||||
@ -138,9 +145,13 @@ def config_changed():
|
||||
|
||||
save_script_rc()
|
||||
configure_https()
|
||||
|
||||
update_nrpe_config()
|
||||
CONFIGS.write_all()
|
||||
|
||||
if is_pki_enabled():
|
||||
initialise_pki()
|
||||
|
||||
# Update relations since SSL may have been configured. If we have peer
|
||||
# units we can rely on the sync to do this in cluster relation.
|
||||
if is_elected_leader(CLUSTER_RES) and not peer_units():
|
||||
@ -150,15 +161,28 @@ def config_changed():
|
||||
admin_relation_changed(rid)
|
||||
|
||||
# Ensure sync request is sent out (needed for upgrade to ssl from non-ssl)
|
||||
settings = {}
|
||||
append_ssl_sync_request(settings)
|
||||
if settings:
|
||||
for rid in relation_ids('cluster'):
|
||||
relation_set(relation_id=rid, relation_settings=settings)
|
||||
send_ssl_sync_request()
|
||||
|
||||
for r_id in relation_ids('ha'):
|
||||
ha_joined(relation_id=r_id)
|
||||
|
||||
|
||||
@synchronize_ca_if_changed(fatal=True)
|
||||
def initialise_pki():
|
||||
"""Create certs and keys required for PKI token signing.
|
||||
|
||||
NOTE: keystone.conf [signing] section must be up-to-date prior to
|
||||
executing this.
|
||||
"""
|
||||
if is_ssl_cert_master():
|
||||
log("Ensuring PKI token certs created", level=DEBUG)
|
||||
cmd = ['keystone-manage', 'pki_setup', '--keystone-user', 'keystone',
|
||||
'--keystone-group', 'keystone']
|
||||
check_call(cmd)
|
||||
|
||||
ensure_pki_cert_permissions()
|
||||
|
||||
|
||||
@hooks.hook('shared-db-relation-joined')
|
||||
def db_joined():
|
||||
if is_relation_made('pgsql-db'):
|
||||
@ -189,19 +213,25 @@ def pgsql_db_joined():
|
||||
relation_set(database=config('database'))
|
||||
|
||||
|
||||
def update_all_identity_relation_units():
|
||||
def update_all_identity_relation_units(check_db_ready=True):
|
||||
CONFIGS.write_all()
|
||||
try:
|
||||
migrate_database()
|
||||
except Exception as exc:
|
||||
log("Database initialisation failed (%s) - db not ready?" % (exc),
|
||||
level=WARNING)
|
||||
else:
|
||||
if check_db_ready and not is_db_ready():
|
||||
log('Allowed_units list provided and this unit not present',
|
||||
level=INFO)
|
||||
return
|
||||
|
||||
if not is_db_initialised():
|
||||
log("Database not yet initialised - deferring identity-relation "
|
||||
"updates", level=INFO)
|
||||
return
|
||||
|
||||
if is_elected_leader(CLUSTER_RES):
|
||||
ensure_initial_admin(config)
|
||||
log('Firing identity_changed hook for all related services.')
|
||||
for rid in relation_ids('identity-service'):
|
||||
for unit in related_units(rid):
|
||||
identity_changed(relation_id=rid, remote_unit=unit)
|
||||
|
||||
log('Firing identity_changed hook for all related services.')
|
||||
for rid in relation_ids('identity-service'):
|
||||
for unit in related_units(rid):
|
||||
identity_changed(relation_id=rid, remote_unit=unit)
|
||||
|
||||
|
||||
@synchronize_ca_if_changed(force=True)
|
||||
@ -222,11 +252,14 @@ def db_changed():
|
||||
# units acl entry has been added. So, if the db supports passing
|
||||
# a list of permitted units then check if we're in the list.
|
||||
if not is_db_ready(use_current_context=True):
|
||||
log('Allowed_units list provided and this unit not present')
|
||||
log('Allowed_units list provided and this unit not present',
|
||||
level=INFO)
|
||||
return
|
||||
|
||||
migrate_database()
|
||||
# Ensure any existing service entries are updated in the
|
||||
# new database backend
|
||||
update_all_identity_relation_units()
|
||||
# new database backend. Also avoid duplicate db ready check.
|
||||
update_all_identity_relation_units(check_db_ready=False)
|
||||
|
||||
|
||||
@hooks.hook('pgsql-db-relation-changed')
|
||||
@ -238,24 +271,35 @@ def pgsql_db_changed():
|
||||
else:
|
||||
CONFIGS.write(KEYSTONE_CONF)
|
||||
if is_elected_leader(CLUSTER_RES):
|
||||
if not is_db_ready(use_current_context=True):
|
||||
log('Allowed_units list provided and this unit not present',
|
||||
level=INFO)
|
||||
return
|
||||
|
||||
migrate_database()
|
||||
# Ensure any existing service entries are updated in the
|
||||
# new database backend
|
||||
update_all_identity_relation_units()
|
||||
# new database backend. Also avoid duplicate db ready check.
|
||||
update_all_identity_relation_units(check_db_ready=False)
|
||||
|
||||
|
||||
@hooks.hook('identity-service-relation-changed')
|
||||
@restart_on_change(restart_map())
|
||||
@synchronize_ca_if_changed()
|
||||
def identity_changed(relation_id=None, remote_unit=None):
|
||||
CONFIGS.write_all()
|
||||
|
||||
notifications = {}
|
||||
if is_elected_leader(CLUSTER_RES):
|
||||
|
||||
if not is_db_ready():
|
||||
log("identity-service-relation-changed hook fired before db "
|
||||
"ready - deferring until db ready", level=WARNING)
|
||||
return
|
||||
|
||||
if not is_db_initialised():
|
||||
log("Database not yet initialised - deferring identity-relation "
|
||||
"updates", level=INFO)
|
||||
return
|
||||
|
||||
add_service_to_keystone(relation_id, remote_unit)
|
||||
settings = relation_get(rid=relation_id, unit=remote_unit)
|
||||
service = settings.get('service', None)
|
||||
@ -283,15 +327,51 @@ def identity_changed(relation_id=None, remote_unit=None):
|
||||
send_notifications(notifications)
|
||||
|
||||
|
||||
def append_ssl_sync_request(settings):
|
||||
"""Add request to be synced to relation settings.
|
||||
def send_ssl_sync_request():
|
||||
"""Set sync request on cluster relation.
|
||||
|
||||
This will be consumed by cluster-relation-changed ssl master.
|
||||
Value set equals number of ssl configs currently enabled so that if they
|
||||
change, we ensure that certs are synced. This setting is consumed by
|
||||
cluster-relation-changed ssl master. We also clear the 'synced' set to
|
||||
guarantee that a sync will occur.
|
||||
|
||||
Note the we do nothing if the setting is already applied.
|
||||
"""
|
||||
if (is_str_true(config('use-https')) or
|
||||
is_str_true(config('https-service-endpoints'))):
|
||||
unit = local_unit().replace('/', '-')
|
||||
settings['ssl-sync-required-%s' % (unit)] = '1'
|
||||
unit = local_unit().replace('/', '-')
|
||||
count = 0
|
||||
|
||||
use_https = config('use-https')
|
||||
if use_https and bool_from_string(use_https):
|
||||
count += 1
|
||||
|
||||
https_service_endpoints = config('https-service-endpoints')
|
||||
if (https_service_endpoints and
|
||||
bool_from_string(https_service_endpoints)):
|
||||
count += 2
|
||||
|
||||
enable_pki = config('enable-pki')
|
||||
if enable_pki and bool_from_string(enable_pki):
|
||||
count += 3
|
||||
|
||||
enable_pkiz = config('enable-pkiz')
|
||||
if enable_pkiz and bool_from_string(enable_pkiz):
|
||||
count += 4
|
||||
|
||||
if count:
|
||||
key = 'ssl-sync-required-%s' % (unit)
|
||||
settings = {key: count}
|
||||
prev = 0
|
||||
rid = None
|
||||
for rid in relation_ids('cluster'):
|
||||
for unit in related_units(rid):
|
||||
_prev = relation_get(rid=rid, unit=unit, attribute=key) or 0
|
||||
if _prev and _prev > prev:
|
||||
prev = _prev
|
||||
|
||||
if rid and prev < count:
|
||||
clear_ssl_synced_units()
|
||||
log("Setting %s=%s" % (key, count), level=DEBUG)
|
||||
relation_set(relation_id=rid, relation_settings=settings)
|
||||
|
||||
|
||||
@hooks.hook('cluster-relation-joined')
|
||||
@ -314,9 +394,8 @@ def cluster_joined():
|
||||
private_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
|
||||
settings['private-address'] = private_addr
|
||||
|
||||
append_ssl_sync_request(settings)
|
||||
|
||||
relation_set(relation_settings=settings)
|
||||
send_ssl_sync_request()
|
||||
|
||||
|
||||
def apply_echo_filters(settings, echo_whitelist):
|
||||
@ -362,7 +441,7 @@ def cluster_changed():
|
||||
# NOTE(jamespage) re-echo passwords for peer storage
|
||||
echo_whitelist, overrides = \
|
||||
apply_echo_filters(settings, ['_passwd', 'identity-service:',
|
||||
'ssl-cert-master'])
|
||||
'ssl-cert-master', 'db-initialised'])
|
||||
log("Peer echo overrides: %s" % (overrides), level=DEBUG)
|
||||
relation_set(**overrides)
|
||||
if echo_whitelist:
|
||||
@ -379,6 +458,9 @@ def cluster_changed():
|
||||
synced_units = json.loads(synced_units)
|
||||
diff = set(units).symmetric_difference(set(synced_units))
|
||||
|
||||
if is_pki_enabled():
|
||||
initialise_pki()
|
||||
|
||||
if units and (not synced_units or diff):
|
||||
log("New peers joined and need syncing - %s" %
|
||||
(', '.join(units)), level=DEBUG)
|
||||
@ -455,10 +537,8 @@ def ha_changed():
|
||||
|
||||
clustered = relation_get('clustered')
|
||||
if clustered and is_elected_leader(CLUSTER_RES):
|
||||
ensure_initial_admin(config)
|
||||
log('Cluster configured, notifying other services and updating '
|
||||
'keystone endpoint configuration')
|
||||
|
||||
update_all_identity_relation_units()
|
||||
|
||||
|
||||
@ -509,7 +589,6 @@ def upgrade_charm():
|
||||
if is_elected_leader(CLUSTER_RES):
|
||||
log('Cluster leader - ensuring endpoint configuration is up to '
|
||||
'date', level=DEBUG)
|
||||
time.sleep(10)
|
||||
update_all_identity_relation_units()
|
||||
|
||||
|
||||
|
@ -113,15 +113,16 @@ CA_SINGLETON = []
|
||||
|
||||
|
||||
def init_ca(ca_dir, common_name, org_name=ORG_NAME, org_unit_name=ORG_UNIT):
|
||||
print 'Ensuring certificate authority exists at %s.' % ca_dir
|
||||
log('Ensuring certificate authority exists at %s.' % ca_dir, level=DEBUG)
|
||||
if not os.path.exists(ca_dir):
|
||||
print 'Initializing new certificate authority at %s' % ca_dir
|
||||
log('Initializing new certificate authority at %s' % ca_dir,
|
||||
level=DEBUG)
|
||||
os.mkdir(ca_dir)
|
||||
|
||||
for i in ['certs', 'crl', 'newcerts', 'private']:
|
||||
d = os.path.join(ca_dir, i)
|
||||
if not os.path.exists(d):
|
||||
print 'Creating %s.' % d
|
||||
log('Creating %s.' % d, level=DEBUG)
|
||||
os.mkdir(d)
|
||||
os.chmod(os.path.join(ca_dir, 'private'), 0o710)
|
||||
|
||||
@ -132,9 +133,11 @@ def init_ca(ca_dir, common_name, org_name=ORG_NAME, org_unit_name=ORG_UNIT):
|
||||
if not os.path.isfile(os.path.join(ca_dir, 'index.txt')):
|
||||
with open(os.path.join(ca_dir, 'index.txt'), 'wb') as out:
|
||||
out.write('')
|
||||
if not os.path.isfile(os.path.join(ca_dir, 'ca.cnf')):
|
||||
print 'Creating new CA config in %s' % ca_dir
|
||||
with open(os.path.join(ca_dir, 'ca.cnf'), 'wb') as out:
|
||||
|
||||
conf = os.path.join(ca_dir, 'ca.cnf')
|
||||
if not os.path.isfile(conf):
|
||||
log('Creating new CA config in %s' % ca_dir, level=DEBUG)
|
||||
with open(conf, 'wb') as out:
|
||||
out.write(CA_CONFIG % locals())
|
||||
|
||||
|
||||
@ -144,40 +147,42 @@ def root_ca_crt_key(ca_dir):
|
||||
key = os.path.join(ca_dir, 'private', 'cacert.key')
|
||||
for f in [crt, key]:
|
||||
if not os.path.isfile(f):
|
||||
print 'Missing %s, will re-initialize cert+key.' % f
|
||||
log('Missing %s, will re-initialize cert+key.' % f, level=DEBUG)
|
||||
init = True
|
||||
else:
|
||||
print 'Found %s.' % f
|
||||
log('Found %s.' % f, level=DEBUG)
|
||||
|
||||
if init:
|
||||
cmd = ['openssl', 'req', '-config', os.path.join(ca_dir, 'ca.cnf'),
|
||||
conf = os.path.join(ca_dir, 'ca.cnf')
|
||||
cmd = ['openssl', 'req', '-config', conf,
|
||||
'-x509', '-nodes', '-newkey', 'rsa', '-days', '21360',
|
||||
'-keyout', key, '-out', crt, '-outform', 'PEM']
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
return crt, key
|
||||
|
||||
|
||||
def intermediate_ca_csr_key(ca_dir):
|
||||
print 'Creating new intermediate CSR.'
|
||||
log('Creating new intermediate CSR.', level=DEBUG)
|
||||
key = os.path.join(ca_dir, 'private', 'cacert.key')
|
||||
csr = os.path.join(ca_dir, 'cacert.csr')
|
||||
cmd = ['openssl', 'req', '-config', os.path.join(ca_dir, 'ca.cnf'),
|
||||
'-sha1', '-newkey', 'rsa', '-nodes', '-keyout', key, '-out',
|
||||
csr, '-outform',
|
||||
'PEM']
|
||||
conf = os.path.join(ca_dir, 'ca.cnf')
|
||||
cmd = ['openssl', 'req', '-config', conf, '-sha1', '-newkey', 'rsa',
|
||||
'-nodes', '-keyout', key, '-out', csr, '-outform', 'PEM']
|
||||
subprocess.check_call(cmd)
|
||||
return csr, key
|
||||
|
||||
|
||||
def sign_int_csr(ca_dir, csr, common_name):
|
||||
print 'Signing certificate request %s.' % csr
|
||||
crt = os.path.join(ca_dir, 'certs',
|
||||
'%s.crt' % os.path.basename(csr).split('.')[0])
|
||||
log('Signing certificate request %s.' % csr, level=DEBUG)
|
||||
crt_name = os.path.basename(csr).split('.')[0]
|
||||
crt = os.path.join(ca_dir, 'certs', '%s.crt' % crt_name)
|
||||
subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name)
|
||||
cmd = ['openssl', 'ca', '-batch', '-config',
|
||||
os.path.join(ca_dir, 'ca.cnf'),
|
||||
'-extensions', 'ca_extensions', '-days', CA_EXPIRY, '-notext',
|
||||
'-in', csr, '-out', crt, '-subj', subj, '-batch']
|
||||
print ' '.join(cmd)
|
||||
conf = os.path.join(ca_dir, 'ca.cnf')
|
||||
cmd = ['openssl', 'ca', '-batch', '-config', conf, '-extensions',
|
||||
'ca_extensions', '-days', CA_EXPIRY, '-notext', '-in', csr, '-out',
|
||||
crt, '-subj', subj, '-batch']
|
||||
log("Executing: %s" % ' '.join(cmd), level=DEBUG)
|
||||
subprocess.check_call(cmd)
|
||||
return crt
|
||||
|
||||
@ -187,19 +192,20 @@ def init_root_ca(ca_dir, common_name):
|
||||
return root_ca_crt_key(ca_dir)
|
||||
|
||||
|
||||
def init_intermediate_ca(ca_dir, common_name, root_ca_dir,
|
||||
org_name=ORG_NAME, org_unit_name=ORG_UNIT):
|
||||
def init_intermediate_ca(ca_dir, common_name, root_ca_dir, org_name=ORG_NAME,
|
||||
org_unit_name=ORG_UNIT):
|
||||
init_ca(ca_dir, common_name)
|
||||
if not os.path.isfile(os.path.join(ca_dir, 'cacert.pem')):
|
||||
csr, key = intermediate_ca_csr_key(ca_dir)
|
||||
crt = sign_int_csr(root_ca_dir, csr, common_name)
|
||||
shutil.copy(crt, os.path.join(ca_dir, 'cacert.pem'))
|
||||
else:
|
||||
print 'Intermediate CA certificate already exists.'
|
||||
log('Intermediate CA certificate already exists.', level=DEBUG)
|
||||
|
||||
if not os.path.isfile(os.path.join(ca_dir, 'signing.cnf')):
|
||||
print 'Creating new signing config in %s' % ca_dir
|
||||
with open(os.path.join(ca_dir, 'signing.cnf'), 'wb') as out:
|
||||
conf = os.path.join(ca_dir, 'signing.cnf')
|
||||
if not os.path.isfile(conf):
|
||||
log('Creating new signing config in %s' % ca_dir, level=DEBUG)
|
||||
with open(conf, 'wb') as out:
|
||||
out.write(SIGNING_CONFIG % locals())
|
||||
|
||||
|
||||
@ -212,7 +218,7 @@ def create_certificate(ca_dir, service):
|
||||
key, '-out', csr, '-subj', subj]
|
||||
subprocess.check_call(cmd)
|
||||
crt = sign_int_csr(ca_dir, csr, common_name)
|
||||
print 'Signed new CSR, crt @ %s' % crt
|
||||
log('Signed new CSR, crt @ %s' % crt, level=DEBUG)
|
||||
return
|
||||
|
||||
|
||||
@ -221,13 +227,14 @@ def update_bundle(bundle_file, new_bundle):
|
||||
if os.path.isfile(bundle_file):
|
||||
current = open(bundle_file, 'r').read().strip()
|
||||
if new_bundle == current:
|
||||
print 'CA Bundle @ %s is up to date.' % bundle_file
|
||||
log('CA Bundle @ %s is up to date.' % bundle_file, level=DEBUG)
|
||||
return
|
||||
else:
|
||||
print 'Updating CA bundle @ %s.' % bundle_file
|
||||
|
||||
log('Updating CA bundle @ %s.' % bundle_file, level=DEBUG)
|
||||
|
||||
with open(bundle_file, 'wb') as out:
|
||||
out.write(new_bundle)
|
||||
|
||||
subprocess.check_call(['update-ca-certificates'])
|
||||
|
||||
|
||||
@ -250,15 +257,19 @@ def tar_directory(path):
|
||||
class JujuCA(object):
|
||||
|
||||
def __init__(self, name, ca_dir, root_ca_dir, user, group):
|
||||
root_crt, root_key = init_root_ca(root_ca_dir,
|
||||
'%s Certificate Authority' % name)
|
||||
init_intermediate_ca(ca_dir,
|
||||
'%s Intermediate Certificate Authority' % name,
|
||||
root_ca_dir)
|
||||
# Root CA
|
||||
cn = '%s Certificate Authority' % name
|
||||
root_crt, root_key = init_root_ca(root_ca_dir, cn)
|
||||
# Intermediate CA
|
||||
cn = '%s Intermediate Certificate Authority' % name
|
||||
init_intermediate_ca(ca_dir, cn, root_ca_dir)
|
||||
|
||||
# Create dirs
|
||||
cmd = ['chown', '-R', '%s.%s' % (user, group), ca_dir]
|
||||
subprocess.check_call(cmd)
|
||||
cmd = ['chown', '-R', '%s.%s' % (user, group), root_ca_dir]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
self.ca_dir = ca_dir
|
||||
self.root_ca_dir = root_ca_dir
|
||||
self.user = user
|
||||
@ -268,8 +279,8 @@ class JujuCA(object):
|
||||
def _sign_csr(self, csr, service, common_name):
|
||||
subj = '/O=%s/OU=%s/CN=%s' % (ORG_NAME, ORG_UNIT, common_name)
|
||||
crt = os.path.join(self.ca_dir, 'certs', '%s.crt' % common_name)
|
||||
cmd = ['openssl', 'ca', '-config',
|
||||
os.path.join(self.ca_dir, 'signing.cnf'), '-extensions',
|
||||
conf = os.path.join(self.ca_dir, 'signing.cnf')
|
||||
cmd = ['openssl', 'ca', '-config', conf, '-extensions',
|
||||
'req_extensions', '-days', '365', '-notext', '-in', csr,
|
||||
'-out', crt, '-batch', '-subj', subj]
|
||||
subprocess.check_call(cmd)
|
||||
@ -288,10 +299,16 @@ class JujuCA(object):
|
||||
log('Signed new CSR, crt @ %s' % crt, level=DEBUG)
|
||||
return crt, key
|
||||
|
||||
def get_key_path(self, cn):
|
||||
return os.path.join(self.ca_dir, 'certs', '%s.key' % cn)
|
||||
|
||||
def get_cert_path(self, cn):
|
||||
return os.path.join(self.ca_dir, 'certs', '%s.crt' % cn)
|
||||
|
||||
def get_cert_and_key(self, common_name):
|
||||
log('Getting certificate and key for %s.' % common_name, level=DEBUG)
|
||||
keypath = os.path.join(self.ca_dir, 'certs', '%s.key' % common_name)
|
||||
crtpath = os.path.join(self.ca_dir, 'certs', '%s.crt' % common_name)
|
||||
keypath = self.get_key_path(common_name)
|
||||
crtpath = self.get_cert_path(common_name)
|
||||
if os.path.isfile(crtpath):
|
||||
log('Found existing certificate for %s.' % common_name,
|
||||
level=DEBUG)
|
||||
@ -324,8 +341,24 @@ class JujuCA(object):
|
||||
crt, key = self._create_certificate(common_name, common_name)
|
||||
return open(crt, 'r').read(), open(key, 'r').read()
|
||||
|
||||
@property
|
||||
def ca_cert_path(self):
|
||||
return os.path.join(self.ca_dir, 'cacert.pem')
|
||||
|
||||
@property
|
||||
def ca_key_path(self):
|
||||
return os.path.join(self.ca_dir, 'private', 'cacert.key')
|
||||
|
||||
@property
|
||||
def root_ca_cert_path(self):
|
||||
return os.path.join(self.root_ca_dir, 'cacert.pem')
|
||||
|
||||
@property
|
||||
def root_ca_key_path(self):
|
||||
return os.path.join(self.root_ca_dir, 'private', 'cacert.key')
|
||||
|
||||
def get_ca_bundle(self):
|
||||
int_cert = open(os.path.join(self.ca_dir, 'cacert.pem')).read()
|
||||
root_cert = open(os.path.join(self.root_ca_dir, 'cacert.pem')).read()
|
||||
int_cert = open(self.ca_cert_path).read()
|
||||
root_cert = open(self.root_ca_cert_path).read()
|
||||
# NOTE: ordering of certs in bundle matters!
|
||||
return int_cert + root_cert
|
||||
|
@ -21,7 +21,6 @@ from charmhelpers.contrib.hahelpers.cluster import(
|
||||
determine_api_port,
|
||||
https,
|
||||
peer_units,
|
||||
oldest_peer,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.openstack import context, templating
|
||||
@ -49,6 +48,10 @@ from charmhelpers.core.host import (
|
||||
write_file,
|
||||
)
|
||||
|
||||
from charmhelpers.core.strutils import (
|
||||
bool_from_string,
|
||||
)
|
||||
|
||||
import charmhelpers.contrib.unison as unison
|
||||
|
||||
from charmhelpers.core.decorators import (
|
||||
@ -134,10 +137,13 @@ APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf'
|
||||
APACHE_SSL_DIR = '/etc/apache2/ssl/keystone'
|
||||
SYNC_FLAGS_DIR = '/var/lib/keystone/juju_sync_flags/'
|
||||
SSL_DIR = '/var/lib/keystone/juju_ssl/'
|
||||
PKI_CERTS_DIR = os.path.join(SSL_DIR, 'pki')
|
||||
SSL_CA_NAME = 'Ubuntu Cloud'
|
||||
CLUSTER_RES = 'grp_ks_vips'
|
||||
SSH_USER = 'juju_keystone'
|
||||
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
|
||||
SSL_SYNC_SEMAPHORE = threading.Semaphore()
|
||||
SSL_DIRS = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]
|
||||
|
||||
BASE_RESOURCE_MAP = OrderedDict([
|
||||
(KEYSTONE_CONF, {
|
||||
@ -169,8 +175,6 @@ BASE_RESOURCE_MAP = OrderedDict([
|
||||
}),
|
||||
])
|
||||
|
||||
CA_CERT_PATH = '/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt'
|
||||
|
||||
valid_services = {
|
||||
"nova": {
|
||||
"type": "compute",
|
||||
@ -227,18 +231,18 @@ valid_services = {
|
||||
}
|
||||
|
||||
|
||||
def is_str_true(value):
|
||||
if value and value.lower() in ['true', 'yes']:
|
||||
return True
|
||||
|
||||
return False
|
||||
def ensure_pki_cert_permissions():
|
||||
perms = 0o755
|
||||
# Ensure accessible by unison user and group (for sync).
|
||||
for path in glob.glob("%s/*" % PKI_CERTS_DIR):
|
||||
ensure_permissions(path, user=SSH_USER, group='keystone', perms=perms,
|
||||
recurse=True)
|
||||
|
||||
|
||||
def resource_map():
|
||||
'''
|
||||
Dynamically generate a map of resources that will be managed for a single
|
||||
hook execution.
|
||||
'''
|
||||
"""Dynamically generate a map of resources that will be managed for a
|
||||
single hook execution.
|
||||
"""
|
||||
resource_map = deepcopy(BASE_RESOURCE_MAP)
|
||||
|
||||
if os.path.exists('/etc/apache2/conf-available'):
|
||||
@ -264,7 +268,7 @@ def restart_map():
|
||||
|
||||
|
||||
def services():
|
||||
''' Returns a list of services associate with this charm '''
|
||||
"""Returns a list of services associate with this charm"""
|
||||
_services = []
|
||||
for v in restart_map().values():
|
||||
_services = _services + v
|
||||
@ -272,7 +276,7 @@ def services():
|
||||
|
||||
|
||||
def determine_ports():
|
||||
'''Assemble a list of API ports for services we are managing'''
|
||||
"""Assemble a list of API ports for services we are managing"""
|
||||
ports = [config('admin-port'), config('service-port')]
|
||||
return list(set(ports))
|
||||
|
||||
@ -319,11 +323,36 @@ def do_openstack_upgrade(configs):
|
||||
configs.write_all()
|
||||
|
||||
if is_elected_leader(CLUSTER_RES):
|
||||
migrate_database()
|
||||
if is_db_ready():
|
||||
migrate_database()
|
||||
else:
|
||||
log("Database not ready - deferring to shared-db relation",
|
||||
level=INFO)
|
||||
return
|
||||
|
||||
|
||||
def set_db_initialised():
|
||||
for rid in relation_ids('cluster'):
|
||||
relation_set(relation_settings={'db-initialised': 'True'},
|
||||
relation_id=rid)
|
||||
|
||||
|
||||
def is_db_initialised():
|
||||
for rid in relation_ids('cluster'):
|
||||
units = related_units(rid) + [local_unit()]
|
||||
for unit in units:
|
||||
db_initialised = relation_get(attribute='db-initialised',
|
||||
unit=unit, rid=rid)
|
||||
if db_initialised:
|
||||
log("Database is initialised", level=DEBUG)
|
||||
return True
|
||||
|
||||
log("Database is NOT initialised", level=DEBUG)
|
||||
return False
|
||||
|
||||
|
||||
def migrate_database():
|
||||
'''Runs keystone-manage to initialize a new database or migrate existing'''
|
||||
"""Runs keystone-manage to initialize a new database or migrate existing"""
|
||||
log('Migrating the keystone database.', level=INFO)
|
||||
service_stop('keystone')
|
||||
# NOTE(jamespage) > icehouse creates a log file as root so use
|
||||
@ -333,12 +362,13 @@ def migrate_database():
|
||||
subprocess.check_output(cmd)
|
||||
service_start('keystone')
|
||||
time.sleep(10)
|
||||
|
||||
set_db_initialised()
|
||||
|
||||
# OLD
|
||||
|
||||
|
||||
def get_local_endpoint():
|
||||
""" Returns the URL for the local end-point bypassing haproxy/ssl """
|
||||
"""Returns the URL for the local end-point bypassing haproxy/ssl"""
|
||||
if config('prefer-ipv6'):
|
||||
ipv6_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
|
||||
endpoint_url = 'http://[%s]:{}/v2.0/' % ipv6_addr
|
||||
@ -439,7 +469,7 @@ def create_endpoint_template(region, service, publicurl, adminurl,
|
||||
|
||||
|
||||
def create_tenant(name):
|
||||
""" creates a tenant if it does not already exist """
|
||||
"""Creates a tenant if it does not already exist"""
|
||||
import manager
|
||||
manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
|
||||
token=get_admin_token())
|
||||
@ -453,7 +483,7 @@ def create_tenant(name):
|
||||
|
||||
|
||||
def create_user(name, password, tenant):
|
||||
""" creates a user if it doesn't already exist, as a member of tenant """
|
||||
"""Creates a user if it doesn't already exist, as a member of tenant"""
|
||||
import manager
|
||||
manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
|
||||
token=get_admin_token())
|
||||
@ -472,7 +502,7 @@ def create_user(name, password, tenant):
|
||||
|
||||
|
||||
def create_role(name, user=None, tenant=None):
|
||||
""" creates a role if it doesn't already exist. grants role to user """
|
||||
"""Creates a role if it doesn't already exist. grants role to user"""
|
||||
import manager
|
||||
manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
|
||||
token=get_admin_token())
|
||||
@ -499,7 +529,7 @@ def create_role(name, user=None, tenant=None):
|
||||
|
||||
|
||||
def grant_role(user, role, tenant):
|
||||
"""grant user+tenant a specific role"""
|
||||
"""Grant user and tenant a specific role"""
|
||||
import manager
|
||||
manager = manager.KeystoneManager(endpoint=get_local_endpoint(),
|
||||
token=get_admin_token())
|
||||
@ -646,7 +676,7 @@ def load_stored_passwords(path=SERVICE_PASSWD_PATH):
|
||||
|
||||
|
||||
def _migrate_service_passwords():
|
||||
''' Migrate on-disk service passwords to peer storage '''
|
||||
"""Migrate on-disk service passwords to peer storage"""
|
||||
if os.path.exists(SERVICE_PASSWD_PATH):
|
||||
log('Migrating on-disk stored passwords to peer storage')
|
||||
creds = load_stored_passwords()
|
||||
@ -666,11 +696,25 @@ def get_service_password(service_username):
|
||||
return passwd
|
||||
|
||||
|
||||
def ensure_permissions(path, user=None, group=None, perms=None):
|
||||
def ensure_permissions(path, user=None, group=None, perms=None, recurse=False,
|
||||
maxdepth=50):
|
||||
"""Set chownand chmod for path
|
||||
|
||||
Note that -1 for uid or gid result in no change.
|
||||
"""
|
||||
if recurse:
|
||||
if not maxdepth:
|
||||
log("Max recursion depth reached - skipping further recursion")
|
||||
return
|
||||
|
||||
paths = glob.glob("%s/*" % (path))
|
||||
if len(paths) > 1:
|
||||
for path in paths:
|
||||
ensure_permissions(path, user=user, group=group, perms=perms,
|
||||
recurse=recurse, maxdepth=maxdepth - 1)
|
||||
|
||||
return
|
||||
|
||||
if user:
|
||||
uid = pwd.getpwnam(user).pw_uid
|
||||
else:
|
||||
@ -764,14 +808,27 @@ def create_peer_actions(actions):
|
||||
def unison_sync(paths_to_sync):
|
||||
"""Do unison sync and retry a few times if it fails since peers may not be
|
||||
ready for sync.
|
||||
|
||||
Returns list of synced units or None if one or more peers was not synced.
|
||||
"""
|
||||
log('Synchronizing CA (%s) to all peers.' % (', '.join(paths_to_sync)),
|
||||
level=INFO)
|
||||
keystone_gid = grp.getgrnam('keystone').gr_gid
|
||||
|
||||
# NOTE(dosaboy): This will sync to all peers who have already provided
|
||||
# their ssh keys. If any existing peers have not provided their keys yet,
|
||||
# they will be silently ignored.
|
||||
unison.sync_to_peers(peer_interface='cluster', paths=paths_to_sync,
|
||||
user=SSH_USER, verbose=True, gid=keystone_gid,
|
||||
fatal=True)
|
||||
|
||||
synced_units = peer_units()
|
||||
if len(unison.collect_authed_hosts('cluster')) != len(synced_units):
|
||||
log("Not all peer units synced due to missing public keys", level=INFO)
|
||||
return None
|
||||
else:
|
||||
return synced_units
|
||||
|
||||
|
||||
def get_ssl_sync_request_units():
|
||||
"""Get list of units that have requested to be synced.
|
||||
@ -791,20 +848,31 @@ def get_ssl_sync_request_units():
|
||||
return units
|
||||
|
||||
|
||||
def is_ssl_cert_master():
|
||||
def is_ssl_cert_master(votes=None):
|
||||
"""Return True if this unit is ssl cert master."""
|
||||
master = None
|
||||
for rid in relation_ids('cluster'):
|
||||
master = relation_get(attribute='ssl-cert-master', rid=rid,
|
||||
unit=local_unit())
|
||||
|
||||
return master == local_unit()
|
||||
if master == local_unit():
|
||||
votes = votes or get_ssl_cert_master_votes()
|
||||
if not peer_units() or (len(votes) == 1 and master in votes):
|
||||
return True
|
||||
|
||||
log("Did not get consensus from peers on who is ssl-cert-master "
|
||||
"(%s)" % (votes), level=INFO)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def is_ssl_enabled():
|
||||
# Don't do anything if we are not in ssl/https mode
|
||||
if (is_str_true(config('use-https')) or
|
||||
is_str_true(config('https-service-endpoints'))):
|
||||
use_https = config('use-https')
|
||||
https_service_endpoints = config('https-service-endpoints')
|
||||
if ((use_https and bool_from_string(use_https)) or
|
||||
(https_service_endpoints and
|
||||
bool_from_string(https_service_endpoints)) or
|
||||
is_pki_enabled()):
|
||||
log("SSL/HTTPS is enabled", level=DEBUG)
|
||||
return True
|
||||
|
||||
@ -812,7 +880,21 @@ def is_ssl_enabled():
|
||||
return True
|
||||
|
||||
|
||||
def ensure_ssl_cert_master(use_oldest_peer=False):
|
||||
def get_ssl_cert_master_votes():
|
||||
"""Returns a list of unique votes."""
|
||||
votes = []
|
||||
# Gather election results from peers. These will need to be consistent.
|
||||
for rid in relation_ids('cluster'):
|
||||
for unit in related_units(rid):
|
||||
m = relation_get(rid=rid, unit=unit,
|
||||
attribute='ssl-cert-master')
|
||||
if m is not None:
|
||||
votes.append(m)
|
||||
|
||||
return list(set(votes))
|
||||
|
||||
|
||||
def ensure_ssl_cert_master():
|
||||
"""Ensure that an ssl cert master has been elected.
|
||||
|
||||
Normally the cluster leader will take control but we allow for this to be
|
||||
@ -822,31 +904,19 @@ def ensure_ssl_cert_master(use_oldest_peer=False):
|
||||
if not is_ssl_enabled():
|
||||
return False
|
||||
|
||||
elect = False
|
||||
peers = peer_units()
|
||||
master_override = False
|
||||
if use_oldest_peer:
|
||||
elect = oldest_peer(peers)
|
||||
else:
|
||||
elect = is_elected_leader(CLUSTER_RES)
|
||||
elect = is_elected_leader(CLUSTER_RES)
|
||||
|
||||
# If no peers we allow this unit to elect itsef as master and do
|
||||
# sync immediately.
|
||||
if not peers and not is_ssl_cert_master():
|
||||
if not peer_units():
|
||||
elect = True
|
||||
master_override = True
|
||||
|
||||
if elect:
|
||||
masters = []
|
||||
for rid in relation_ids('cluster'):
|
||||
for unit in related_units(rid):
|
||||
m = relation_get(rid=rid, unit=unit,
|
||||
attribute='ssl-cert-master')
|
||||
if m is not None:
|
||||
masters.append(m)
|
||||
|
||||
votes = get_ssl_cert_master_votes()
|
||||
# We expect all peers to echo this setting
|
||||
if not masters or 'unknown' in masters:
|
||||
if not votes or 'unknown' in votes:
|
||||
log("Notifying peers this unit is ssl-cert-master", level=INFO)
|
||||
for rid in relation_ids('cluster'):
|
||||
settings = {'ssl-cert-master': local_unit()}
|
||||
@ -855,10 +925,11 @@ def ensure_ssl_cert_master(use_oldest_peer=False):
|
||||
# Return now and wait for cluster-relation-changed (peer_echo) for
|
||||
# sync.
|
||||
return master_override
|
||||
elif len(set(masters)) != 1 and local_unit() not in masters:
|
||||
log("Did not get consensus from peers on who is ssl-cert-master "
|
||||
"(%s) - waiting for current master to release before "
|
||||
"self-electing" % (masters), level=INFO)
|
||||
elif not is_ssl_cert_master(votes):
|
||||
if not master_override:
|
||||
log("Conscensus not reached - current master will need to "
|
||||
"release", level=INFO)
|
||||
|
||||
return master_override
|
||||
|
||||
if not is_ssl_cert_master():
|
||||
@ -868,6 +939,16 @@ def ensure_ssl_cert_master(use_oldest_peer=False):
|
||||
return True
|
||||
|
||||
|
||||
def is_pki_enabled():
|
||||
enable_pki = config('enable-pki')
|
||||
enable_pkiz = config('enable-pkiz')
|
||||
if (enable_pki and bool_from_string(enable_pki) or
|
||||
enable_pkiz and bool_from_string(enable_pkiz)):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def synchronize_ca(fatal=False):
|
||||
"""Broadcast service credentials to peers.
|
||||
|
||||
@ -883,19 +964,26 @@ def synchronize_ca(fatal=False):
|
||||
"""
|
||||
paths_to_sync = [SYNC_FLAGS_DIR]
|
||||
|
||||
if is_str_true(config('https-service-endpoints')):
|
||||
if bool_from_string(config('https-service-endpoints')):
|
||||
log("Syncing all endpoint certs since https-service-endpoints=True",
|
||||
level=DEBUG)
|
||||
paths_to_sync.append(SSL_DIR)
|
||||
paths_to_sync.append(APACHE_SSL_DIR)
|
||||
paths_to_sync.append(CA_CERT_PATH)
|
||||
elif is_str_true(config('use-https')):
|
||||
|
||||
if bool_from_string(config('use-https')):
|
||||
log("Syncing keystone-endpoint certs since use-https=True",
|
||||
level=DEBUG)
|
||||
paths_to_sync.append(SSL_DIR)
|
||||
paths_to_sync.append(APACHE_SSL_DIR)
|
||||
paths_to_sync.append(CA_CERT_PATH)
|
||||
|
||||
if is_pki_enabled():
|
||||
log("Syncing token certs", level=DEBUG)
|
||||
paths_to_sync.append(PKI_CERTS_DIR)
|
||||
|
||||
# Ensure unique
|
||||
paths_to_sync = list(set(paths_to_sync))
|
||||
|
||||
if not paths_to_sync:
|
||||
log("Nothing to sync - skipping", level=DEBUG)
|
||||
return {}
|
||||
@ -908,8 +996,7 @@ def synchronize_ca(fatal=False):
|
||||
create_peer_service_actions('restart', ['apache2'])
|
||||
create_peer_actions(['update-ca-certificates'])
|
||||
|
||||
# Format here needs to match that used when peers request sync
|
||||
synced_units = [unit.replace('/', '-') for unit in peer_units()]
|
||||
cluster_rel_settings = {}
|
||||
|
||||
retries = 3
|
||||
while True:
|
||||
@ -918,12 +1005,17 @@ def synchronize_ca(fatal=False):
|
||||
update_hash_from_path(hash1, path)
|
||||
|
||||
try:
|
||||
unison_sync(paths_to_sync)
|
||||
except:
|
||||
synced_units = unison_sync(paths_to_sync)
|
||||
if synced_units:
|
||||
# Format here needs to match that used when peers request sync
|
||||
synced_units = [u.replace('/', '-') for u in synced_units]
|
||||
cluster_rel_settings['ssl-synced-units'] = \
|
||||
json.dumps(synced_units)
|
||||
except Exception as exc:
|
||||
if fatal:
|
||||
raise
|
||||
else:
|
||||
log("Sync failed but fatal=False", level=INFO)
|
||||
log("Sync failed but fatal=False - %s" % (exc), level=INFO)
|
||||
return {}
|
||||
|
||||
hash2 = hashlib.sha256()
|
||||
@ -947,10 +1039,22 @@ def synchronize_ca(fatal=False):
|
||||
hash = hash1.hexdigest()
|
||||
log("Sending restart-services-trigger=%s to all peers" % (hash),
|
||||
level=DEBUG)
|
||||
cluster_rel_settings['restart-services-trigger'] = hash
|
||||
|
||||
log("Sync complete", level=DEBUG)
|
||||
return {'restart-services-trigger': hash,
|
||||
'ssl-synced-units': json.dumps(synced_units)}
|
||||
return cluster_rel_settings
|
||||
|
||||
|
||||
def clear_ssl_synced_units():
|
||||
"""Clear the 'synced' units record on the cluster relation.
|
||||
|
||||
If new unit sync reauests are set this will ensure that a sync occurs when
|
||||
the sync master receives the requests.
|
||||
"""
|
||||
log("Clearing ssl sync units", level=DEBUG)
|
||||
for rid in relation_ids('cluster'):
|
||||
relation_set(relation_id=rid,
|
||||
relation_settings={'ssl-synced-units': None})
|
||||
|
||||
|
||||
def update_hash_from_path(hash, path, recurse_depth=10):
|
||||
@ -992,16 +1096,14 @@ def synchronize_ca_if_changed(force=False, fatal=False):
|
||||
|
||||
peer_settings = {}
|
||||
if not force:
|
||||
ssl_dirs = [SSL_DIR, APACHE_SSL_DIR, CA_CERT_PATH]
|
||||
|
||||
hash1 = hashlib.sha256()
|
||||
for path in ssl_dirs:
|
||||
for path in SSL_DIRS:
|
||||
update_hash_from_path(hash1, path)
|
||||
|
||||
ret = f(*args, **kwargs)
|
||||
|
||||
hash2 = hashlib.sha256()
|
||||
for path in ssl_dirs:
|
||||
for path in SSL_DIRS:
|
||||
update_hash_from_path(hash2, path)
|
||||
|
||||
if hash1.hexdigest() != hash2.hexdigest():
|
||||
@ -1037,13 +1139,18 @@ def synchronize_ca_if_changed(force=False, fatal=False):
|
||||
|
||||
|
||||
def get_ca(user='keystone', group='keystone'):
|
||||
"""
|
||||
Initialize a new CA object if one hasn't already been loaded.
|
||||
"""Initialize a new CA object if one hasn't already been loaded.
|
||||
|
||||
This will create a new CA or load an existing one.
|
||||
"""
|
||||
if not ssl.CA_SINGLETON:
|
||||
# Ensure unsion read/writable
|
||||
perms = 0o755
|
||||
if not os.path.isdir(SSL_DIR):
|
||||
os.mkdir(SSL_DIR)
|
||||
mkdir(SSL_DIR, SSH_USER, 'keystone', perms)
|
||||
else:
|
||||
ensure_permissions(SSL_DIR, user=SSH_USER, group='keystone',
|
||||
perms=perms)
|
||||
|
||||
d_name = '_'.join(SSL_CA_NAME.lower().split(' '))
|
||||
ca = ssl.JujuCA(name=SSL_CA_NAME, user=user, group=group,
|
||||
@ -1058,11 +1165,11 @@ def get_ca(user='keystone', group='keystone'):
|
||||
'%s' % SSL_DIR])
|
||||
subprocess.check_output(['chmod', '-R', 'g+rwx', '%s' % SSL_DIR])
|
||||
|
||||
# Ensure a master has been elected and prefer this unit. Note that we
|
||||
# prefer oldest peer as predicate since this action i normally only
|
||||
# performed once at deploy time when the oldest peer should be the
|
||||
# first to be ready.
|
||||
ensure_ssl_cert_master(use_oldest_peer=True)
|
||||
# Ensure a master is elected. This should cover the following cases:
|
||||
# * single unit == 'oldest' unit is elected as master
|
||||
# * multi unit + not clustered == 'oldest' unit is elcted as master
|
||||
# * multi unit + clustered == cluster leader is elected as master
|
||||
ensure_ssl_cert_master()
|
||||
|
||||
ssl.CA_SINGLETON.append(ca)
|
||||
|
||||
@ -1090,6 +1197,12 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
||||
single = set(['service', 'region', 'public_url', 'admin_url',
|
||||
'internal_url'])
|
||||
https_cns = []
|
||||
|
||||
if https():
|
||||
protocol = 'https'
|
||||
else:
|
||||
protocol = 'http'
|
||||
|
||||
if single.issubset(settings):
|
||||
# other end of relation advertised only one endpoint
|
||||
if 'None' in settings.itervalues():
|
||||
@ -1099,22 +1212,22 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
||||
# Check if clustered and use vip + haproxy ports if so
|
||||
relation_data["auth_host"] = resolve_address(ADMIN)
|
||||
relation_data["service_host"] = resolve_address(PUBLIC)
|
||||
if https():
|
||||
relation_data["auth_protocol"] = "https"
|
||||
relation_data["service_protocol"] = "https"
|
||||
else:
|
||||
relation_data["auth_protocol"] = "http"
|
||||
relation_data["service_protocol"] = "http"
|
||||
relation_data["auth_protocol"] = protocol
|
||||
relation_data["service_protocol"] = protocol
|
||||
relation_data["auth_port"] = config('admin-port')
|
||||
relation_data["service_port"] = config('service-port')
|
||||
relation_data["region"] = config('region')
|
||||
if is_str_true(config('https-service-endpoints')):
|
||||
|
||||
https_service_endpoints = config('https-service-endpoints')
|
||||
if (https_service_endpoints and
|
||||
bool_from_string(https_service_endpoints)):
|
||||
# Pass CA cert as client will need it to
|
||||
# verify https connections
|
||||
ca = get_ca(user=SSH_USER)
|
||||
ca_bundle = ca.get_ca_bundle()
|
||||
relation_data['https_keystone'] = 'True'
|
||||
relation_data['ca_cert'] = b64encode(ca_bundle)
|
||||
|
||||
# Allow the remote service to request creation of any additional
|
||||
# roles. Currently used by Horizon
|
||||
for role in get_requested_roles(settings):
|
||||
@ -1142,8 +1255,8 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
||||
# NOTE(jamespage) internal IP for backwards compat for SSL certs
|
||||
internal_cn = urlparse.urlparse(settings['internal_url']).hostname
|
||||
https_cns.append(internal_cn)
|
||||
https_cns.append(
|
||||
urlparse.urlparse(settings['public_url']).hostname)
|
||||
public_cn = urlparse.urlparse(settings['public_url']).hostname
|
||||
https_cns.append(public_cn)
|
||||
https_cns.append(urlparse.urlparse(settings['admin_url']).hostname)
|
||||
else:
|
||||
# assemble multiple endpoints from relation data. service name
|
||||
@ -1169,6 +1282,7 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
||||
if ep not in endpoints:
|
||||
endpoints[ep] = {}
|
||||
endpoints[ep][x] = v
|
||||
|
||||
services = []
|
||||
https_cn = None
|
||||
for ep in endpoints:
|
||||
@ -1189,6 +1303,7 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
||||
https_cns.append(internal_cn)
|
||||
https_cns.append(urlparse.urlparse(ep['public_url']).hostname)
|
||||
https_cns.append(urlparse.urlparse(ep['admin_url']).hostname)
|
||||
|
||||
service_username = '_'.join(services)
|
||||
|
||||
# If an admin username prefix is provided, ensure all services use it.
|
||||
@ -1214,8 +1329,7 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
||||
# Currently used by Swift and Ceilometer.
|
||||
for role in get_requested_roles(settings):
|
||||
log("Creating requested role: %s" % role)
|
||||
create_role(role, service_username,
|
||||
config('service-tenant'))
|
||||
create_role(role, service_username, config('service-tenant'))
|
||||
|
||||
# As of https://review.openstack.org/#change,4675, all nodes hosting
|
||||
# an endpoint(s) needs a service username and password assigned to
|
||||
@ -1237,18 +1351,14 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
||||
"https_keystone": "False",
|
||||
"ssl_cert": "",
|
||||
"ssl_key": "",
|
||||
"ca_cert": ""
|
||||
"ca_cert": "",
|
||||
"auth_protocol": protocol,
|
||||
"service_protocol": protocol,
|
||||
}
|
||||
|
||||
# Check if https is enabled
|
||||
if https():
|
||||
relation_data["auth_protocol"] = "https"
|
||||
relation_data["service_protocol"] = "https"
|
||||
else:
|
||||
relation_data["auth_protocol"] = "http"
|
||||
relation_data["service_protocol"] = "http"
|
||||
# generate or get a new cert/key for service if set to manage certs.
|
||||
if is_str_true(config('https-service-endpoints')):
|
||||
https_service_endpoints = config('https-service-endpoints')
|
||||
if https_service_endpoints and bool_from_string(https_service_endpoints):
|
||||
ca = get_ca(user=SSH_USER)
|
||||
# NOTE(jamespage) may have multiple cns to deal with to iterate
|
||||
https_cns = set(https_cns)
|
||||
@ -1256,6 +1366,7 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
||||
cert, key = ca.get_cert_and_key(common_name=https_cn)
|
||||
relation_data['ssl_cert_{}'.format(https_cn)] = b64encode(cert)
|
||||
relation_data['ssl_key_{}'.format(https_cn)] = b64encode(key)
|
||||
|
||||
# NOTE(jamespage) for backwards compatibility
|
||||
cert, key = ca.get_cert_and_key(common_name=internal_cn)
|
||||
relation_data['ssl_cert'] = b64encode(cert)
|
||||
@ -1264,8 +1375,7 @@ def add_service_to_keystone(relation_id=None, remote_unit=None):
|
||||
relation_data['ca_cert'] = b64encode(ca_bundle)
|
||||
relation_data['https_keystone'] = 'True'
|
||||
|
||||
peer_store_and_set(relation_id=relation_id,
|
||||
**relation_data)
|
||||
peer_store_and_set(relation_id=relation_id, **relation_data)
|
||||
|
||||
|
||||
def ensure_valid_service(service):
|
||||
@ -1286,7 +1396,7 @@ def add_endpoint(region, service, publicurl, adminurl, internalurl):
|
||||
|
||||
|
||||
def get_requested_roles(settings):
|
||||
''' Retrieve any valid requested_roles from dict settings '''
|
||||
"""Retrieve any valid requested_roles from dict settings"""
|
||||
if ('requested_roles' in settings and
|
||||
settings['requested_roles'] not in ['None', None]):
|
||||
return settings['requested_roles'].split(',')
|
||||
@ -1295,6 +1405,7 @@ def get_requested_roles(settings):
|
||||
|
||||
|
||||
def setup_ipv6():
|
||||
"""Check ipv6-mode validity and setup dependencies"""
|
||||
ubuntu_rel = lsb_release()['DISTRIB_CODENAME'].lower()
|
||||
if ubuntu_rel < "trusty":
|
||||
raise Exception("IPv6 is not supported in the charms for Ubuntu "
|
||||
@ -1408,9 +1519,8 @@ def is_db_ready(use_current_context=False, db_rel=None):
|
||||
if allowed_units and local_unit() in allowed_units.split():
|
||||
return True
|
||||
|
||||
# If relation has units
|
||||
return False
|
||||
rel_has_units = True
|
||||
|
||||
# If neither relation has units then we are probably in sqllite mode return
|
||||
# True.
|
||||
# If neither relation has units then we are probably in sqlite mode so
|
||||
# return True.
|
||||
return not rel_has_units
|
||||
|
@ -43,7 +43,15 @@ driver = keystone.catalog.backends.sql.Catalog
|
||||
|
||||
[token]
|
||||
driver = keystone.token.backends.sql.Token
|
||||
provider = keystone.token.providers.uuid.Provider
|
||||
{% if token_provider == 'pki' -%}
|
||||
provider = keystone.token.providers.pki.Provider
|
||||
{% elif token_provider == 'pkiz' -%}
|
||||
provider = keystone.token.providers.pkiz.Provider
|
||||
{% else -%}
|
||||
provider = keystone.token.providers.uuid.Provider
|
||||
{% endif %}
|
||||
|
||||
{% include "parts/section-signing" %}
|
||||
|
||||
[cache]
|
||||
|
||||
@ -58,8 +66,6 @@ driver = keystone.assignment.backends.{{ assignment_backend }}.Assignment
|
||||
|
||||
[oauth1]
|
||||
|
||||
[signing]
|
||||
|
||||
[auth]
|
||||
methods = external,password,token,oauth1
|
||||
password = keystone.auth.plugins.password.Password
|
||||
|
105
templates/kilo/keystone.conf
Normal file
105
templates/kilo/keystone.conf
Normal file
@ -0,0 +1,105 @@
|
||||
# kilo
|
||||
###############################################################################
|
||||
# [ WARNING ]
|
||||
# Configuration file maintained by Juju. Local changes may be overwritten.
|
||||
###############################################################################
|
||||
[DEFAULT]
|
||||
admin_token = {{ token }}
|
||||
admin_port = {{ admin_port }}
|
||||
public_port = {{ public_port }}
|
||||
use_syslog = {{ use_syslog }}
|
||||
log_config = /etc/keystone/logging.conf
|
||||
debug = {{ debug }}
|
||||
verbose = {{ verbose }}
|
||||
public_endpoint = {{ public_endpoint }}
|
||||
admin_endpoint = {{ admin_endpoint }}
|
||||
bind_host = {{ bind_host }}
|
||||
public_workers = {{ workers }}
|
||||
admin_workers = {{ workers }}
|
||||
|
||||
[database]
|
||||
{% if database_host -%}
|
||||
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 -%}
|
||||
idle_timeout = 200
|
||||
|
||||
[identity]
|
||||
driver = keystone.identity.backends.{{ identity_backend }}.Identity
|
||||
|
||||
[credential]
|
||||
driver = keystone.credential.backends.sql.Credential
|
||||
|
||||
[trust]
|
||||
driver = keystone.trust.backends.sql.Trust
|
||||
|
||||
[os_inherit]
|
||||
|
||||
[catalog]
|
||||
driver = keystone.catalog.backends.sql.Catalog
|
||||
|
||||
[endpoint_filter]
|
||||
|
||||
[token]
|
||||
driver = keystone.token.persistence.backends.sql.Token
|
||||
provider = keystone.token.providers.uuid.Provider
|
||||
|
||||
[cache]
|
||||
|
||||
[policy]
|
||||
driver = keystone.policy.backends.sql.Policy
|
||||
|
||||
[ec2]
|
||||
driver = keystone.contrib.ec2.backends.sql.Ec2
|
||||
|
||||
[assignment]
|
||||
driver = keystone.assignment.backends.{{ assignment_backend }}.Assignment
|
||||
|
||||
[oauth1]
|
||||
|
||||
[signing]
|
||||
|
||||
[auth]
|
||||
methods = external,password,token,oauth1
|
||||
password = keystone.auth.plugins.password.Password
|
||||
token = keystone.auth.plugins.token.Token
|
||||
oauth1 = keystone.auth.plugins.oauth1.OAuth
|
||||
|
||||
[paste_deploy]
|
||||
config_file = keystone-paste.ini
|
||||
|
||||
[extra_headers]
|
||||
Distribution = Ubuntu
|
||||
|
||||
[ldap]
|
||||
{% if identity_backend == 'ldap' -%}
|
||||
url = {{ ldap_server }}
|
||||
user = {{ ldap_user }}
|
||||
password = {{ ldap_password }}
|
||||
suffix = {{ ldap_suffix }}
|
||||
|
||||
{% if ldap_config_flags -%}
|
||||
{% for key, value in ldap_config_flags.iteritems() -%}
|
||||
{{ key }} = {{ value }}
|
||||
{% endfor -%}
|
||||
{% endif -%}
|
||||
|
||||
{% if ldap_readonly -%}
|
||||
user_allow_create = False
|
||||
user_allow_update = False
|
||||
user_allow_delete = False
|
||||
|
||||
tenant_allow_create = False
|
||||
tenant_allow_update = False
|
||||
tenant_allow_delete = False
|
||||
|
||||
role_allow_create = False
|
||||
role_allow_update = False
|
||||
role_allow_delete = False
|
||||
|
||||
group_allow_create = False
|
||||
group_allow_update = False
|
||||
group_allow_delete = False
|
||||
{% endif -%}
|
||||
{% endif -%}
|
44
templates/kilo/logging.conf
Normal file
44
templates/kilo/logging.conf
Normal file
@ -0,0 +1,44 @@
|
||||
# kilo
|
||||
[loggers]
|
||||
keys=root
|
||||
|
||||
[formatters]
|
||||
keys=normal,normal_with_name,debug
|
||||
|
||||
[handlers]
|
||||
keys=production,file,devel
|
||||
|
||||
[logger_root]
|
||||
{% if root_level -%}
|
||||
level={{ root_level }}
|
||||
{% else -%}
|
||||
level=WARNING
|
||||
{% endif -%}
|
||||
handlers=file
|
||||
|
||||
[handler_production]
|
||||
class=handlers.SysLogHandler
|
||||
level=ERROR
|
||||
formatter=normal_with_name
|
||||
args=(('localhost', handlers.SYSLOG_UDP_PORT), handlers.SysLogHandler.LOG_USER)
|
||||
|
||||
[handler_file]
|
||||
class=FileHandler
|
||||
level=DEBUG
|
||||
formatter=normal_with_name
|
||||
args=('/var/log/keystone/keystone.log', 'a')
|
||||
|
||||
[handler_devel]
|
||||
class=StreamHandler
|
||||
level=NOTSET
|
||||
formatter=debug
|
||||
args=(sys.stdout,)
|
||||
|
||||
[formatter_normal]
|
||||
format=%(asctime)s %(levelname)s %(message)s
|
||||
|
||||
[formatter_normal_with_name]
|
||||
format=(%(name)s): %(asctime)s %(levelname)s %(message)s
|
||||
|
||||
[formatter_debug]
|
||||
format=(%(name)s): %(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s
|
13
templates/parts/section-signing
Normal file
13
templates/parts/section-signing
Normal file
@ -0,0 +1,13 @@
|
||||
[signing]
|
||||
{% if certfile -%}
|
||||
certfile = {{ certfile }}
|
||||
{% endif -%}
|
||||
{% if keyfile -%}
|
||||
keyfile = {{ keyfile }}
|
||||
{% endif -%}
|
||||
{% if ca_certs -%}
|
||||
ca_certs = {{ ca_certs }}
|
||||
{% endif -%}
|
||||
{% if ca_key -%}
|
||||
ca_key = {{ ca_key }}
|
||||
{% endif -%}
|
@ -71,16 +71,19 @@ class OpenStackAmuletDeployment(AmuletDeployment):
|
||||
services.append(this_service)
|
||||
use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
|
||||
'ceph-osd', 'ceph-radosgw']
|
||||
# Openstack subordinate charms do not expose an origin option as that
|
||||
# is controlled by the principle
|
||||
ignore = ['neutron-openvswitch']
|
||||
|
||||
if self.openstack:
|
||||
for svc in services:
|
||||
if svc['name'] not in use_source:
|
||||
if svc['name'] not in use_source + ignore:
|
||||
config = {'openstack-origin': self.openstack}
|
||||
self.d.configure(svc['name'], config)
|
||||
|
||||
if self.source:
|
||||
for svc in services:
|
||||
if svc['name'] in use_source:
|
||||
if svc['name'] in use_source and svc['name'] not in ignore:
|
||||
config = {'source': self.source}
|
||||
self.d.configure(svc['name'], config)
|
||||
|
||||
|
@ -63,7 +63,6 @@ TO_PATCH = [
|
||||
'execd_preinstall',
|
||||
'mkdir',
|
||||
'os',
|
||||
'time',
|
||||
# ip
|
||||
'get_iface_for_address',
|
||||
'get_netmask_for_address',
|
||||
@ -203,6 +202,7 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
configs.write = MagicMock()
|
||||
hooks.pgsql_db_changed()
|
||||
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch.object(hooks, 'is_db_ready')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@ -210,7 +210,9 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
@patch.object(hooks, 'identity_changed')
|
||||
def test_db_changed_allowed(self, identity_changed, configs,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log, mock_is_db_ready):
|
||||
mock_log, mock_is_db_ready,
|
||||
mock_is_db_initialised):
|
||||
mock_is_db_initialised.return_value = True
|
||||
mock_is_db_ready.return_value = True
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
self.relation_ids.return_value = ['identity-service:0']
|
||||
@ -247,10 +249,15 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch.object(hooks, 'is_db_ready')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
@patch.object(hooks, 'identity_changed')
|
||||
def test_postgresql_db_changed(self, identity_changed, configs,
|
||||
mock_is_db_ready, mock_is_db_initialised,
|
||||
mock_ensure_ssl_cert_master, mock_log):
|
||||
mock_is_db_initialised.return_value = True
|
||||
mock_is_db_ready.return_value = True
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
self.relation_ids.return_value = ['identity-service:0']
|
||||
self.related_units.return_value = ['unit/0']
|
||||
@ -266,6 +273,10 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'is_pki_enabled')
|
||||
@patch.object(hooks, 'is_ssl_cert_master')
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch.object(hooks, 'is_db_ready')
|
||||
@patch.object(hooks, 'peer_units')
|
||||
@patch.object(hooks, 'ensure_permissions')
|
||||
@patch.object(hooks, 'admin_relation_changed')
|
||||
@ -275,11 +286,24 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
@patch.object(hooks, 'identity_changed')
|
||||
@patch.object(hooks, 'configure_https')
|
||||
def test_config_changed_no_openstack_upgrade_leader(
|
||||
self, configure_https, identity_changed,
|
||||
configs, get_homedir, ensure_user, cluster_joined,
|
||||
admin_relation_changed, ensure_permissions, mock_peer_units,
|
||||
mock_ensure_ssl_cert_master, mock_log):
|
||||
def test_config_changed_no_upgrade_leader(self, configure_https,
|
||||
identity_changed,
|
||||
configs, get_homedir,
|
||||
ensure_user,
|
||||
cluster_joined,
|
||||
admin_relation_changed,
|
||||
ensure_permissions,
|
||||
mock_peer_units,
|
||||
mock_is_db_ready,
|
||||
mock_is_db_initialised,
|
||||
mock_is_ssl_cert_master,
|
||||
mock_is_pki_enabled,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log):
|
||||
mock_is_pki_enabled.return_value = True
|
||||
mock_is_ssl_cert_master.return_value = True
|
||||
mock_is_db_initialised.return_value = True
|
||||
mock_is_db_ready.return_value = True
|
||||
self.openstack_upgrade_available.return_value = False
|
||||
self.is_elected_leader.return_value = True
|
||||
# avoid having to mock syncer
|
||||
@ -296,7 +320,6 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
configure_https.assert_called_with()
|
||||
self.assertTrue(configs.write_all.called)
|
||||
|
||||
self.migrate_database.assert_called_with()
|
||||
self.assertTrue(self.ensure_initial_admin.called)
|
||||
self.log.assert_called_with(
|
||||
'Firing identity_changed hook for all related services.')
|
||||
@ -307,6 +330,8 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'is_pki_enabled')
|
||||
@patch.object(hooks, 'is_ssl_cert_master')
|
||||
@patch.object(hooks, 'ensure_permissions')
|
||||
@patch.object(hooks, 'cluster_joined')
|
||||
@patch.object(unison, 'ensure_user')
|
||||
@ -317,8 +342,10 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
def test_config_changed_no_openstack_upgrade_not_leader(
|
||||
self, configure_https, identity_changed,
|
||||
configs, get_homedir, ensure_user, cluster_joined,
|
||||
ensure_permissions, mock_ensure_ssl_cert_master,
|
||||
mock_log):
|
||||
ensure_permissions, mock_is_ssl_cert_master, mock_is_pki_enabled,
|
||||
mock_ensure_ssl_cert_master, mock_log):
|
||||
mock_is_pki_enabled.return_value = True
|
||||
mock_is_ssl_cert_master.return_value = True
|
||||
self.openstack_upgrade_available.return_value = False
|
||||
self.is_elected_leader.return_value = False
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
@ -337,6 +364,10 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'is_pki_enabled')
|
||||
@patch.object(hooks, 'is_ssl_cert_master')
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch.object(hooks, 'is_db_ready')
|
||||
@patch.object(hooks, 'peer_units')
|
||||
@patch.object(hooks, 'ensure_permissions')
|
||||
@patch.object(hooks, 'admin_relation_changed')
|
||||
@ -346,12 +377,23 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
@patch.object(hooks, 'identity_changed')
|
||||
@patch.object(hooks, 'configure_https')
|
||||
def test_config_changed_with_openstack_upgrade(
|
||||
self, configure_https, identity_changed,
|
||||
configs, get_homedir, ensure_user, cluster_joined,
|
||||
admin_relation_changed,
|
||||
ensure_permissions, mock_peer_units, mock_ensure_ssl_cert_master,
|
||||
mock_log):
|
||||
def test_config_changed_with_openstack_upgrade(self, configure_https,
|
||||
identity_changed,
|
||||
configs, get_homedir,
|
||||
ensure_user, cluster_joined,
|
||||
admin_relation_changed,
|
||||
ensure_permissions,
|
||||
mock_peer_units,
|
||||
mock_is_db_ready,
|
||||
mock_is_db_initialised,
|
||||
mock_is_ssl_cert_master,
|
||||
mock_is_pki_enabled,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log):
|
||||
mock_is_pki_enabled.return_value = True
|
||||
mock_is_ssl_cert_master.return_value = True
|
||||
mock_is_db_ready.return_value = True
|
||||
mock_is_db_initialised.return_value = True
|
||||
self.openstack_upgrade_available.return_value = True
|
||||
self.is_elected_leader.return_value = True
|
||||
# avoid having to mock syncer
|
||||
@ -370,7 +412,6 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
configure_https.assert_called_with()
|
||||
self.assertTrue(configs.write_all.called)
|
||||
|
||||
self.migrate_database.assert_called_with()
|
||||
self.assertTrue(self.ensure_initial_admin.called)
|
||||
self.log.assert_called_with(
|
||||
'Firing identity_changed hook for all related services.')
|
||||
@ -379,6 +420,7 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
remote_unit='unit/0')
|
||||
admin_relation_changed.assert_called_with('identity-service:0')
|
||||
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch.object(hooks, 'is_db_ready')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@ -386,7 +428,9 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
@patch.object(hooks, 'send_notifications')
|
||||
def test_identity_changed_leader(self, mock_send_notifications,
|
||||
mock_hashlib, mock_ensure_ssl_cert_master,
|
||||
mock_log, mock_is_db_ready):
|
||||
mock_log, mock_is_db_ready,
|
||||
mock_is_db_initialised):
|
||||
mock_is_db_initialised.return_value = True
|
||||
mock_is_db_ready.return_value = True
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
hooks.identity_changed(
|
||||
@ -544,12 +588,18 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.ensure_ssl_cert_master')
|
||||
@patch.object(hooks, 'is_db_ready')
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch.object(hooks, 'identity_changed')
|
||||
@patch.object(hooks, 'CONFIGS')
|
||||
def test_ha_relation_changed_clustered_leader(self, configs,
|
||||
identity_changed,
|
||||
mock_is_db_initialised,
|
||||
mock_is_db_ready,
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_log):
|
||||
mock_is_db_initialised.return_value = True
|
||||
mock_is_db_ready.return_value = True
|
||||
mock_ensure_ssl_cert_master.return_value = False
|
||||
self.relation_get.return_value = True
|
||||
self.is_elected_leader.return_value = True
|
||||
@ -595,6 +645,8 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
cmd = ['a2dissite', 'openstack_https_frontend']
|
||||
self.check_call.assert_called_with(cmd)
|
||||
|
||||
@patch.object(hooks, 'is_db_ready')
|
||||
@patch.object(hooks, 'is_db_initialised')
|
||||
@patch('keystone_utils.log')
|
||||
@patch('keystone_utils.relation_ids')
|
||||
@patch('keystone_utils.is_elected_leader')
|
||||
@ -608,7 +660,11 @@ class KeystoneRelationTests(CharmTestCase):
|
||||
mock_ensure_ssl_cert_master,
|
||||
mock_is_elected_leader,
|
||||
mock_relation_ids,
|
||||
mock_log):
|
||||
mock_log,
|
||||
mock_is_db_ready,
|
||||
mock_is_db_initialised):
|
||||
mock_is_db_initialised.return_value = True
|
||||
mock_is_db_ready.return_value = True
|
||||
mock_is_elected_leader.return_value = False
|
||||
mock_relation_ids.return_value = []
|
||||
mock_ensure_ssl_cert_master.return_value = True
|
||||
|
@ -28,6 +28,7 @@ TO_PATCH = [
|
||||
'grant_role',
|
||||
'configure_installation_source',
|
||||
'is_elected_leader',
|
||||
'is_ssl_cert_master',
|
||||
'https',
|
||||
'peer_store_and_set',
|
||||
'service_stop',
|
||||
@ -352,65 +353,169 @@ class TestKeystoneUtils(CharmTestCase):
|
||||
self.assertEqual(utils.get_admin_passwd(), 'supersecretgen')
|
||||
|
||||
def test_is_db_ready(self):
|
||||
allowed_units = None
|
||||
|
||||
def fake_rel_get(attribute=None, *args, **kwargs):
|
||||
if attribute == 'allowed_units':
|
||||
return allowed_units
|
||||
|
||||
self.relation_get.side_effect = fake_rel_get
|
||||
|
||||
self.relation_id.return_value = 'shared-db:0'
|
||||
self.relation_ids.return_value = [self.relation_id.return_value]
|
||||
self.relation_ids.return_value = ['shared-db:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
self.relation_get.return_value = 'unit/0'
|
||||
allowed_units = 'unit/0'
|
||||
self.assertTrue(utils.is_db_ready(use_current_context=True))
|
||||
|
||||
self.relation_ids.return_value = ['acme:0']
|
||||
self.assertRaises(utils.is_db_ready, use_current_context=True)
|
||||
|
||||
self.related_units.return_value = ['unit/0']
|
||||
self.relation_ids.return_value = [self.relation_id.return_value]
|
||||
self.relation_ids.return_value = ['shared-db:0', 'shared-db:1']
|
||||
self.assertTrue(utils.is_db_ready())
|
||||
|
||||
self.relation_get.return_value = 'unit/1'
|
||||
allowed_units = 'unit/1'
|
||||
self.assertFalse(utils.is_db_ready())
|
||||
|
||||
self.related_units.return_value = []
|
||||
self.assertTrue(utils.is_db_ready())
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
@patch.object(utils, 'is_elected_leader')
|
||||
@patch.object(utils, 'oldest_peer')
|
||||
@patch.object(utils, 'is_ssl_enabled')
|
||||
def test_ensure_ssl_cert_master(self, mock_is_str_true, mock_oldest_peer,
|
||||
mock_is_elected_leader, mock_peer_units):
|
||||
def test_ensure_ssl_cert_master_no_ssl(self, mock_is_ssl_enabled,
|
||||
mock_peer_units):
|
||||
mock_is_ssl_enabled.return_value = False
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(self.relation_set.called)
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
@patch.object(utils, 'is_ssl_enabled')
|
||||
def test_ensure_ssl_cert_master_ssl_no_peers(self, mock_is_ssl_enabled,
|
||||
mock_peer_units):
|
||||
def mock_rel_get(unit=None, **kwargs):
|
||||
return None
|
||||
|
||||
self.relation_get.side_effect = mock_rel_get
|
||||
mock_is_ssl_enabled.return_value = True
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
|
||||
mock_is_str_true.return_value = False
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(self.relation_set.called)
|
||||
|
||||
mock_is_elected_leader.return_value = False
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(self.relation_set.called)
|
||||
|
||||
mock_is_str_true.return_value = True
|
||||
mock_is_elected_leader.return_value = False
|
||||
mock_peer_units.return_value = ['unit/0']
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(self.relation_set.called)
|
||||
|
||||
self.related_units.return_value = []
|
||||
mock_peer_units.return_value = []
|
||||
# This should get ignored since we are overriding
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = False
|
||||
self.assertTrue(utils.ensure_ssl_cert_master())
|
||||
settings = {'ssl-cert-master': 'unit/0'}
|
||||
self.relation_set.assert_called_with(relation_id='cluster:0',
|
||||
relation_settings=settings)
|
||||
self.relation_set.reset_mock()
|
||||
|
||||
self.assertTrue(utils.ensure_ssl_cert_master(use_oldest_peer=True))
|
||||
@patch.object(utils, 'peer_units')
|
||||
@patch.object(utils, 'is_ssl_enabled')
|
||||
def test_ensure_ssl_cert_master_ssl_master_no_peers(self,
|
||||
mock_is_ssl_enabled,
|
||||
mock_peer_units):
|
||||
def mock_rel_get(unit=None, **kwargs):
|
||||
if unit == 'unit/0':
|
||||
return 'unit/0'
|
||||
|
||||
return None
|
||||
|
||||
self.relation_get.side_effect = mock_rel_get
|
||||
mock_is_ssl_enabled.return_value = True
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
self.related_units.return_value = []
|
||||
mock_peer_units.return_value = []
|
||||
# This should get ignored since we are overriding
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = False
|
||||
self.assertTrue(utils.ensure_ssl_cert_master())
|
||||
settings = {'ssl-cert-master': 'unit/0'}
|
||||
self.relation_set.assert_called_with(relation_id='cluster:0',
|
||||
relation_settings=settings)
|
||||
self.relation_set.reset_mock()
|
||||
|
||||
mock_peer_units.return_value = ['unit/0']
|
||||
@patch.object(utils, 'peer_units')
|
||||
@patch.object(utils, 'is_ssl_enabled')
|
||||
def test_ensure_ssl_cert_master_ssl_not_leader(self, mock_is_ssl_enabled,
|
||||
mock_peer_units):
|
||||
mock_is_ssl_enabled.return_value = True
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
mock_peer_units.return_value = ['unit/1']
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = False
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(self.relation_set.called)
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
@patch.object(utils, 'is_ssl_enabled')
|
||||
def test_ensure_ssl_cert_master_is_leader_new_peer(self,
|
||||
mock_is_ssl_enabled,
|
||||
mock_peer_units):
|
||||
def mock_rel_get(unit=None, **kwargs):
|
||||
if unit == 'unit/0':
|
||||
return 'unit/0'
|
||||
|
||||
return 'unknown'
|
||||
|
||||
self.relation_get.side_effect = mock_rel_get
|
||||
mock_is_ssl_enabled.return_value = True
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
mock_peer_units.return_value = ['unit/1']
|
||||
self.related_units.return_value = ['unit/1']
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = True
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(utils.ensure_ssl_cert_master(use_oldest_peer=True))
|
||||
settings = {'ssl-cert-master': 'unit/0'}
|
||||
self.relation_set.assert_called_with(relation_id='cluster:0',
|
||||
relation_settings=settings)
|
||||
self.relation_set.reset_mock()
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
@patch.object(utils, 'is_ssl_enabled')
|
||||
def test_ensure_ssl_cert_master_is_leader_no_new_peer(self,
|
||||
mock_is_ssl_enabled,
|
||||
mock_peer_units):
|
||||
def mock_rel_get(unit=None, **kwargs):
|
||||
if unit == 'unit/0':
|
||||
return 'unit/0'
|
||||
|
||||
return 'unit/0'
|
||||
|
||||
self.relation_get.side_effect = mock_rel_get
|
||||
mock_is_ssl_enabled.return_value = True
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
mock_peer_units.return_value = ['unit/1']
|
||||
self.related_units.return_value = ['unit/1']
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = True
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(self.relation_set.called)
|
||||
|
||||
@patch.object(utils, 'peer_units')
|
||||
@patch.object(utils, 'is_ssl_enabled')
|
||||
def test_ensure_ssl_cert_master_is_leader_bad_votes(self,
|
||||
mock_is_ssl_enabled,
|
||||
mock_peer_units):
|
||||
counter = {0: 0}
|
||||
|
||||
def mock_rel_get(unit=None, **kwargs):
|
||||
"""Returns a mix of votes."""
|
||||
if unit == 'unit/0':
|
||||
return 'unit/0'
|
||||
|
||||
ret = 'unit/%d' % (counter[0])
|
||||
counter[0] += 1
|
||||
return ret
|
||||
|
||||
self.relation_get.side_effect = mock_rel_get
|
||||
mock_is_ssl_enabled.return_value = True
|
||||
self.relation_ids.return_value = ['cluster:0']
|
||||
self.local_unit.return_value = 'unit/0'
|
||||
mock_peer_units.return_value = ['unit/1']
|
||||
self.related_units.return_value = ['unit/1']
|
||||
self.is_ssl_cert_master.return_value = False
|
||||
self.is_elected_leader.return_value = True
|
||||
self.assertFalse(utils.ensure_ssl_cert_master())
|
||||
self.assertFalse(self.relation_set.called)
|
||||
|
Loading…
x
Reference in New Issue
Block a user