Merge "Snap install OpenStack in Charms"

This commit is contained in:
Jenkins 2017-10-04 18:00:29 +00:00 committed by Gerrit Code Review
commit 2359950179
20 changed files with 897 additions and 234 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ xenial/
func-results.json
.local
__pycache__
.stestr

View File

@ -802,8 +802,9 @@ class ApacheSSLContext(OSContextGenerator):
else:
# Expect cert/key provided in config (currently assumed that ca
# uses ip for cn)
cn = resolve_address(endpoint_type=INTERNAL)
self.configure_cert(cn)
for net_type in (INTERNAL, ADMIN, PUBLIC):
cn = resolve_address(endpoint_type=net_type)
self.configure_cert(cn)
addresses = self.get_network_addresses()
for address, endpoint in addresses:

View File

@ -95,7 +95,7 @@ from charmhelpers.fetch import (
from charmhelpers.fetch.snap import (
snap_install,
snap_refresh,
SNAP_CHANNELS,
valid_snap_channel,
)
from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
@ -579,6 +579,9 @@ def configure_installation_source(source_plus_key):
Note that the behaviour on error is to log the error to the juju log and
then call sys.exit(1).
"""
if source_plus_key.startswith('snap'):
# Do nothing for snap installs
return
# extract the key if there is one, denoted by a '|' in the rel
source, key = get_source_and_pgp_key(source_plus_key)
@ -2048,7 +2051,7 @@ def update_json_file(filename, items):
def snap_install_requested():
""" Determine if installing from snaps
If openstack-origin is of the form snap:channel-series-release
If openstack-origin is of the form snap:track/channel
and channel is in SNAPS_CHANNELS return True.
"""
origin = config('openstack-origin') or ""
@ -2056,10 +2059,12 @@ def snap_install_requested():
return False
_src = origin[5:]
channel, series, release = _src.split('-')
if channel.lower() in SNAP_CHANNELS:
return True
return False
if '/' in _src:
_track, channel = _src.split('/')
else:
# Hanlde snap:track with no channel
channel = 'stable'
return valid_snap_channel(channel)
def get_snaps_install_info_from_origin(snaps, src, mode='classic'):
@ -2067,7 +2072,7 @@ def get_snaps_install_info_from_origin(snaps, src, mode='classic'):
@param snaps: List of snaps
@param src: String of openstack-origin or source of the form
snap:channel-series-track
snap:track/channel
@param mode: String classic, devmode or jailmode
@returns: Dictionary of snaps with channels and modes
"""
@ -2077,8 +2082,7 @@ def get_snaps_install_info_from_origin(snaps, src, mode='classic'):
return {}
_src = src[5:]
_channel, _series, _release = _src.split('-')
channel = '--channel={}/{}'.format(_release, _channel)
channel = '--channel={}'.format(_src)
return {snap: {'channel': channel, 'mode': mode}
for snap in snaps}
@ -2090,8 +2094,8 @@ def install_os_snaps(snaps, refresh=False):
@param snaps: Dictionary of snaps with channels and modes of the form:
{'snap_name': {'channel': 'snap_channel',
'mode': 'snap_mode'}}
Where channel a snapstore channel and mode is --classic, --devmode or
--jailmode.
Where channel is a snapstore channel and mode is --classic, --devmode
or --jailmode.
@param post_snap_install: Callback function to run after snaps have been
installed
"""

View File

@ -41,6 +41,10 @@ class CouldNotAcquireLockException(Exception):
pass
class InvalidSnapChannel(Exception):
pass
def _snap_exec(commands):
"""
Execute snap commands.
@ -132,3 +136,15 @@ def snap_refresh(packages, *flags):
log(message, level='INFO')
return _snap_exec(['refresh'] + flags + packages)
def valid_snap_channel(channel):
""" Validate snap channel exists
:raises InvalidSnapChannel: When channel does not exist
:return: Boolean
"""
if channel.lower() in SNAP_CHANNELS:
return True
else:
raise InvalidSnapChannel("Invalid Snap Channel: {}".format(channel))

View File

@ -30,6 +30,7 @@ from charmhelpers.contrib.hahelpers.cluster import (
determine_apache_port,
determine_api_port,
is_elected_leader,
https,
)
from charmhelpers.core.hookenv import (
@ -56,11 +57,90 @@ def is_cert_provided_in_config():
return bool(ca and cert and key)
class ApacheSSLContext(context.ApacheSSLContext):
class SSLContext(context.ApacheSSLContext):
def configure_cert(self, cn):
from keystone_utils import (
SSH_USER,
get_ca,
ensure_permissions,
is_ssl_cert_master,
KEYSTONE_USER,
)
# Ensure ssl dir exists whether master or not
perms = 0o775
mkdir(path=self.ssl_dir, owner=SSH_USER, group=KEYSTONE_USER,
perms=perms)
# Ensure accessible by keystone ssh user and group (for sync)
ensure_permissions(self.ssl_dir, user=SSH_USER, group=KEYSTONE_USER,
perms=perms)
if not is_cert_provided_in_config() and not is_ssl_cert_master():
log("Not ssl-cert-master - skipping apache cert config until "
"master is elected", level=INFO)
return
log("Creating apache ssl certs in %s" % (self.ssl_dir), level=INFO)
cert = config('ssl_cert')
key = config('ssl_key')
if not (cert and key):
ca = get_ca(user=SSH_USER)
cert, key = ca.get_cert_and_key(common_name=cn)
else:
cert = b64decode(cert)
key = b64decode(key)
write_file(path=os.path.join(self.ssl_dir, 'cert_{}'.format(cn)),
content=cert, owner=SSH_USER, group=KEYSTONE_USER,
perms=0o644)
write_file(path=os.path.join(self.ssl_dir, 'key_{}'.format(cn)),
content=key, owner=SSH_USER, group=KEYSTONE_USER,
perms=0o644)
def configure_ca(self):
from keystone_utils import (
SSH_USER,
get_ca,
ensure_permissions,
is_ssl_cert_master,
KEYSTONE_USER,
)
if not is_cert_provided_in_config() and not is_ssl_cert_master():
log("Not ssl-cert-master - skipping apache ca config until "
"master is elected", level=INFO)
return
ca_cert = config('ssl_ca')
if ca_cert is None:
ca = get_ca(user=SSH_USER)
ca_cert = ca.get_ca_bundle()
else:
ca_cert = b64decode(ca_cert)
# Ensure accessible by keystone ssh user and group (unison)
install_ca_cert(ca_cert)
ensure_permissions(CA_CERT_PATH, user=SSH_USER, group=KEYSTONE_USER,
perms=0o0644)
def canonical_names(self):
addresses = self.get_network_addresses()
addrs = []
for address, endpoint in addresses:
addrs.append(endpoint)
return list(set(addrs))
class ApacheSSLContext(SSLContext):
interfaces = ['https']
external_ports = []
service_namespace = 'keystone'
ssl_dir = os.path.join('/etc/apache2/ssl/', service_namespace)
def __call__(self):
# late import to work around circular dependency
@ -69,9 +149,7 @@ class ApacheSSLContext(context.ApacheSSLContext):
update_hash_from_path,
)
ssl_paths = [CA_CERT_PATH,
os.path.join('/etc/apache2/ssl/',
self.service_namespace)]
ssl_paths = [CA_CERT_PATH, self.ssl_dir]
self.external_ports = determine_ports()
before = hashlib.sha256()
@ -90,76 +168,75 @@ class ApacheSSLContext(context.ApacheSSLContext):
return ret
def configure_cert(self, cn):
class NginxSSLContext(SSLContext):
interfaces = ['https']
external_ports = []
service_namespace = 'keystone'
ssl_dir = ('/var/snap/{}/common/lib/juju_ssl/{}/'
''.format(service_namespace, service_namespace))
def __call__(self):
# late import to work around circular dependency
from keystone_utils import (
SSH_USER,
get_ca,
ensure_permissions,
is_ssl_cert_master,
determine_ports,
update_hash_from_path,
APACHE_SSL_DIR
)
# Ensure ssl dir exists whether master or not
ssl_dir = os.path.join('/etc/apache2/ssl/', self.service_namespace)
perms = 0o755
mkdir(path=ssl_dir, owner=SSH_USER, group='keystone', perms=perms)
# Ensure accessible by keystone ssh user and group (for sync)
ensure_permissions(ssl_dir, user=SSH_USER, group='keystone',
perms=perms)
ssl_paths = [CA_CERT_PATH, APACHE_SSL_DIR]
if not is_cert_provided_in_config() and not is_ssl_cert_master():
log("Not ssl-cert-master - skipping apache cert config until "
"master is elected", level=INFO)
return
self.external_ports = determine_ports()
before = hashlib.sha256()
for path in ssl_paths:
update_hash_from_path(before, path)
log("Creating apache ssl certs in %s" % (ssl_dir), level=INFO)
ret = super(NginxSSLContext, self).__call__()
if not ret:
log("SSL not used", level='DEBUG')
return {}
cert = config('ssl_cert')
key = config('ssl_key')
after = hashlib.sha256()
for path in ssl_paths:
update_hash_from_path(after, path)
if not (cert and key):
ca = get_ca(user=SSH_USER)
cert, key = ca.get_cert_and_key(common_name=cn)
else:
cert = b64decode(cert)
key = b64decode(key)
# Ensure that Nginx is restarted if these change
if before.hexdigest() != after.hexdigest():
service_restart('snap.keystone.nginx')
write_file(path=os.path.join(ssl_dir, 'cert_{}'.format(cn)),
content=cert, owner=SSH_USER, group='keystone', perms=0o644)
write_file(path=os.path.join(ssl_dir, 'key_{}'.format(cn)),
content=key, owner=SSH_USER, group='keystone', perms=0o644)
# Transform for use by Nginx
"""
{'endpoints': [(u'10.5.0.30', u'10.5.0.30', 4990, 4980),
(u'10.5.0.30', u'10.5.0.30', 35347, 35337)],
'ext_ports': [4990, 35347],
'namespace': 'keystone'}
"""
def configure_ca(self):
from keystone_utils import (
SSH_USER,
get_ca,
ensure_permissions,
is_ssl_cert_master,
)
nginx_ret = {}
nginx_ret['ssl'] = https()
nginx_ret['namespace'] = self.service_namespace
endpoints = {}
for ep in ret['endpoints']:
int_address, address, ext, internal = ep
if ext <= 5000:
endpoints['public'] = {
'socket': 'public',
'address': address,
'ext': ext}
elif ext >= 35337:
endpoints['admin'] = {
'socket': 'admin',
'address': address,
'ext': ext}
else:
log("Unrecognized internal port", level='ERROR')
nginx_ret['endpoints'] = endpoints
if not is_cert_provided_in_config() and not is_ssl_cert_master():
log("Not ssl-cert-master - skipping apache ca config until "
"master is elected", level=INFO)
return
return nginx_ret
ca_cert = config('ssl_ca')
if ca_cert is None:
ca = get_ca(user=SSH_USER)
ca_cert = ca.get_ca_bundle()
else:
ca_cert = b64decode(ca_cert)
# Ensure accessible by keystone ssh user and group (unison)
install_ca_cert(ca_cert)
ensure_permissions(CA_CERT_PATH, user=SSH_USER, group='keystone',
perms=0o0644)
def canonical_names(self):
addresses = self.get_network_addresses()
addrs = []
for address, endpoint in addresses:
addrs.append(endpoint)
return list(set(addrs))
def enable_modules(self):
return
class HAProxyContext(context.HAProxyContext):
@ -207,6 +284,7 @@ class KeystoneContext(context.OSContextGenerator):
from keystone_utils import (
api_port, set_admin_token, endpoint_url, resolve_address,
PUBLIC, ADMIN, PKI_CERTS_DIR, ensure_pki_cert_paths, ADMIN_DOMAIN,
snap_install_requested,
)
ctxt = {}
ctxt['token'] = set_admin_token(config('admin-token'))
@ -271,12 +349,27 @@ class KeystoneContext(context.OSContextGenerator):
resolve_address(ADMIN),
api_port('keystone-admin')).replace('v2.0', '')
if snap_install_requested():
ctxt['domain_config_dir'] = (
'/var/snap/keystone/common/etc/keystone/domains')
ctxt['log_config'] = (
'/var/snap/keystone/common/etc/keystone/logging.conf')
ctxt['paste_config_file'] = (
'/var/snap/keystone/common/etc/keystone/keystone-paste.ini')
else:
ctxt['domain_config_dir'] = '/etc/keystone/domains'
ctxt['log_config'] = ('/etc/keystone/logging.conf')
ctxt['paste_config_file'] = '/etc/keystone/keystone-paste.ini'
return ctxt
class KeystoneLoggingContext(context.OSContextGenerator):
def __call__(self):
from keystone_utils import (
snap_install_requested,
)
ctxt = {}
debug = config('debug')
if debug:
@ -289,6 +382,11 @@ class KeystoneLoggingContext(context.OSContextGenerator):
log("log-level must be one of the following states "
"(WARNING, INFO, DEBUG, ERROR) keeping the current state.")
ctxt['log_level'] = None
if snap_install_requested():
ctxt['log_file'] = (
'/var/snap/keystone/common/log/keystone.log')
else:
ctxt['log_file'] = '/var/log/keystone/keystone.log'
return ctxt

