292 lines
10 KiB
Python
292 lines
10 KiB
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.
|
|
|
|
# vim: set ts=4:et
|
|
|
|
from charmhelpers.core.hookenv import (
|
|
config,
|
|
relation_ids,
|
|
related_units,
|
|
relation_get,
|
|
local_unit,
|
|
unit_get,
|
|
log,
|
|
WARNING,
|
|
ERROR,
|
|
)
|
|
from charmhelpers.core.strutils import bool_from_string
|
|
from charmhelpers.contrib.openstack.context import (
|
|
OSContextGenerator,
|
|
HAProxyContext,
|
|
context_complete
|
|
)
|
|
from charmhelpers.contrib.openstack.utils import (
|
|
get_host_ip,
|
|
git_default_repos,
|
|
git_pip_venv_dir,
|
|
)
|
|
from charmhelpers.contrib.hahelpers.apache import (
|
|
get_ca_cert,
|
|
get_cert,
|
|
install_ca_cert,
|
|
)
|
|
from charmhelpers.contrib.network.ip import (
|
|
get_ipv6_addr,
|
|
format_ipv6_addr,
|
|
)
|
|
|
|
from charmhelpers.core.host import pwgen
|
|
|
|
from base64 import b64decode
|
|
import os
|
|
|
|
VALID_ENDPOINT_TYPES = {
|
|
'PUBLICURL': 'publicURL',
|
|
'INTERNALURL': 'internalURL',
|
|
'ADMINURL': 'adminURL',
|
|
}
|
|
|
|
|
|
class HorizonHAProxyContext(HAProxyContext):
|
|
def __call__(self):
|
|
'''
|
|
Horizon specific HAProxy context; haproxy is used all the time
|
|
in the openstack dashboard charm so a single instance just
|
|
self refers
|
|
'''
|
|
cluster_hosts = {}
|
|
l_unit = local_unit().replace('/', '-')
|
|
if config('prefer-ipv6'):
|
|
cluster_hosts[l_unit] = get_ipv6_addr(exc_list=[config('vip')])[0]
|
|
else:
|
|
cluster_hosts[l_unit] = unit_get('private-address')
|
|
|
|
for rid in relation_ids('cluster'):
|
|
for unit in related_units(rid):
|
|
_unit = unit.replace('/', '-')
|
|
addr = relation_get('private-address', rid=rid, unit=unit)
|
|
cluster_hosts[_unit] = addr
|
|
|
|
log('Ensuring haproxy enabled in /etc/default/haproxy.')
|
|
with open('/etc/default/haproxy', 'w') as out:
|
|
out.write('ENABLED=1\n')
|
|
|
|
ctxt = {
|
|
'units': cluster_hosts,
|
|
'service_ports': {
|
|
'dash_insecure': [80, 70],
|
|
'dash_secure': [443, 433]
|
|
},
|
|
'prefer_ipv6': config('prefer-ipv6')
|
|
}
|
|
return ctxt
|
|
|
|
|
|
# NOTE: this is a stripped-down version of
|
|
# contrib.openstack.IdentityServiceContext
|
|
class IdentityServiceContext(OSContextGenerator):
|
|
interfaces = ['identity-service']
|
|
|
|
def normalize(self, endpoint_type):
|
|
"""Normalizes the endpoint type values.
|
|
|
|
:param endpoint_type (string): the endpoint type to normalize.
|
|
:raises: Exception if the endpoint type is not valid.
|
|
:return (string): the normalized form of the endpoint type.
|
|
"""
|
|
normalized_form = VALID_ENDPOINT_TYPES.get(endpoint_type.upper(), None)
|
|
if not normalized_form:
|
|
msg = ('Endpoint type specified %s is not a valid'
|
|
' endpoint type' % endpoint_type)
|
|
log(msg, ERROR)
|
|
raise Exception(msg)
|
|
|
|
return normalized_form
|
|
|
|
def __call__(self):
|
|
log('Generating template context for identity-service')
|
|
ctxt = {}
|
|
regions = set()
|
|
|
|
for rid in relation_ids('identity-service'):
|
|
for unit in related_units(rid):
|
|
rdata = relation_get(rid=rid, unit=unit)
|
|
serv_host = rdata.get('service_host')
|
|
serv_host = format_ipv6_addr(serv_host) or serv_host
|
|
region = rdata.get('region')
|
|
|
|
local_ctxt = {
|
|
'service_port': rdata.get('service_port'),
|
|
'service_host': serv_host,
|
|
'service_protocol':
|
|
rdata.get('service_protocol') or 'http',
|
|
'api_version': rdata.get('api_version', '2')
|
|
}
|
|
# If using keystone v3 the context is incomplete without the
|
|
# admin domain id
|
|
if local_ctxt['api_version'] == '3':
|
|
local_ctxt['admin_domain_id'] = rdata.get(
|
|
'admin_domain_id')
|
|
if not context_complete(local_ctxt):
|
|
continue
|
|
|
|
# Update the service endpoint and title for each available
|
|
# region in order to support multi-region deployments
|
|
if region is not None:
|
|
endpoint = ("%(service_protocol)s://%(service_host)s"
|
|
":%(service_port)s/v2.0") % local_ctxt
|
|
for reg in region.split():
|
|
regions.add((endpoint, reg))
|
|
|
|
if len(ctxt) == 0:
|
|
ctxt = local_ctxt
|
|
|
|
if len(regions) > 1:
|
|
avail_regions = map(lambda r: {'endpoint': r[0], 'title': r[1]},
|
|
regions)
|
|
ctxt['regions'] = sorted(avail_regions)
|
|
|
|
# Allow the endpoint types to be specified via a config parameter.
|
|
# The config parameter accepts either:
|
|
# 1. a single endpoint type to be specified, in which case the
|
|
# primary endpoint is configured
|
|
# 2. a list of endpoint types, in which case the primary endpoint
|
|
# is taken as the first entry and the secondary endpoint is
|
|
# taken as the second entry. All subsequent entries are ignored.
|
|
ep_types = config('endpoint-type')
|
|
if ep_types:
|
|
ep_types = [self.normalize(e) for e in ep_types.split(',')]
|
|
ctxt['primary_endpoint'] = ep_types[0]
|
|
if len(ep_types) > 1:
|
|
ctxt['secondary_endpoint'] = ep_types[1]
|
|
|
|
return ctxt
|
|
|
|
|
|
class HorizonContext(OSContextGenerator):
|
|
def __call__(self):
|
|
''' Provide all configuration for Horizon '''
|
|
projects_yaml = git_default_repos(config('openstack-origin-git'))
|
|
ctxt = {
|
|
'compress_offline':
|
|
bool_from_string(config('offline-compression')),
|
|
'debug': bool_from_string(config('debug')),
|
|
'customization_module': config('customization-module'),
|
|
'default_role': config('default-role'),
|
|
"webroot": config('webroot'),
|
|
"ubuntu_theme": bool_from_string(config('ubuntu-theme')),
|
|
"default_theme": config('default-theme'),
|
|
"secret": config('secret') or pwgen(),
|
|
'support_profile': config('profile')
|
|
if config('profile') in ['cisco'] else None,
|
|
"neutron_network_dvr": config("neutron-network-dvr"),
|
|
"neutron_network_l3ha": config("neutron-network-l3ha"),
|
|
"neutron_network_lb": config("neutron-network-lb"),
|
|
"neutron_network_firewall": config("neutron-network-firewall"),
|
|
"neutron_network_vpn": config("neutron-network-vpn"),
|
|
"cinder_backup": config("cinder-backup"),
|
|
"password_retrieve": config("password-retrieve"),
|
|
'virtualenv': git_pip_venv_dir(projects_yaml)
|
|
if config('openstack-origin-git') else None,
|
|
}
|
|
|
|
return ctxt
|
|
|
|
|
|
class ApacheContext(OSContextGenerator):
|
|
def __call__(self):
|
|
''' Grab cert and key from configuraton for SSL config '''
|
|
ctxt = {
|
|
'http_port': 70,
|
|
'https_port': 433
|
|
}
|
|
|
|
if config('enforce-ssl'):
|
|
# NOTE(dosaboy): if ssl is not configured we shouldn't allow this
|
|
if all(get_cert()):
|
|
if config('vip'):
|
|
addr = config('vip')
|
|
elif config('prefer-ipv6'):
|
|
addr = format_ipv6_addr(get_ipv6_addr()[0])
|
|
else:
|
|
addr = get_host_ip(unit_get('private-address'))
|
|
|
|
ctxt['ssl_addr'] = addr
|
|
else:
|
|
log("Enforce ssl redirect requested but ssl not configured - "
|
|
"skipping redirect", level=WARNING)
|
|
|
|
return ctxt
|
|
|
|
|
|
class ApacheSSLContext(OSContextGenerator):
|
|
def __call__(self):
|
|
''' Grab cert and key from configuration for SSL config '''
|
|
ca_cert = get_ca_cert()
|
|
if ca_cert:
|
|
install_ca_cert(b64decode(ca_cert))
|
|
|
|
ssl_cert, ssl_key = get_cert()
|
|
if all([ssl_cert, ssl_key]):
|
|
with open('/etc/ssl/certs/dashboard.cert', 'w') as cert_out:
|
|
cert_out.write(b64decode(ssl_cert))
|
|
with open('/etc/ssl/private/dashboard.key', 'w') as key_out:
|
|
key_out.write(b64decode(ssl_key))
|
|
os.chmod('/etc/ssl/private/dashboard.key', 0600)
|
|
ctxt = {
|
|
'ssl_configured': True,
|
|
'ssl_cert': '/etc/ssl/certs/dashboard.cert',
|
|
'ssl_key': '/etc/ssl/private/dashboard.key',
|
|
}
|
|
else:
|
|
# Use snakeoil ones by default
|
|
ctxt = {
|
|
'ssl_configured': False,
|
|
}
|
|
return ctxt
|
|
|
|
|
|
class RouterSettingContext(OSContextGenerator):
|
|
def __call__(self):
|
|
''' Enable/Disable Router Tab on horizon '''
|
|
ctxt = {
|
|
'disable_router': False if config('profile') in ['cisco'] else True
|
|
}
|
|
return ctxt
|
|
|
|
|
|
class LocalSettingsContext(OSContextGenerator):
|
|
def __call__(self):
|
|
''' Additional config stanzas to be appended to local_settings.py '''
|
|
|
|
relations = []
|
|
|
|
for rid in relation_ids("plugin"):
|
|
try:
|
|
unit = related_units(rid)[0]
|
|
except IndexError:
|
|
pass
|
|
else:
|
|
rdata = relation_get(unit=unit, rid=rid)
|
|
if set(('local-settings', 'priority')) <= set(rdata.keys()):
|
|
relations.append((unit, rdata))
|
|
|
|
ctxt = {
|
|
'settings': [
|
|
'# {0}\n{1}'.format(u, rd['local-settings'])
|
|
for u, rd in sorted(relations,
|
|
key=lambda r: r[1]['priority'])]
|
|
}
|
|
return ctxt
|