305 lines
9.8 KiB
Python
Executable File
305 lines
9.8 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# Copyright 2014 Canonical Ltd. released under AGPL
|
|
#
|
|
# Authors:
|
|
# Tycho Andersen <tycho.andersen@canonical.com>
|
|
#
|
|
|
|
# This file is part of the glance-simplestreams sync charm.
|
|
#
|
|
# The glance-simplestreams sync charm is free software: you can
|
|
# redistribute it and/or modify it under the terms of the GNU Affero General
|
|
# Public License as published by the Free Software Foundation, either
|
|
# version 3 of the License, or (at your option) any later version.
|
|
#
|
|
# The charm 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 Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this charm. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import glob
|
|
import os
|
|
import sys
|
|
import shutil
|
|
|
|
from charmhelpers.fetch import add_source, apt_install, apt_update
|
|
from charmhelpers.core import hookenv
|
|
from charmhelpers.payload.execd import execd_preinstall
|
|
|
|
from charmhelpers.contrib.openstack.context import (AMQPContext,
|
|
IdentityServiceContext,
|
|
OSContextGenerator)
|
|
from charmhelpers.contrib.openstack.utils import get_os_codename_package
|
|
from charmhelpers.contrib.openstack.templating import OSConfigRenderer
|
|
|
|
from charmhelpers.contrib.charmsupport import nrpe
|
|
|
|
CONF_FILE_DIR = '/etc/glance-simplestreams-sync'
|
|
USR_SHARE_DIR = '/usr/share/glance-simplestreams-sync'
|
|
|
|
MIRRORS_CONF_FILE_NAME = os.path.join(CONF_FILE_DIR, 'mirrors.yaml')
|
|
ID_CONF_FILE_NAME = os.path.join(CONF_FILE_DIR, 'identity.yaml')
|
|
|
|
SYNC_SCRIPT_NAME = "glance-simplestreams-sync.py"
|
|
SCRIPT_WRAPPER_NAME = "glance-simplestreams-sync.sh"
|
|
|
|
CRON_D = '/etc/cron.d/'
|
|
CRON_JOB_FILENAME = 'glance_simplestreams_sync'
|
|
CRON_POLL_FILENAME = 'glance_simplestreams_sync_fastpoll'
|
|
CRON_POLL_FILEPATH = os.path.join(CRON_D, CRON_POLL_FILENAME)
|
|
|
|
ERR_FILE_EXISTS = 17
|
|
|
|
hooks = hookenv.Hooks()
|
|
|
|
|
|
class MultipleImageModifierSubordinatesIsNotSupported(Exception):
|
|
"""Raise this if multiple image-modifier subordinates are related to
|
|
this charm.
|
|
"""
|
|
|
|
|
|
class UnitNameContext(OSContextGenerator):
|
|
"""Simple context to generate local unit_name"""
|
|
def __call__(self):
|
|
return {'unit_name': hookenv.local_unit()}
|
|
|
|
|
|
class MirrorsConfigServiceContext(OSContextGenerator):
|
|
"""Context for mirrors.yaml template.
|
|
|
|
Uses image-modifier relation if available to set
|
|
modify_hook_scripts config value.
|
|
|
|
"""
|
|
interfaces = ['simplestreams-image-service']
|
|
|
|
def __call__(self):
|
|
hookenv.log("Generating template ctxt for simplestreams-image-service")
|
|
config = hookenv.config()
|
|
|
|
modify_hook_scripts = []
|
|
image_modifiers = hookenv.relations_of_type('image-modifier')
|
|
if len(image_modifiers) > 1:
|
|
raise MultipleImageModifierSubordinatesIsNotSupported()
|
|
|
|
if len(image_modifiers) == 1:
|
|
im = image_modifiers[0]
|
|
try:
|
|
modify_hook_scripts.append(im['script-path'])
|
|
|
|
except KeyError as ke:
|
|
hookenv.log('relation {} yielded '
|
|
'exception {} - ignoring.'.format(repr(im),
|
|
repr(ke)))
|
|
|
|
# default no-op so that None still means "missing" for config
|
|
# validation (see elsewhere)
|
|
if len(modify_hook_scripts) == 0:
|
|
modify_hook_scripts.append('/bin/true')
|
|
|
|
return dict(mirror_list=config['mirror_list'],
|
|
modify_hook_scripts=', '.join(modify_hook_scripts),
|
|
name_prefix=config['name_prefix'],
|
|
content_id_template=config['content_id_template'],
|
|
use_swift=config['use_swift'],
|
|
region=config['region'],
|
|
cloud_name=config['cloud_name'],
|
|
user_agent=config['user_agent'],
|
|
hypervisor_mapping=config['hypervisor_mapping'])
|
|
|
|
|
|
def ensure_perms():
|
|
"""Ensure gss file permissions."""
|
|
if os.path.isfile(ID_CONF_FILE_NAME):
|
|
os.chmod(ID_CONF_FILE_NAME, 0o640)
|
|
|
|
if os.path.isfile(MIRRORS_CONF_FILE_NAME,):
|
|
os.chmod(MIRRORS_CONF_FILE_NAME, 0o640)
|
|
|
|
|
|
def get_release():
|
|
return get_os_codename_package('glance-common', fatal=False) or 'icehouse'
|
|
|
|
|
|
def get_configs():
|
|
configs = OSConfigRenderer(templates_dir='templates/',
|
|
openstack_release=get_release())
|
|
|
|
configs.register(MIRRORS_CONF_FILE_NAME, [MirrorsConfigServiceContext()])
|
|
configs.register(ID_CONF_FILE_NAME, [IdentityServiceContext(),
|
|
AMQPContext(),
|
|
UnitNameContext()])
|
|
return configs
|
|
|
|
|
|
def install_cron_script():
|
|
"""Installs cron job in /etc/cron.$frequency/ for repeating sync
|
|
|
|
Script is not a template but we always overwrite, to ensure it is
|
|
up-to-date.
|
|
|
|
"""
|
|
for fn in [SYNC_SCRIPT_NAME, SCRIPT_WRAPPER_NAME]:
|
|
shutil.copy(os.path.join("scripts", fn), USR_SHARE_DIR)
|
|
|
|
config = hookenv.config()
|
|
installed_script = os.path.join(USR_SHARE_DIR, SCRIPT_WRAPPER_NAME)
|
|
linkname = '/etc/cron.{f}/{s}'.format(f=config['frequency'],
|
|
s=CRON_JOB_FILENAME)
|
|
try:
|
|
hookenv.log("Creating symlink: %s -> %s" % (installed_script,
|
|
linkname))
|
|
os.symlink(installed_script, linkname)
|
|
except OSError as ex:
|
|
if ex.errno == ERR_FILE_EXISTS:
|
|
hookenv.log('symlink %s already exists' % linkname,
|
|
level=hookenv.INFO)
|
|
else:
|
|
raise ex
|
|
|
|
|
|
def install_cron_poll():
|
|
"Installs /etc/cron.d every-minute job in crontab for quick polling."
|
|
poll_file_source = os.path.join('scripts', CRON_POLL_FILENAME)
|
|
shutil.copy(poll_file_source, CRON_D)
|
|
|
|
|
|
def uninstall_cron_script():
|
|
"Removes sync program from any cron place it might be"
|
|
for fn in glob.glob("/etc/cron.*/" + CRON_JOB_FILENAME):
|
|
if os.path.exists(fn):
|
|
os.remove(fn)
|
|
|
|
|
|
def uninstall_cron_poll():
|
|
"Removes cron poll"
|
|
if os.path.exists(CRON_POLL_FILEPATH):
|
|
os.remove(CRON_POLL_FILEPATH)
|
|
|
|
|
|
@hooks.hook('identity-service-relation-joined')
|
|
def identity_service_joined(relation_id=None):
|
|
config = hookenv.config()
|
|
|
|
# Generate temporary bogus service URL to make keystone charm
|
|
# happy. The sync script will replace it with the endpoint for
|
|
# swift, because when this hook is fired, we do not yet
|
|
# necessarily know the swift endpoint URL (it might not even exist
|
|
# yet).
|
|
|
|
url = 'http://' + hookenv.unit_get('private-address')
|
|
relation_data = {
|
|
'service': 'image-stream',
|
|
'region': config['region'],
|
|
'public_url': url,
|
|
'admin_url': url,
|
|
'internal_url': url}
|
|
|
|
hookenv.relation_set(relation_id=relation_id, **relation_data)
|
|
|
|
|
|
@hooks.hook('identity-service-relation-changed')
|
|
def identity_service_changed():
|
|
configs = get_configs()
|
|
configs.write(ID_CONF_FILE_NAME)
|
|
ensure_perms()
|
|
|
|
|
|
@hooks.hook('install.real')
|
|
def install():
|
|
execd_preinstall()
|
|
add_source(hookenv.config('source'), hookenv.config('key'))
|
|
for directory in [CONF_FILE_DIR, USR_SHARE_DIR]:
|
|
hookenv.log("creating config dir at {}".format(directory))
|
|
if not os.path.isdir(directory):
|
|
if os.path.exists(directory):
|
|
hookenv.log("error: {} exists but is not a directory."
|
|
" exiting.".format(directory))
|
|
return
|
|
os.mkdir(directory)
|
|
|
|
apt_update(fatal=True)
|
|
|
|
apt_install(packages=['python-simplestreams', 'python-glanceclient',
|
|
'python-yaml', 'python-keystoneclient',
|
|
'python-kombu',
|
|
'python-swiftclient', 'ubuntu-cloudimage-keyring'])
|
|
|
|
hookenv.log('end install hook.')
|
|
|
|
|
|
@hooks.hook('config-changed',
|
|
'image-modifier-relation-changed',
|
|
'image-modifier-relation-joined')
|
|
def config_changed():
|
|
hookenv.log('begin config-changed hook.')
|
|
configs = get_configs()
|
|
configs.write(MIRRORS_CONF_FILE_NAME)
|
|
ensure_perms()
|
|
|
|
update_nrpe_config()
|
|
|
|
config = hookenv.config()
|
|
|
|
if config.changed('frequency'):
|
|
hookenv.log("'frequency' changed, removing cron job")
|
|
uninstall_cron_script()
|
|
|
|
if config['run']:
|
|
hookenv.log("installing to cronjob to "
|
|
"/etc/cron.{}".format(config['frequency']))
|
|
hookenv.log("installing {} for polling".format(CRON_POLL_FILEPATH))
|
|
install_cron_poll()
|
|
install_cron_script()
|
|
else:
|
|
hookenv.log("'run' set to False, removing cron jobs")
|
|
uninstall_cron_script()
|
|
uninstall_cron_poll()
|
|
|
|
config.save()
|
|
|
|
|
|
@hooks.hook('upgrade-charm')
|
|
def upgrade_charm():
|
|
install()
|
|
update_nrpe_config()
|
|
configs = get_configs()
|
|
configs.write_all()
|
|
ensure_perms()
|
|
|
|
|
|
@hooks.hook('amqp-relation-joined')
|
|
def amqp_joined():
|
|
conf = hookenv.config()
|
|
hookenv.relation_set(username=conf['rabbit-user'],
|
|
vhost=conf['rabbit-vhost'])
|
|
|
|
|
|
@hooks.hook('amqp-relation-changed')
|
|
def amqp_changed():
|
|
configs = get_configs()
|
|
if 'amqp' not in configs.complete_contexts():
|
|
hookenv.log('amqp relation incomplete. Peer not ready?')
|
|
return
|
|
configs.write(ID_CONF_FILE_NAME)
|
|
|
|
|
|
@hooks.hook('nrpe-external-master-relation-joined',
|
|
'nrpe-external-master-relation-changed')
|
|
def update_nrpe_config():
|
|
hostname = nrpe.get_nagios_hostname()
|
|
nrpe_setup = nrpe.NRPE(hostname=hostname)
|
|
nrpe_setup.write()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
try:
|
|
hooks.execute(sys.argv)
|
|
except hookenv.UnregisteredHookError as e:
|
|
hookenv.log('Unknown hook {} - skipping.'.format(e))
|