View File

@ -71,6 +71,9 @@ from charmhelpers.contrib.openstack.utils import (
pausable_restart_on_change as restart_on_change,
is_unit_paused_set,
CompareOpenStackReleases,
snap_install_requested,
install_os_snaps,
get_snaps_install_info_from_origin,
)
from keystone_utils import (
@ -84,12 +87,14 @@ from keystone_utils import (
git_install,
migrate_database,
save_script_rc,
post_snap_install,
synchronize_ca_if_changed,
register_configs,
restart_map,
services,
CLUSTER_RES,
KEYSTONE_CONF,
KEYSTONE_USER,
POLICY_JSON,
TOKEN_FLUSH_CRON_FILE,
SSH_USER,
@ -170,20 +175,32 @@ def install():
status_set('maintenance', 'Installing apt packages')
apt_update()
apt_install(determine_packages(), fatal=True)
# unconfigured keystone service will prevent start of haproxy in some
# circumstances. make sure haproxy runs. LP #1648396
service_stop('keystone')
service_start('haproxy')
if run_in_apache():
disable_unused_apache_sites()
if not git_install_requested():
service_pause('keystone')
if snap_install_requested():
status_set('maintenance', 'Installing keystone snap')
# NOTE(thedac) Setting devmode until LP#1719636 is fixed
install_os_snaps(
get_snaps_install_info_from_origin(
['keystone'],
config('openstack-origin'),
mode='devmode'))
post_snap_install()
service_stop('snap.keystone.*')
else:
# unconfigured keystone service will prevent start of haproxy in some
# circumstances. make sure haproxy runs. LP #1648396
service_stop('keystone')
service_start('haproxy')
if run_in_apache():
disable_unused_apache_sites()
if not git_install_requested():
service_pause('keystone')
status_set('maintenance', 'Git install')
git_install(config('openstack-origin-git'))
unison.ensure_user(user=SSH_USER, group='juju_keystone')
unison.ensure_user(user=SSH_USER, group='keystone')
unison.ensure_user(user=SSH_USER, group=SSH_USER)
unison.ensure_user(user=SSH_USER, group=KEYSTONE_USER)
@hooks.hook('config-changed')
@ -197,11 +214,11 @@ def config_changed():
sync_db_with_multi_ipv6_addresses(config('database'),
config('database-user'))
unison.ensure_user(user=SSH_USER, group='juju_keystone')
unison.ensure_user(user=SSH_USER, group='keystone')
unison.ensure_user(user=SSH_USER, group=SSH_USER)
unison.ensure_user(user=SSH_USER, group=KEYSTONE_USER)
homedir = unison.get_homedir(SSH_USER)
if not os.path.isdir(homedir):
mkdir(homedir, SSH_USER, 'juju_keystone', 0o775)
mkdir(homedir, SSH_USER, SSH_USER, 0o775)
if git_install_requested():
if config_value_changed('openstack-origin-git'):
@ -226,7 +243,8 @@ def config_changed_postupgrade():
# Ensure ssl dir exists and is unison-accessible
ensure_ssl_dir()
check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/'])
if not snap_install_requested():
check_call(['chmod', '-R', 'g+wrx', '/var/lib/keystone/'])
ensure_ssl_dirs()
@ -239,16 +257,23 @@ def config_changed_postupgrade():
# when deployed from source, init scripts aren't installed
if not git_install_requested():
service_pause('keystone')
disable_unused_apache_sites()
CONFIGS.write(WSGI_KEYSTONE_API_CONF)
if WSGI_KEYSTONE_API_CONF in CONFIGS.templates:
CONFIGS.write(WSGI_KEYSTONE_API_CONF)
if not is_unit_paused_set():
restart_pid_check('apache2')
configure_https()
open_port(config('service-port'))
update_nrpe_config()
CONFIGS.write_all()
if snap_install_requested() and not is_unit_paused_set():
service_restart('snap.keystone.*')
initialise_pki()
update_all_identity_relation_units()
@ -276,16 +301,25 @@ def initialise_pki():
ensure_pki_cert_paths()
if not peer_units() or is_ssl_cert_master():
log("Ensuring PKI token certs created", level=DEBUG)
cmd = ['keystone-manage', 'pki_setup', '--keystone-user', 'keystone',
'--keystone-group', 'keystone']
if snap_install_requested():
cmd = ['/snap/bin/keystone-manage', 'pki_setup',
'--keystone-user', KEYSTONE_USER,
'--keystone-group', KEYSTONE_USER]
_log_dir = '/var/snap/keystone/common/log'
else:
cmd = ['keystone-manage', 'pki_setup',
'--keystone-user', KEYSTONE_USER,
'--keystone-group', KEYSTONE_USER]
_log_dir = '/var/log/keystone'
check_call(cmd)
# Ensure logfile has keystone perms since we may have just created it
# with root.
ensure_permissions('/var/log/keystone', user='keystone',
group='keystone', perms=0o744)
ensure_permissions('/var/log/keystone/keystone.log', user='keystone',
group='keystone', perms=0o644)
ensure_permissions(_log_dir, user=KEYSTONE_USER,
group=KEYSTONE_USER, perms=0o744)
ensure_permissions('{}/keystone.log'.format(_log_dir),
user=KEYSTONE_USER, group=KEYSTONE_USER,
perms=0o644)
ensure_pki_dir_permissions()
@ -559,7 +593,7 @@ def send_ssl_sync_request():
@hooks.hook('cluster-relation-joined')
def cluster_joined(rid=None, ssl_sync_request=True):
unison.ssh_authorized_peers(user=SSH_USER,
group='juju_keystone',
group=SSH_USER,
peer_interface='cluster',
ensure_local_user=True)
@ -585,7 +619,7 @@ def cluster_joined(rid=None, ssl_sync_request=True):
@update_certs_if_available
def cluster_changed():
unison.ssh_authorized_peers(user=SSH_USER,
group='juju_keystone',
group=SSH_USER,
peer_interface='cluster',
ensure_local_user=True)
# NOTE(jamespage) re-echo passwords for peer storage
@ -775,7 +809,10 @@ def domain_backend_changed(relation_id=None, unit=None):
db = unitdata.kv()
if restart_nonce != db.get(domain_nonce_key):
if not is_unit_paused_set():
service_restart(keystone_service())
if snap_install_requested():
service_restart('snap.keystone.*')
else:
service_restart(keystone_service())
db.set(domain_nonce_key, restart_nonce)
db.flush()
@ -789,6 +826,10 @@ def configure_https():
# need to write all to ensure changes to the entire request pipeline
# propagate (c-api, haprxy, apache)
CONFIGS.write_all()
# NOTE (thedac): When using snaps, nginx is installed, skip any apache2
# config.
if snap_install_requested():
return
if 'https' in CONFIGS.complete_contexts():
cmd = ['a2ensite', 'openstack_https_frontend']
check_call(cmd)
@ -805,7 +846,7 @@ def upgrade_charm():
status_set('maintenance', 'Installing apt packages')
apt_install(filter_installed_packages(determine_packages()))
unison.ssh_authorized_peers(user=SSH_USER,
group='juju_keystone',
group=SSH_USER,
peer_interface='cluster',
ensure_local_user=True)
@ -842,7 +883,12 @@ def update_nrpe_config():
current_unit = nrpe.get_nagios_unit_name()
nrpe_setup = nrpe.NRPE(hostname=hostname)
nrpe.copy_nrpe_checks()
nrpe.add_init_service_checks(nrpe_setup, services(), current_unit)
_services = []
for service in services():
if service.startswith('snap.'):
service = service.split('.')[1]
_services.append(service)
nrpe.add_init_service_checks(nrpe_setup, _services, current_unit)
nrpe.add_haproxy_checks(nrpe_setup, current_unit)
nrpe_setup.write()

View File

@ -28,6 +28,7 @@ import threading
import time
import urlparse
import uuid
import sys
from itertools import chain
from base64 import b64encode
@ -75,6 +76,9 @@ from charmhelpers.contrib.openstack.utils import (
os_application_version_set,
CompareOpenStackReleases,
reset_os_release,
snap_install_requested,
install_os_snaps,
get_snaps_install_info_from_origin,
)
from charmhelpers.contrib.python.packages import (
@ -160,6 +164,15 @@ BASE_PACKAGES = [
'uuid',
]
BASE_PACKAGES_SNAP = [
'haproxy',
'openssl',
'python-six',
'pwgen',
'unison',
'uuid',
]
VERSION_PACKAGE = 'keystone'
BASE_GIT_PACKAGES = [
@ -175,37 +188,76 @@ BASE_GIT_PACKAGES = [
'zlib1g-dev',
]
BASE_SERVICES = [
'keystone',
]
# ubuntu packages that should not be installed when deploying from git
GIT_PACKAGE_BLACKLIST = [
'keystone',
]
KEYSTONE_CONF = "/etc/keystone/keystone.conf"
KEYSTONE_LOGGER_CONF = "/etc/keystone/logging.conf"
KEYSTONE_CONF_DIR = os.path.dirname(KEYSTONE_CONF)
STORED_PASSWD = "/var/lib/keystone/keystone.passwd"
STORED_TOKEN = "/var/lib/keystone/keystone.token"
STORED_ADMIN_DOMAIN_ID = "/var/lib/keystone/keystone.admin_domain_id"
STORED_DEFAULT_DOMAIN_ID = "/var/lib/keystone/keystone.default_domain_id"
SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd'
SSH_USER = 'juju_keystone'
if snap_install_requested():
SNAP_BASE_DIR = "/snap/keystone/current"
SNAP_COMMON_DIR = "/var/snap/keystone/common"
SNAP_COMMON_ETC_DIR = "{}/etc".format(SNAP_COMMON_DIR)
SNAP_COMMON_KEYSTONE_DIR = "{}/keystone".format(SNAP_COMMON_ETC_DIR)
KEYSTONE_USER = 'root'
KEYSTONE_CONF = ('{}/keystone.conf.d/keystone.conf'
''.format(SNAP_COMMON_KEYSTONE_DIR))
KEYSTONE_CONF_DIR = os.path.dirname(KEYSTONE_CONF)
KEYSTONE_NGINX_SITE_CONF = ("{}/nginx/sites-enabled/keystone-nginx.conf"
"".format(SNAP_COMMON_ETC_DIR))
KEYSTONE_NGINX_CONF = "{}/nginx/nginx.conf".format(SNAP_COMMON_ETC_DIR)
KEYSTONE_LOGGER_CONF = "{}/logging.conf".format(SNAP_COMMON_KEYSTONE_DIR)
SNAP_LIB_DIR = '{}/lib'.format(SNAP_COMMON_DIR)
STORED_PASSWD = "{}/keystone.passwd".format(SNAP_LIB_DIR)
STORED_TOKEN = "{}/keystone.token".format(SNAP_LIB_DIR)
STORED_ADMIN_DOMAIN_ID = ("{}/keystone.admin_domain_id"
"".format(SNAP_LIB_DIR))
STORED_DEFAULT_DOMAIN_ID = ("{}/keystone.default_domain_id"
"".format(SNAP_LIB_DIR))
SERVICE_PASSWD_PATH = '{}/services.passwd'.format(SNAP_LIB_DIR)
SSH_USER_HOME = '/home/{}'.format(SSH_USER)
SYNC_FLAGS_DIR = '{}/juju_sync_flags/'.format(SSH_USER_HOME)
SYNC_DIR = '{}/juju_sync/'.format(SSH_USER_HOME)
SSL_SYNC_ARCHIVE = os.path.join(SYNC_DIR, 'juju-ssl-sync.tar')
SSL_DIR = '{}/juju_ssl/'.format(SNAP_LIB_DIR)
PKI_CERTS_DIR = os.path.join(SSL_DIR, 'pki')
POLICY_JSON = ('{}/keystone.conf.d/policy.json'
''.format(SNAP_COMMON_KEYSTONE_DIR))
BASE_SERVICES = ['snap.keystone.uwsgi', 'snap.keystone.nginx']
APACHE_SSL_DIR = '{}/keystone'.format(SSL_DIR)
else:
APACHE_SSL_DIR = '/etc/apache2/ssl/keystone'
KEYSTONE_USER = 'keystone'
KEYSTONE_CONF = "/etc/keystone/keystone.conf"
KEYSTONE_NGINX_CONF = None
KEYSTONE_NGINX_SITE_CONF = None
KEYSTONE_LOGGER_CONF = "/etc/keystone/logging.conf"
KEYSTONE_CONF_DIR = os.path.dirname(KEYSTONE_CONF)
STORED_PASSWD = "/var/lib/keystone/keystone.passwd"
STORED_TOKEN = "/var/lib/keystone/keystone.token"
STORED_ADMIN_DOMAIN_ID = "/var/lib/keystone/keystone.admin_domain_id"
STORED_DEFAULT_DOMAIN_ID = "/var/lib/keystone/keystone.default_domain_id"
SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd'
SYNC_FLAGS_DIR = '/var/lib/keystone/juju_sync_flags/'
SYNC_DIR = '/var/lib/keystone/juju_sync/'
SSL_SYNC_ARCHIVE = os.path.join(SYNC_DIR, 'juju-ssl-sync.tar')
SSL_DIR = '/var/lib/keystone/juju_ssl/'
PKI_CERTS_DIR = os.path.join(SSL_DIR, 'pki')
POLICY_JSON = '/etc/keystone/policy.json'
BASE_SERVICES = [
'keystone',
]
HAPROXY_CONF = '/etc/haproxy/haproxy.cfg'
APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend'
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/'
SYNC_DIR = '/var/lib/keystone/juju_sync/'
SSL_SYNC_ARCHIVE = os.path.join(SYNC_DIR, 'juju-ssl-sync.tar')
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]
@ -213,7 +265,6 @@ ADMIN_DOMAIN = 'admin_domain'
ADMIN_PROJECT = 'admin'
DEFAULT_DOMAIN = 'default'
SERVICE_DOMAIN = 'service_domain'
POLICY_JSON = '/etc/keystone/policy.json'
TOKEN_FLUSH_CRON_FILE = '/etc/cron.d/keystone-token-flush'
WSGI_KEYSTONE_API_CONF = '/etc/apache2/sites-enabled/wsgi-openstack-api.conf'
UNUSED_APACHE_SITE_FILES = ['/etc/apache2/sites-enabled/keystone.conf',
@ -239,6 +290,28 @@ BASE_RESOURCE_MAP = OrderedDict([
keystone_context.HAProxyContext()],
'services': ['haproxy'],
}),
(KEYSTONE_NGINX_CONF, {
'services': BASE_SERVICES,
'contexts': [keystone_context.KeystoneContext(),
keystone_context.NginxSSLContext(),
context.SharedDBContext(ssl_dir=KEYSTONE_CONF_DIR),
context.PostgresqlDBContext(),
context.SyslogContext(),
keystone_context.HAProxyContext(),
context.BindHostContext(),
context.WorkerConfigContext()],
}),
(KEYSTONE_NGINX_SITE_CONF, {
'services': BASE_SERVICES,
'contexts': [keystone_context.KeystoneContext(),
context.SharedDBContext(ssl_dir=KEYSTONE_CONF_DIR),
context.PostgresqlDBContext(),
context.SyslogContext(),
keystone_context.HAProxyContext(),
keystone_context.NginxSSLContext(),
context.BindHostContext(),
context.WorkerConfigContext()],
}),
(APACHE_CONF, {
'contexts': [keystone_context.ApacheSSLContext()],
'services': ['apache2'],
@ -440,25 +513,48 @@ def resource_map():
else:
resource_map.pop(APACHE_24_CONF)
if run_in_apache():
if snap_install_requested():
if APACHE_CONF in resource_map:
resource_map.pop(APACHE_CONF)
if APACHE_24_CONF in resource_map:
resource_map.pop(APACHE_24_CONF)
else:
if KEYSTONE_NGINX_CONF in resource_map:
resource_map.pop(KEYSTONE_NGINX_CONF)
if KEYSTONE_NGINX_SITE_CONF in resource_map:
resource_map.pop(KEYSTONE_NGINX_SITE_CONF)
if snap_install_requested():
for cfile in resource_map:
svcs = resource_map[cfile]['services']
if 'apache2' in svcs:
svcs.remove('apache2')
if 'keystone' in svcs:
svcs.remove('keystone')
svcs.append('snap.keystone.nginx')
svcs.append('snap.keystone.uwsgi')
if run_in_apache():
if not snap_install_requested():
for cfile in resource_map:
svcs = resource_map[cfile]['services']
if 'keystone' in svcs:
svcs.remove('keystone')
if 'apache2' not in svcs:
svcs.append('apache2')
admin_script = os.path.join(git_determine_usr_bin(),
"keystone-wsgi-admin")
public_script = os.path.join(git_determine_usr_bin(),
"keystone-wsgi-public")
resource_map[WSGI_KEYSTONE_API_CONF] = {
'contexts': [
context.WSGIWorkerConfigContext(name="keystone",
admin_script=admin_script,
public_script=public_script),
keystone_context.KeystoneContext()],
'services': ['apache2']
}
admin_script = os.path.join(git_determine_usr_bin(),
"keystone-wsgi-admin")
public_script = os.path.join(git_determine_usr_bin(),
"keystone-wsgi-public")
resource_map[WSGI_KEYSTONE_API_CONF] = {
'contexts': [
context.WSGIWorkerConfigContext(
name="keystone",
admin_script=admin_script,
public_script=public_script),
keystone_context.KeystoneContext()],
'services': ['apache2']
}
return resource_map
@ -495,7 +591,8 @@ def run_in_apache():
"""Return true if keystone API is run under apache2 with mod_wsgi in
this release.
"""
return CompareOpenStackReleases(os_release('keystone')) >= 'liberty'
return (CompareOpenStackReleases(os_release('keystone')) >= 'liberty' and
not snap_install_requested())
def disable_unused_apache_sites():
@ -548,13 +645,16 @@ def api_port(service):
def determine_packages():
# currently all packages match service names
packages = set(services()).union(BASE_PACKAGES)
if git_install_requested():
packages |= set(BASE_GIT_PACKAGES)
packages -= set(GIT_PACKAGE_BLACKLIST)
if run_in_apache():
packages.add('libapache2-mod-wsgi')
return sorted(packages)
if snap_install_requested():
return sorted(BASE_PACKAGES_SNAP)
else:
packages = set(services()).union(BASE_PACKAGES)
if git_install_requested():
packages |= set(BASE_GIT_PACKAGES)
packages -= set(GIT_PACKAGE_BLACKLIST)
if run_in_apache():
packages.add('libapache2-mod-wsgi')
return sorted(packages)
def save_script_rc():
@ -578,16 +678,28 @@ def do_openstack_upgrade(configs):
new_os_rel = get_os_codename_install_source(new_src)
log('Performing OpenStack upgrade to %s.' % (new_os_rel))
configure_installation_source(new_src)
apt_update()
dpkg_opts = [
'--option', 'Dpkg::Options::=--force-confnew',
'--option', 'Dpkg::Options::=--force-confdef',
]
apt_upgrade(options=dpkg_opts, fatal=True, dist=True)
reset_os_release()
apt_install(packages=determine_packages(), options=dpkg_opts, fatal=True)
if not snap_install_requested():
configure_installation_source(new_src)
apt_update()
dpkg_opts = [
'--option', 'Dpkg::Options::=--force-confnew',
'--option', 'Dpkg::Options::=--force-confdef',
]
apt_upgrade(options=dpkg_opts, fatal=True, dist=True)
reset_os_release()
apt_install(packages=determine_packages(),
options=dpkg_opts, fatal=True)
else:
# TODO: Add support for upgrade from deb->snap
# NOTE(thedac): Setting devmode until LP#1719636 is fixed
install_os_snaps(
get_snaps_install_info_from_origin(
['keystone'],
new_src,
mode='devmode'),
refresh=True)
post_snap_install()
reset_os_release()
# set CONFIGS to load templates from new release and regenerate config
configs.set_release(openstack_release=new_os_rel)
@ -625,13 +737,26 @@ def keystone_service():
def migrate_database():
"""Runs keystone-manage to initialize a new database or migrate existing"""
log('Migrating the keystone database.', level=INFO)
service_stop(keystone_service())
if snap_install_requested():
service_stop('snap.keystone.*')
else:
service_stop(keystone_service())
# NOTE(jamespage) > icehouse creates a log file as root so use
# sudo to execute as keystone otherwise keystone won't start
# afterwards.
cmd = ['sudo', '-u', 'keystone', 'keystone-manage', 'db_sync']
# NOTE(coreycb): Can just use keystone-manage when snap has alias support.
# Also can run as keystone once snap has drop privs support.
if snap_install_requested():
cmd = ['/snap/bin/keystone-manage', 'db_sync']
else:
cmd = ['sudo', '-u', 'keystone', 'keystone-manage', 'db_sync']
subprocess.check_output(cmd)
service_start(keystone_service())
if snap_install_requested():
service_start('snap.keystone.nginx')
service_start('snap.keystone.uwsgi')
else:
service_start(keystone_service())
time.sleep(10)
peer_store('db-initialised', 'True')
@ -646,8 +771,10 @@ def get_local_endpoint(api_suffix=None):
"""Returns the URL for the local end-point bypassing haproxy/ssl"""
if not api_suffix:
api_suffix = get_api_suffix()
keystone_port = determine_api_port(api_port('keystone-admin'),
singlenode_mode=True)
if config('prefer-ipv6'):
ipv6_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
local_endpoint = 'http://[{}]:{}/{}/'.format(
@ -889,6 +1016,7 @@ def create_user(name, password, tenant=None, domain=None):
def get_manager(api_version=None):
"""Return a keystonemanager for the correct API version"""
set_python_path()
from manager import get_keystone_manager
return get_keystone_manager(get_local_endpoint(), get_admin_token(),
api_version)
@ -1013,10 +1141,23 @@ def get_api_version():
return api_version
def set_python_path():
""" Set the Python path to include snap installed python libraries
The charm itself requires access to the python client. When installed as a
snap the client libraries are in /snap/$SNAP/common/lib/python2.7. This
function sets the python path to allow clients to be imported from snap
installs.
"""
if snap_install_requested():
sys.path.append(determine_python_path())
def ensure_initial_admin(config):
# Allow retry on fail since leader may not be ready yet.
# NOTE(hopem): ks client may not be installed at module import time so we
# use this wrapped approach instead.
set_python_path()
try:
from keystoneclient.apiclient.exceptions import InternalServerError
except:
@ -1192,10 +1333,10 @@ def ensure_ssl_dirs():
"""Ensure unison has access to these dirs."""
for path in [SYNC_FLAGS_DIR, SYNC_DIR]:
if not os.path.isdir(path):
mkdir(path, SSH_USER, 'juju_keystone', 0o775)
mkdir(path, SSH_USER, KEYSTONE_USER, 0o775)
else:
ensure_permissions(path, user=SSH_USER, group='keystone',
perms=0o755)
ensure_permissions(path, user=SSH_USER, group=KEYSTONE_USER,
perms=0o775)
def ensure_permissions(path, user=None, group=None, perms=None, recurse=False,
@ -1293,7 +1434,7 @@ def create_peer_service_actions(action, services):
(local_unit().replace('/', '-'),
service.strip(), action))
log("Creating action %s" % (flagfile), level=DEBUG)
write_file(flagfile, content='', owner=SSH_USER, group='keystone',
write_file(flagfile, content='', owner=SSH_USER, group=KEYSTONE_USER,
perms=0o744)
@ -1302,7 +1443,7 @@ def create_peer_actions(actions):
action = "%s.%s" % (local_unit().replace('/', '-'), action)
flagfile = os.path.join(SYNC_FLAGS_DIR, action)
log("Creating action %s" % (flagfile), level=DEBUG)
write_file(flagfile, content='', owner=SSH_USER, group='keystone',
write_file(flagfile, content='', owner=SSH_USER, group=KEYSTONE_USER,
perms=0o744)
@ -1315,7 +1456,7 @@ def unison_sync(paths_to_sync):
"""
log('Synchronizing CA (%s) to all peers.' % (', '.join(paths_to_sync)),
level=INFO)
keystone_gid = grp.getgrnam('keystone').gr_gid
keystone_gid = grp.getgrnam(KEYSTONE_USER).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,
@ -1462,8 +1603,8 @@ def stage_paths_for_sync(paths):
log("Path '%s' does not exist - not adding to sync "
"tarball" % (path), level=INFO)
ensure_permissions(SYNC_DIR, user=SSH_USER, group='keystone',
perms=0o755, recurse=True)
ensure_permissions(SYNC_DIR, user=SSH_USER, group=KEYSTONE_USER,
perms=0o775, recurse=True)
def is_pki_enabled():
@ -1481,20 +1622,21 @@ def ensure_pki_cert_paths():
if not os.path.exists(p)]
if not_exists:
log("Configuring token signing cert paths", level=DEBUG)
perms = 0o755
perms = 0o775
for path in not_exists:
if not os.path.isdir(path):
mkdir(path=path, owner=SSH_USER, group='keystone', perms=perms)
mkdir(path=path, owner=SSH_USER, group=KEYSTONE_USER,
perms=perms)
else:
# Ensure accessible by ssh user and group (for sync).
ensure_permissions(path, user=SSH_USER, group='keystone',
ensure_permissions(path, user=SSH_USER, group=KEYSTONE_USER,
perms=perms)
def ensure_pki_dir_permissions():
# Ensure accessible by unison user and group (for sync).
ensure_permissions(PKI_CERTS_DIR, user=SSH_USER, group='keystone',
perms=0o755, recurse=True)
ensure_permissions(PKI_CERTS_DIR, user=SSH_USER, group=KEYSTONE_USER,
perms=0o775, recurse=True)
def update_certs_if_available(f):
@ -1511,7 +1653,8 @@ def update_certs_if_available(f):
fd.extractall(path='/')
for syncfile in files:
ensure_permissions(syncfile, user='keystone', group='keystone',
ensure_permissions(syncfile, user=KEYSTONE_USER,
group=KEYSTONE_USER,
perms=0o744, recurse=True)
# Mark as complete
@ -1572,7 +1715,7 @@ def synchronize_ca(fatal=False):
return {}
if not os.path.isdir(SYNC_FLAGS_DIR):
mkdir(SYNC_FLAGS_DIR, SSH_USER, 'keystone', 0o775)
mkdir(SYNC_FLAGS_DIR, SSH_USER, KEYSTONE_USER, 0o775)
for action, services in peer_service_actions.iteritems():
create_peer_service_actions(action, set(services))
@ -1710,15 +1853,17 @@ def force_ssl_sync():
def ensure_ssl_dir():
"""Ensure juju ssl dir exists and is unsion read/writable."""
perms = 0o755
# NOTE(thedac) Snap service restarts will override permissions
# in SNAP_LIB_DIR including SSL_DIR
perms = 0o775
if not os.path.isdir(SSL_DIR):
mkdir(SSL_DIR, SSH_USER, 'keystone', perms)
mkdir(SSL_DIR, SSH_USER, KEYSTONE_USER, perms)
else:
ensure_permissions(SSL_DIR, user=SSH_USER, group='keystone',
ensure_permissions(SSL_DIR, user=SSH_USER, group=KEYSTONE_USER,
perms=perms)
def get_ca(user='keystone', group='keystone'):
def get_ca(user=KEYSTONE_USER, group=KEYSTONE_USER):
"""Initialize a new CA object if one hasn't already been loaded.
This will create a new CA or load an existing one.
@ -2273,7 +2418,6 @@ def is_db_ready(use_current_context=False, db_rel=None):
def determine_usr_bin():
"""Return the /usr/bin path for Apache2 vhost config.
The /usr/bin path will be located in the virtualenv if the charm
is configured to deploy keystone from source.
"""
@ -2286,16 +2430,20 @@ def determine_usr_bin():
def determine_python_path():
"""Return the python-path for Apache2 vhost config.
"""Return the python-path
Returns None unless the charm is configured to deploy keystone from source,
in which case the path of the virtualenv's site-packages is returned.
Determine if git or snap installed and return the appropriate python path.
Returns None unless the charm if neither condition is true.
:returns: string python path or None
"""
if git_install_requested():
_python_path = 'lib/python2.7/site-packages'
if snap_install_requested():
return os.path.join(SNAP_BASE_DIR, _python_path)
elif git_install_requested():
projects_yaml = config('openstack-origin-git')
projects_yaml = git_default_repos(projects_yaml)
return os.path.join(git_pip_venv_dir(projects_yaml),
'lib/python2.7/site-packages')
return os.path.join(git_pip_venv_dir(projects_yaml), _python_path)
else:
return None
@ -2327,10 +2475,12 @@ def git_pre_install():
add_user_to_group('keystone', 'keystone')
for d in dirs:
mkdir(d, owner='keystone', group='keystone', perms=0o755, force=False)
mkdir(d, owner=KEYSTONE_USER, group=KEYSTONE_USER, perms=0o755,
force=False)
for l in logs:
write_file(l, '', owner='keystone', group='keystone', perms=0o600)
write_file(l, '', owner=KEYSTONE_USER, group=KEYSTONE_USER,
perms=0o600)
def git_post_install(projects_yaml):
@ -2379,9 +2529,9 @@ def git_post_install(projects_yaml):
'process_name': 'keystone',
'executable_name': os.path.join(bin_dir, 'keystone-all'),
'config_files': ['/etc/keystone/keystone.conf'],
'log_file': '/var/log/keystone/keystone.log',
}
keystone_context['log_file'] = '/var/log/keystone/keystone.log'
templates_dir = 'hooks/charmhelpers/contrib/openstack/templates'
templates_dir = os.path.join(charm_dir(), templates_dir)
render('git.upstart', '/etc/init/keystone.conf', keystone_context,
@ -2509,3 +2659,16 @@ def _pause_resume_helper(f, configs):
f(assess_status_func(configs),
services=services(),
ports=determine_ports())
def post_snap_install():
""" Specific steps post snap install for this charm
"""
log("Perfoming post snap install tasks", INFO)
PASTE_SRC = ('{}/etc/keystone/keystone-paste.ini'
''.format(SNAP_BASE_DIR))
PASTE_DST = '{}/keystone-paste.ini'.format(SNAP_COMMON_KEYSTONE_DIR)
if os.path.exists(PASTE_SRC):
log("Perfoming post snap install tasks", INFO)
shutil.copy(PASTE_SRC, PASTE_DST)

View File

@ -0,0 +1,46 @@
server {
listen {{ public_port }};
access_log /var/snap/keystone/common/log/nginx-access.log;
error_log /var/snap/keystone/common/log/nginx-error.log;
location / {
include /snap/keystone/current/usr/conf/uwsgi_params;
uwsgi_param SCRIPT_NAME '';
uwsgi_pass unix:///var/snap/keystone/common/run/public.sock;
}
}
server {
listen {{ admin_port }};
access_log /var/snap/keystone/common/log/nginx-access.log;
error_log /var/snap/keystone/common/log/nginx-error.log;
location / {
include /snap/keystone/current/usr/conf/uwsgi_params;
uwsgi_param SCRIPT_NAME '';
uwsgi_pass unix:///var/snap/keystone/common/run/admin.sock;
}
}
{% if ssl -%}
{% if endpoints -%}
{% for ep in endpoints -%}
server {
listen {{ endpoints[ep]['ext'] }} {% if ssl -%}ssl{% endif -%};
{% if ssl -%}
ssl on;
ssl_certificate /var/snap/keystone/common/lib/juju_ssl/{{ namespace }}/cert_{{ endpoints[ep]['address'] }};
ssl_certificate_key /var/snap/keystone/common/lib/juju_ssl/{{ namespace }}/key_{{ endpoints[ep]['address'] }};
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!RC4:!MD5:!aNULL:!eNULL:!EXP:!LOW:!MEDIUM;
server_name {{ endpoints[ep]['address'] }};
{% endif -%}
access_log /var/snap/keystone/common/log/nginx-access.log;
error_log /var/snap/keystone/common/log/nginx-error.log;
location / {
include /snap/keystone/current/usr/conf/uwsgi_params;
uwsgi_param SCRIPT_NAME '';
uwsgi_pass unix:///var/snap/keystone/common/run/{{ endpoints[ep]['socket'] }}.sock;
}
}
{% endfor -%}
{% endif -%}
{% endif -%}

View File

@ -0,0 +1,117 @@
# ocata
###############################################################################
# [ WARNING ]
# Configuration file maintained by Juju. Local changes may be overwritten.
###############################################################################
[DEFAULT]
admin_token = {{ token }}
use_syslog = {{ use_syslog }}
log_config_append = {{ log_config }}
debug = {{ debug }}
public_endpoint = {{ public_endpoint }}
admin_endpoint = {{ admin_endpoint }}
[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 = {{ identity_backend }}
{% if default_domain_id -%}
default_domain_id = {{ default_domain_id }}
{% endif -%}
{% if api_version == 3 -%}
domain_specific_drivers_enabled = True
domain_config_dir = {{ domain_config_dir }}
{% endif -%}
[credential]
driver = sql
[trust]
driver = sql
[os_inherit]
[catalog]
driver = sql
[endpoint_filter]
[token]
driver = sql
{% 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 -%}
expiration = {{ token_expiration }}
{% include "parts/section-signing" %}
[cache]
[policy]
driver = sql
[assignment]
driver = {{ assignment_backend }}
[oauth1]
[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 = {{ paste_config_file }}
[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 -%}
{% if api_version == 3 -%}
[resource]
admin_project_domain_name = {{ admin_domain_name }}
admin_project_name = admin
{% endif -%}

View File

@ -0,0 +1,48 @@
# ocata
[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,production
[handler_production]
class=handlers.SysLogHandler
{% if log_level -%}
level={{ log_level }}
{% else -%}
level=ERROR
{% endif -%}
formatter=normal_with_name
args=(('/dev/log'), handlers.SysLogHandler.LOG_USER)
[handler_file]
class=FileHandler
level=DEBUG
formatter=normal_with_name
args=('{{ log_file }}', '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

View File

@ -0,0 +1,39 @@
user root root;
worker_processes {{ workers }};
pid /var/snap/keystone/common/run/nginx.pid;
events {
worker_connections 768;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /snap/keystone/current/usr/conf/mime.types;
default_type application/octet-stream;
##
# Logging Settings
##
access_log /var/snap/keystone/common/log/nginx-access.log;
error_log /var/snap/keystone/common/log/nginx-error.log;
##
# Gzip Settings
##
gzip on;
gzip_disable "msie6";
include /var/snap/keystone/common/etc/nginx/conf.d/*.conf;
include /var/snap/keystone/common/etc/nginx/sites-enabled/*;
}

View File

@ -9,6 +9,9 @@ charm-tools>=2.0.0
requests==2.6.0
# BEGIN: Amulet OpenStack Charm Helper Requirements
# Liberty client lower constraints
# The websocket-client issue should be resolved in the jujulib/theblues
# Temporarily work around it
websocket-client<=0.40.0
amulet>=1.14.3,<2.0
bundletester>=0.6.1,<1.0
python-ceilometerclient>=1.5.0

View File

@ -45,13 +45,15 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
DEFAULT_DOMAIN = 'default'
def __init__(self, series=None, openstack=None,
source=None, git=False, stable=False):
source=None, git=False, stable=False, snap_source=None):
"""Deploy the entire test environment."""
super(KeystoneBasicDeployment, self).__init__(series, openstack,
source, stable)
self.keystone_num_units = 3
self.keystone_api_version = 2
self.git = git
self._setup_test_object(snap_source)
self._add_services()
self._add_relations()
self._configure_services()
@ -64,14 +66,49 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
self.d.sentry.wait()
self._initialize_tests()
def _assert_services(self, should_run):
if self.is_liberty_or_newer():
services = ("apache2", "haproxy")
def _setup_test_object(self, snap_source):
self.snap_source = snap_source
if self.snap_source:
self.config_base = '/var/snap/keystone/common'
self.keystone_conf = ('{}/etc/keystone/keystone.conf.d/'
'keystone.conf'.format(self.config_base))
self.process_services = ["haproxy", "nginx", "uwsgi"]
self.init_services = ["snap.keystone.nginx",
"snap.keystone.uwsgi"]
self.no_origin = ['keystone']
self.keystone_config = {'openstack-origin': self.snap_source}
self.pymysql = '+pymysql'
self.policy_json = ('{}/etc/keystone/keystone.conf.d/policy.json'
''.format(self.config_base))
self.logging_config = ('{}/etc/keystone/logging.conf'
''.format(self.config_base))
self.log_file = '{}/log/keystone.log'.format(self.config_base)
self.services_to_configs = {'uwsgi': self.keystone_conf}
else:
services = ("keystone-all", "apache2", "haproxy")
self.config_base = ''
self.keystone_conf = '/etc/keystone/keystone.conf'
self.no_origin = []
self.keystone_config = {}
self.pymysql = ''
self.policy_json = ('{}/etc/keystone/policy.json'
''.format(self.config_base))
self.logging_config = ('{}/etc/keystone/logging.conf'
''.format(self.config_base))
self.log_file = '/var/log/keystone/keystone.log'
if self.is_liberty_or_newer():
self.process_services = ["apache2", "haproxy"]
self.init_services = ['apache2']
self.services_to_configs = {'apache2': self.keystone_conf}
else:
self.process_services = ["keystone-all", "apache2", "haproxy"]
self.init_services = ['keystone']
self.services_to_configs = {'keystone-all': self.keystone_conf}
def _assert_services(self, should_run):
for unit in self.keystone_sentries:
u.get_unit_process_ids(
{unit: services}, expect_success=should_run)
{unit: self.process_services}, expect_success=should_run)
def _add_services(self):
"""Add services
@ -86,8 +123,8 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
{'name': 'rabbitmq-server'}, # satisfy wrkload stat
{'name': 'cinder'},
]
super(KeystoneBasicDeployment, self)._add_services(this_service,
other_services)
super(KeystoneBasicDeployment, self)._add_services(
this_service, other_services, no_origin=self.no_origin)
def _add_relations(self):
"""Add all of the relations for the services."""
@ -99,11 +136,11 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
def _configure_services(self):
"""Configure all of the services."""
keystone_config = {
self.keystone_config.update({
'admin-password': 'openstack',
'admin-token': 'ubuntutesting',
'preferred-api-version': self.keystone_api_version,
}
})
if self.git:
amulet_http_proxy = os.environ.get('AMULET_HTTP_PROXY')
@ -129,7 +166,7 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
'http_proxy': amulet_http_proxy,
'https_proxy': amulet_http_proxy,
}
keystone_config['openstack-origin-git'] = \
self.keystone_config['openstack-origin-git'] = \
yaml.dump(openstack_origin_git)
pxc_config = {
@ -143,7 +180,7 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
'overwrite': 'true',
'ephemeral-unmount': '/mnt'}
configs = {
'keystone': keystone_config,
'keystone': self.keystone_config,
'percona-cluster': pxc_config,
'cinder': cinder_config,
}
@ -326,11 +363,8 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
else:
services.update({self.cinder_sentry: ['cinder-api']})
if self.is_liberty_or_newer():
for i in range(0, self.keystone_num_units):
services.update({self.keystone_sentries[i]: ['apache2']})
else:
services.update({self.keystone_sentries[0]: ['keystone']})
for i in range(0, self.keystone_num_units):
services.update({self.keystone_sentries[i]: self.init_services})
ret = u.validate_services_by_name(services)
if ret:
@ -703,22 +737,22 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
"""Verify the data in the keystone config file,
comparing some of the variables vs relation data."""
u.log.debug('Checking keystone config file...')
conf = '/etc/keystone/keystone.conf'
ks_ci_rel = self.keystone_sentries[0].relation(
'identity-service',
'cinder:identity-service')
my_ks_rel = self.pxc_sentry.relation('shared-db',
'keystone:shared-db')
db_uri = "mysql://{}:{}@{}/{}".format('keystone',
my_ks_rel['password'],
my_ks_rel['db_host'],
'keystone')
db_uri = "mysql{}://{}:{}@{}/{}".format(self.pymysql,
'keystone',
my_ks_rel['password'],
my_ks_rel['db_host'],
'keystone')
expected = {
'DEFAULT': {
'debug': 'False',
'admin_token': ks_ci_rel['admin_token'],
'use_syslog': 'False',
'log_config_append': '/etc/keystone/logging.conf',
'log_config_append': (self.logging_config),
'public_endpoint': u.valid_url, # get specific
'admin_endpoint': u.valid_url, # get specific
},
@ -756,7 +790,8 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
for unit in self.keystone_sentries:
for section, pairs in expected.iteritems():
ret = u.validate_config_data(unit, conf, section, pairs)
ret = u.validate_config_data(unit, self.keystone_conf, section,
pairs)
if ret:
message = "keystone config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
@ -768,7 +803,6 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
return
u.log.debug('Checking keystone v3 policy.json file')
self.set_api_version(3)
conf = '/etc/keystone/policy.json'
ks_ci_rel = self.keystone_sentries[0].relation(
'identity-service',
'cinder:identity-service')
@ -804,7 +838,7 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
}
for unit in self.keystone_sentries:
data = json.loads(unit.file_contents(conf))
data = json.loads(unit.file_contents(self.policy_json))
ret = u._validate_dict_data(expected, data)
if ret:
message = "keystone policy.json error: {}".format(ret)
@ -815,7 +849,6 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
def test_302_keystone_logging_config(self):
"""Verify the data in the keystone logging config file"""
u.log.debug('Checking keystone config file...')
conf = '/etc/keystone/logging.conf'
expected = {
'logger_root': {
'level': 'WARNING',
@ -826,13 +859,14 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
},
'handler_file': {
'level': 'DEBUG',
'args': "('/var/log/keystone/keystone.log', 'a')"
'args': "('{}', 'a')".format(self.log_file)
}
}
for unit in self.keystone_sentries:
for section, pairs in expected.iteritems():
ret = u.validate_config_data(unit, conf, section, pairs)
ret = u.validate_config_data(unit, self.logging_config,
section, pairs)
if ret:
message = "keystone logging config error: {}".format(ret)
amulet.raise_status(amulet.FAIL, msg=message)
@ -847,19 +881,13 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
set_default = {'use-syslog': 'False'}
set_alternate = {'use-syslog': 'True'}
# Services which are expected to restart upon config change,
# and corresponding config files affected by the change
if self.is_liberty_or_newer():
services = {'apache2': '/etc/keystone/keystone.conf'}
else:
services = {'keystone-all': '/etc/keystone/keystone.conf'}
# Make config change, check for service restarts
u.log.debug('Making config change on {}...'.format(juju_service))
mtime = u.get_sentry_time(sentry)
self.d.configure(juju_service, set_alternate)
sleep_time = 30
for s, conf_file in services.iteritems():
for s, conf_file in self.services_to_configs.iteritems():
u.log.debug("Checking that service restarted: {}".format(s))
if not u.validate_service_config_changed(sentry, mtime, s,
conf_file,

View File

@ -0,0 +1,26 @@
#!/usr/bin/env python
#
# Copyright 2016 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Amulet tests on a basic keystone deployment on xenial-ocata."""
from basic_deployment import KeystoneBasicDeployment
if __name__ == '__main__':
deployment = KeystoneBasicDeployment(series='xenial',
openstack='cloud:xenial-ocata',
snap_source='snap:ocata/edge',
source='cloud:xenial-updates/ocata')
deployment.run_tests()

View File

@ -17,9 +17,12 @@ from mock import patch
from test_utils import CharmTestCase
with patch('actions.hooks.keystone_utils.register_configs') as configs:
configs.return_value = 'test-config'
import actions.actions
with patch('actions.hooks.charmhelpers.contrib.openstack.utils.'
'snap_install_requested') as snap_install_requested, \
patch('actions.hooks.keystone_utils.register_configs') as configs:
snap_install_requested.return_value = False
configs.return_value = 'test-config'
import actions.actions
class PauseTestCase(CharmTestCase):

View File

@ -24,13 +24,17 @@ mock_apt.apt_pkg = MagicMock()
# NOTE(hopem): we have to mock hooks.charmhelpers (not charmhelpers)
# otherwise the mock is not applied to action.hooks.*
with patch('hooks.charmhelpers.contrib.hardening.harden.harden') as mock_dec:
with patch('hooks.charmhelpers.contrib.hardening.harden.harden') as mock_dec, \
patch('hooks.charmhelpers.contrib.openstack.utils.'
'snap_install_requested') as snap_install_requested, \
patch('hooks.keystone_utils.register_configs') as register_configs, \
patch('hooks.keystone_utils.os_release') as os_release:
snap_install_requested.return_value = False
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
lambda *args, **kwargs: f(*args, **kwargs))
with patch('hooks.keystone_utils.register_configs') as register_configs:
with patch('hooks.keystone_utils.os_release') as os_release:
os_release.return_value = 'juno'
import git_reinstall
os_release.return_value = 'juno'
import git_reinstall
from test_utils import (
CharmTestCase

View File

@ -17,7 +17,10 @@ import os
os.environ['JUJU_UNIT_NAME'] = 'keystone'
import openstack_upgrade
with patch('charmhelpers.contrib.openstack.utils'
'.snap_install_requested') as snap_install_requested:
snap_install_requested.return_value = False
import openstack_upgrade
from test_utils import (
CharmTestCase

View File

@ -14,8 +14,12 @@
import os
import keystone_context as context
from mock import patch, MagicMock
with patch('charmhelpers.contrib.openstack.'
'utils.snap_install_requested') as snap_install_requested:
snap_install_requested.return_value = False
import keystone_utils # noqa
import keystone_context as context
from test_utils import (
CharmTestCase
@ -166,7 +170,9 @@ class TestKeystoneContexts(CharmTestCase):
ctxt = context.KeystoneLoggingContext()
mock_config.return_value = None
self.assertEqual({'log_level': None}, ctxt())
self.assertEqual({'log_level': None,
'log_file': '/var/log/keystone/keystone.log'},
ctxt())
@patch.object(context, 'is_elected_leader')
def test_token_flush_context(self, mock_is_elected_leader):

View File

@ -25,7 +25,10 @@ from test_utils import CharmTestCase
sys.modules['apt'] = MagicMock()
os.environ['JUJU_UNIT_NAME'] = 'keystone'
with patch('charmhelpers.core.hookenv.config') as config:
with patch('charmhelpers.core.hookenv.config') as config, \
patch('charmhelpers.contrib.openstack.'
'utils.snap_install_requested') as snap_install_requested:
snap_install_requested.return_value = False
config.return_value = 'keystone'
import keystone_utils as utils
@ -69,6 +72,7 @@ TO_PATCH = [
# charmhelpers.contrib.openstack.utils
'configure_installation_source',
'git_install_requested',
'snap_install_requested',
# charmhelpers.contrib.openstack.ip
'resolve_address',
# charmhelpers.contrib.openstack.ha.utils
@ -120,6 +124,7 @@ class KeystoneRelationTests(CharmTestCase):
super(KeystoneRelationTests, self).setUp(hooks, TO_PATCH)
self.config.side_effect = self.test_config.get
self.ssh_user = 'juju_keystone'
self.snap_install_requested.return_value = False
@patch.object(utils, 'os_release')
@patch.object(utils, 'git_install_requested')

View File

@ -19,7 +19,10 @@ from base64 import b64encode
import subprocess
os.environ['JUJU_UNIT_NAME'] = 'keystone'
with patch('charmhelpers.core.hookenv.config') as config:
with patch('charmhelpers.core.hookenv.config') as config, \
patch('charmhelpers.contrib.openstack.'
'utils.snap_install_requested') as snap_install_requested:
snap_install_requested.return_value = False
import keystone_utils as utils
TO_PATCH = [
@ -49,6 +52,7 @@ TO_PATCH = [
'service_restart',
'service_stop',
'service_start',
'snap_install_requested',
'relation_get',
'relation_set',
'relation_ids',
@ -86,6 +90,7 @@ class TestKeystoneUtils(CharmTestCase):
def setUp(self):
super(TestKeystoneUtils, self).setUp(utils, TO_PATCH)
self.config.side_effect = self.test_config.get
self.snap_install_requested.return_value = False
self.ctxt = MagicMock()
self.rsc_map = {
@ -138,6 +143,7 @@ class TestKeystoneUtils(CharmTestCase):
@patch('charmhelpers.contrib.openstack.utils.config')
def test_determine_packages(self, _config):
self.os_release.return_value = 'havana'
self.snap_install_requested.return_value = False
_config.return_value = None
result = utils.determine_packages()
ex = utils.BASE_PACKAGES + ['keystone', 'python-keystoneclient']