Merge from trunk
This commit is contained in:
commit
1d70417506
|
@ -1,2 +1,4 @@
|
|||
git-reinstall:
|
||||
description: Reinstall openstack-dashboard from the openstack-origin-git repositories.
|
||||
openstack-upgrade:
|
||||
description: Perform openstack upgrades. Config option action-managed-upgrade must be set to True.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
openstack_upgrade.py
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/python
|
||||
import sys
|
||||
|
||||
sys.path.append('hooks/')
|
||||
|
||||
from charmhelpers.contrib.openstack.utils import (
|
||||
do_action_openstack_upgrade,
|
||||
)
|
||||
|
||||
from horizon_utils import (
|
||||
do_openstack_upgrade,
|
||||
)
|
||||
|
||||
from horizon_hooks import (
|
||||
config_changed,
|
||||
CONFIGS,
|
||||
)
|
||||
|
||||
|
||||
def openstack_upgrade():
|
||||
"""Upgrade packages to config-set Openstack version.
|
||||
|
||||
If the charm was installed from source we cannot upgrade it.
|
||||
For backwards compatibility a config flag must be set for this
|
||||
code to run, otherwise a full service level upgrade will fire
|
||||
on config-changed."""
|
||||
|
||||
if do_action_openstack_upgrade('openstack-dashboard',
|
||||
do_openstack_upgrade,
|
||||
CONFIGS):
|
||||
config_changed()
|
||||
|
||||
if __name__ == '__main__':
|
||||
openstack_upgrade()
|
19
config.yaml
19
config.yaml
|
@ -153,6 +153,14 @@ options:
|
|||
In order for this charm to function correctly, the privacy extension
|
||||
must be disabled and a non-temporary address must be
|
||||
configured/available on your network interface.
|
||||
endpoint-type:
|
||||
type: string
|
||||
default:
|
||||
description: |
|
||||
Specifies the endpoint types to use for endpoints in the Keystone
|
||||
service catalog. Valid values are 'publicURL', 'internalURL',
|
||||
and 'adminURL'. Both the primary and secondary endpoint types can
|
||||
be specified by providing multiple comma delimited values.
|
||||
nagios_context:
|
||||
default: "juju"
|
||||
type: string
|
||||
|
@ -175,4 +183,13 @@ options:
|
|||
description: |
|
||||
A comma-separated list of nagios servicegroups. If left empty, the
|
||||
nagios_context will be used as the servicegroup.
|
||||
|
||||
action-managed-upgrade:
|
||||
type: boolean
|
||||
default: False
|
||||
description: |
|
||||
If True enables openstack upgrades for this charm via juju actions.
|
||||
You will still need to set openstack-origin to the new repository but
|
||||
instead of an upgrade running automatically across all units, it will
|
||||
wait for you to execute the openstack-upgrade action for this charm on
|
||||
each unit. If False it will revert to existing behavior of upgrading
|
||||
all units on config change.
|
||||
|
|
|
@ -25,6 +25,7 @@ import sys
|
|||
import re
|
||||
|
||||
import six
|
||||
import traceback
|
||||
import yaml
|
||||
|
||||
from charmhelpers.contrib.network import ip
|
||||
|
@ -34,6 +35,8 @@ from charmhelpers.core import (
|
|||
)
|
||||
|
||||
from charmhelpers.core.hookenv import (
|
||||
action_fail,
|
||||
action_set,
|
||||
config,
|
||||
log as juju_log,
|
||||
charm_dir,
|
||||
|
@ -114,6 +117,7 @@ SWIFT_CODENAMES = OrderedDict([
|
|||
('2.2.1', 'kilo'),
|
||||
('2.2.2', 'kilo'),
|
||||
('2.3.0', 'liberty'),
|
||||
('2.4.0', 'liberty'),
|
||||
])
|
||||
|
||||
# >= Liberty version->codename mapping
|
||||
|
@ -748,3 +752,47 @@ def git_yaml_value(projects_yaml, key):
|
|||
return projects[key]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def do_action_openstack_upgrade(package, upgrade_callback, configs):
|
||||
"""Perform action-managed OpenStack upgrade.
|
||||
|
||||
Upgrades packages to the configured openstack-origin version and sets
|
||||
the corresponding action status as a result.
|
||||
|
||||
If the charm was installed from source we cannot upgrade it.
|
||||
For backwards compatibility a config flag (action-managed-upgrade) must
|
||||
be set for this code to run, otherwise a full service level upgrade will
|
||||
fire on config-changed.
|
||||
|
||||
@param package: package name for determining if upgrade available
|
||||
@param upgrade_callback: function callback to charm's upgrade function
|
||||
@param configs: templating object derived from OSConfigRenderer class
|
||||
|
||||
@return: True if upgrade successful; False if upgrade failed or skipped
|
||||
"""
|
||||
ret = False
|
||||
|
||||
if git_install_requested():
|
||||
action_set({'outcome': 'installed from source, skipped upgrade.'})
|
||||
else:
|
||||
if openstack_upgrade_available(package):
|
||||
if config('action-managed-upgrade'):
|
||||
juju_log('Upgrading OpenStack release')
|
||||
|
||||
try:
|
||||
upgrade_callback(configs=configs)
|
||||
action_set({'outcome': 'success, upgrade completed.'})
|
||||
ret = True
|
||||
except:
|
||||
action_set({'outcome': 'upgrade failed, see traceback.'})
|
||||
action_set({'traceback': traceback.format_exc()})
|
||||
action_fail('do_openstack_upgrade resulted in an '
|
||||
'unexpected error')
|
||||
else:
|
||||
action_set({'outcome': 'action-managed-upgrade config is '
|
||||
'False, skipped upgrade.'})
|
||||
else:
|
||||
action_set({'outcome': 'no upgrade available.'})
|
||||
|
||||
return ret
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
horizon_hooks.py
|
|
@ -6,7 +6,8 @@ from charmhelpers.core.hookenv import (
|
|||
relation_get,
|
||||
local_unit,
|
||||
unit_get,
|
||||
log
|
||||
log,
|
||||
ERROR,
|
||||
)
|
||||
from charmhelpers.contrib.openstack.context import (
|
||||
OSContextGenerator,
|
||||
|
@ -28,6 +29,12 @@ 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):
|
||||
|
@ -69,6 +76,22 @@ class HorizonHAProxyContext(HAProxyContext):
|
|||
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 = {}
|
||||
|
@ -106,6 +129,21 @@ class IdentityServiceContext(OSContextGenerator):
|
|||
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
|
||||
|
||||
|
||||
|
@ -172,3 +210,28 @@ class RouterSettingContext(OSContextGenerator):
|
|||
'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
|
||||
|
|
|
@ -58,7 +58,7 @@ hooks = Hooks()
|
|||
CONFIGS = register_configs()
|
||||
|
||||
|
||||
@hooks.hook('install')
|
||||
@hooks.hook('install.real')
|
||||
def install():
|
||||
execd_preinstall()
|
||||
configure_installation_source(config('openstack-origin'))
|
||||
|
@ -106,7 +106,7 @@ def config_changed():
|
|||
if git_install_requested():
|
||||
if config_value_changed('openstack-origin-git'):
|
||||
git_install(config('openstack-origin-git'))
|
||||
else:
|
||||
elif not config('action-managed-upgrade'):
|
||||
if openstack_upgrade_available('openstack-dashboard'):
|
||||
do_openstack_upgrade(configs=CONFIGS)
|
||||
|
||||
|
@ -247,16 +247,23 @@ def update_nrpe_config():
|
|||
|
||||
|
||||
@hooks.hook('dashboard-plugin-relation-joined')
|
||||
def update_dashboard_plugin(rel_id=None):
|
||||
def plugin_relation_joined(rel_id=None):
|
||||
if git_install_requested():
|
||||
bin_path = git_pip_venv_dir(config('openstack-origin-git'))
|
||||
else:
|
||||
bin_path = '/usr/bin'
|
||||
relation_set(relation_id=rel_id,
|
||||
relation_set(release=os_release("openstack-dashboard"),
|
||||
relation_id=rel_id,
|
||||
bin_path=bin_path,
|
||||
openstack_dir=INSTALL_DIR)
|
||||
|
||||
|
||||
@hooks.hook('dashboard-plugin-relation-changed')
|
||||
@restart_on_change(restart_map())
|
||||
def update_plugin_config():
|
||||
CONFIGS.write(LOCAL_SETTINGS)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
hooks.execute(sys.argv)
|
||||
|
|
|
@ -101,7 +101,8 @@ CONFIG_FILES = OrderedDict([
|
|||
(LOCAL_SETTINGS, {
|
||||
'hook_contexts': [horizon_contexts.HorizonContext(),
|
||||
horizon_contexts.IdentityServiceContext(),
|
||||
context.SyslogContext()],
|
||||
context.SyslogContext(),
|
||||
horizon_contexts.LocalSettingsContext()],
|
||||
'services': ['apache2']
|
||||
}),
|
||||
(APACHE_CONF, {
|
||||
|
@ -262,12 +263,12 @@ def setup_ipv6():
|
|||
raise Exception("IPv6 is not supported in the charms for Ubuntu "
|
||||
"versions less than Trusty 14.04")
|
||||
|
||||
# NOTE(xianghui): Need to install haproxy(1.5.3) from trusty-backports
|
||||
# to support ipv6 address, so check is required to make sure not
|
||||
# breaking other versions, IPv6 only support for >= Trusty
|
||||
if ubuntu_rel == 'trusty':
|
||||
add_source('deb http://archive.ubuntu.com/ubuntu trusty-backports'
|
||||
' main')
|
||||
# Need haproxy >= 1.5.3 for ipv6 so for Trusty if we are <= Kilo we need to
|
||||
# use trusty-backports otherwise we can use the UCA.
|
||||
os_pkg = 'openstack-dashboard'
|
||||
if ubuntu_rel == 'trusty' and os_release(os_pkg) < 'liberty':
|
||||
add_source('deb http://archive.ubuntu.com/ubuntu trusty-backports '
|
||||
'main')
|
||||
apt_update()
|
||||
apt_install('haproxy/trusty-backports', fatal=True)
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
horizon_hooks.py
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/bash
|
||||
# Wrapper to deal with newer Ubuntu versions that don't have py2 installed
|
||||
# by default.
|
||||
|
||||
declare -a DEPS=('apt' 'netaddr' 'netifaces' 'pip' 'yaml')
|
||||
|
||||
check_and_install() {
|
||||
pkg="${1}-${2}"
|
||||
if ! dpkg -s ${pkg} 2>&1 > /dev/null; then
|
||||
apt-get -y install ${pkg}
|
||||
fi
|
||||
}
|
||||
|
||||
PYTHON="python"
|
||||
|
||||
for dep in ${DEPS[@]}; do
|
||||
check_and_install ${PYTHON} ${dep}
|
||||
done
|
||||
|
||||
exec ./hooks/install.real
|
|
@ -0,0 +1 @@
|
|||
horizon_hooks.py
|
|
@ -21,6 +21,9 @@ requires:
|
|||
ha:
|
||||
interface: hacluster
|
||||
scope: container
|
||||
dashboard-plugin:
|
||||
interface: dashboard-plugin
|
||||
scope: container
|
||||
peers:
|
||||
cluster:
|
||||
interface: openstack-dashboard-ha
|
||||
|
|
|
@ -118,3 +118,5 @@ LOGGING = {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
{{ settings|join('\n\n') }}
|
||||
|
|
|
@ -167,3 +167,5 @@ LOGIN_REDIRECT_URL='{{ webroot }}'
|
|||
# offline compression by default. To enable online compression, install
|
||||
# the node-less package and enable the following option.
|
||||
COMPRESS_OFFLINE = {{ compress_offline }}
|
||||
|
||||
{{ settings|join('\n\n') }}
|
|
@ -263,3 +263,5 @@ LOGGING = {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
{{ settings|join('\n\n') }}
|
||||
|
|
|
@ -483,3 +483,5 @@ COMPRESS_OFFLINE = {{ compress_offline }}
|
|||
# installations should have this set accordingly. For more information
|
||||
# see https://docs.djangoproject.com/en/dev/ref/settings/.
|
||||
ALLOWED_HOSTS = '*'
|
||||
|
||||
{{ settings|join('\n\n') }}
|
|
@ -212,6 +212,9 @@ IMAGE_CUSTOM_PROPERTY_TITLES = {
|
|||
# in the Keystone service catalog. Use this setting when Horizon is running
|
||||
# external to the OpenStack environment. The default is 'publicURL'.
|
||||
#OPENSTACK_ENDPOINT_TYPE = "publicURL"
|
||||
{% if primary_endpoint -%}
|
||||
OPENSTACK_ENDPOINT_TYPE = {{ primary_endpoint }}
|
||||
{% endif -%}
|
||||
|
||||
# SECONDARY_ENDPOINT_TYPE specifies the fallback endpoint type to use in the
|
||||
# case that OPENSTACK_ENDPOINT_TYPE is not present in the endpoints
|
||||
|
@ -219,6 +222,9 @@ IMAGE_CUSTOM_PROPERTY_TITLES = {
|
|||
# external to the OpenStack environment. The default is None. This
|
||||
# value should differ from OPENSTACK_ENDPOINT_TYPE if used.
|
||||
#SECONDARY_ENDPOINT_TYPE = "publicURL"
|
||||
{% if secondary_endpoint -%}
|
||||
SECONDARY_ENDPOINT_TYPE = {{ secondary_endpoint }}
|
||||
{% endif -%}
|
||||
|
||||
# The number of objects (Swift containers/objects or images) to display
|
||||
# on a single page before providing a paging element (a "more" link)
|
||||
|
@ -514,3 +520,5 @@ COMPRESS_OFFLINE = {{ compress_offline }}
|
|||
# installations should have this set accordingly. For more information
|
||||
# see https://docs.djangoproject.com/en/dev/ref/settings/.
|
||||
ALLOWED_HOSTS = '*'
|
||||
|
||||
{{ settings|join('\n\n') }}
|
|
@ -250,6 +250,9 @@ IMAGE_RESERVED_CUSTOM_PROPERTIES = []
|
|||
# in the Keystone service catalog. Use this setting when Horizon is running
|
||||
# external to the OpenStack environment. The default is 'publicURL'.
|
||||
#OPENSTACK_ENDPOINT_TYPE = "publicURL"
|
||||
{% if primary_endpoint -%}
|
||||
OPENSTACK_ENDPOINT_TYPE = {{ primary_endpoint }}
|
||||
{% endif -%}
|
||||
|
||||
# SECONDARY_ENDPOINT_TYPE specifies the fallback endpoint type to use in the
|
||||
# case that OPENSTACK_ENDPOINT_TYPE is not present in the endpoints
|
||||
|
@ -257,6 +260,9 @@ IMAGE_RESERVED_CUSTOM_PROPERTIES = []
|
|||
# external to the OpenStack environment. The default is None. This
|
||||
# value should differ from OPENSTACK_ENDPOINT_TYPE if used.
|
||||
#SECONDARY_ENDPOINT_TYPE = "publicURL"
|
||||
{% if secondary_endpoint -%}
|
||||
SECONDARY_ENDPOINT_TYPE = {{ secondary_endpoint }}
|
||||
{% endif -%}
|
||||
|
||||
# The number of objects (Swift containers/objects or images) to display
|
||||
# on a single page before providing a paging element (a "more" link)
|
||||
|
@ -619,3 +625,5 @@ LOGIN_REDIRECT_URL='{{ webroot }}'
|
|||
# installations should have this set accordingly. For more information
|
||||
# see https://docs.djangoproject.com/en/dev/ref/settings/.
|
||||
ALLOWED_HOSTS = '*'
|
||||
|
||||
{{ settings|join('\n\n') }}
|
|
@ -0,0 +1,53 @@
|
|||
from mock import patch
|
||||
import os
|
||||
|
||||
os.environ['JUJU_UNIT_NAME'] = 'openstack-dashboard'
|
||||
|
||||
with patch('horizon_utils.register_configs') as register_configs:
|
||||
import openstack_upgrade
|
||||
|
||||
from test_utils import (
|
||||
CharmTestCase
|
||||
)
|
||||
|
||||
TO_PATCH = [
|
||||
'do_openstack_upgrade',
|
||||
'config_changed',
|
||||
]
|
||||
|
||||
|
||||
class TestHorizonUpgradeActions(CharmTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHorizonUpgradeActions, self).setUp(openstack_upgrade,
|
||||
TO_PATCH)
|
||||
|
||||
@patch('charmhelpers.contrib.openstack.utils.config')
|
||||
@patch('charmhelpers.contrib.openstack.utils.action_set')
|
||||
@patch('charmhelpers.contrib.openstack.utils.git_install_requested')
|
||||
@patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available')
|
||||
def test_openstack_upgrade_true(self, upgrade_avail, git_requested,
|
||||
action_set, config):
|
||||
git_requested.return_value = False
|
||||
upgrade_avail.return_value = True
|
||||
config.return_value = True
|
||||
|
||||
openstack_upgrade.openstack_upgrade()
|
||||
|
||||
self.assertTrue(self.do_openstack_upgrade.called)
|
||||
self.assertTrue(self.config_changed.called)
|
||||
|
||||
@patch('charmhelpers.contrib.openstack.utils.config')
|
||||
@patch('charmhelpers.contrib.openstack.utils.action_set')
|
||||
@patch('charmhelpers.contrib.openstack.utils.git_install_requested')
|
||||
@patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available')
|
||||
def test_openstack_upgrade_false(self, upgrade_avail, git_requested,
|
||||
action_set, config):
|
||||
git_requested.return_value = False
|
||||
upgrade_avail.return_value = True
|
||||
config.return_value = False
|
||||
|
||||
openstack_upgrade.openstack_upgrade()
|
||||
|
||||
self.assertFalse(self.do_openstack_upgrade.called)
|
||||
self.assertFalse(self.config_changed.called)
|
|
@ -212,6 +212,22 @@ class TestHorizonContexts(CharmTestCase):
|
|||
{'endpoint': 'http://foo:5000/v2.0',
|
||||
'title': 'regionTwo'}]})
|
||||
|
||||
def test_IdentityServiceContext_endpoint_type(self):
|
||||
self.test_config.set('endpoint-type', 'internalURL')
|
||||
self.assertEqual(horizon_contexts.IdentityServiceContext()(),
|
||||
{'primary_endpoint': 'internalURL'})
|
||||
|
||||
def test_IdentityServiceContext_multi_endpoint_types(self):
|
||||
self.test_config.set('endpoint-type', 'internalURL,publicURL')
|
||||
self.assertEqual(horizon_contexts.IdentityServiceContext()(),
|
||||
{'primary_endpoint': 'internalURL',
|
||||
'secondary_endpoint': 'publicURL'})
|
||||
|
||||
def test_IdentityServiceContext_invalid_endpoint_type(self):
|
||||
self.test_config.set('endpoint-type', 'this_is_bad')
|
||||
with self.assertRaises(Exception):
|
||||
horizon_contexts.IdentityServiceContext()()
|
||||
|
||||
def test_HorizonHAProxyContext_no_cluster(self):
|
||||
self.relation_ids.return_value = []
|
||||
self.local_unit.return_value = 'openstack-dashboard/0'
|
||||
|
@ -251,3 +267,18 @@ class TestHorizonContexts(CharmTestCase):
|
|||
self.test_config.set('profile', None)
|
||||
self.assertEquals(horizon_contexts.RouterSettingContext()(),
|
||||
{'disable_router': True, })
|
||||
|
||||
def test_LocalSettingsContext(self):
|
||||
self.relation_ids.return_value = ['plugin:0', 'plugin-too:0']
|
||||
self.related_units.side_effect = [['horizon-plugin/0'],
|
||||
['horizon-plugin-too/0']]
|
||||
self.relation_get.side_effect = [{'priority': 99,
|
||||
'local-settings': 'FOO = True'},
|
||||
{'priority': 60,
|
||||
'local-settings': 'BAR = False'}]
|
||||
|
||||
self.assertEquals(horizon_contexts.LocalSettingsContext()(),
|
||||
{'settings': ['# horizon-plugin-too/0\n'
|
||||
'BAR = False',
|
||||
'# horizon-plugin/0\n'
|
||||
'FOO = True']})
|
||||
|
|
Loading…
Reference in New Issue