Provide heat charm HA support
[cbjchen,r=]
This commit is contained in:
parent
0f36fa663d
commit
1bdc87f495
34
config.yaml
34
config.yaml
@ -122,3 +122,37 @@ options:
|
||||
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.
|
||||
# HA configuration settings
|
||||
vip:
|
||||
type: string
|
||||
default:
|
||||
description: |
|
||||
Virtual IP(s) to use to front API services in HA configuration.
|
||||
.
|
||||
If multiple networks are being used, a VIP should be provided for each
|
||||
network, separated by spaces.
|
||||
vip_iface:
|
||||
type: string
|
||||
default: eth0
|
||||
description: |
|
||||
Default network interface to use for HA vip when it cannot be automatically
|
||||
determined.
|
||||
vip_cidr:
|
||||
type: int
|
||||
default: 24
|
||||
description: |
|
||||
Default CIDR netmask to use for HA vip when it cannot be automatically
|
||||
determined.
|
||||
ha-bindiface:
|
||||
type: string
|
||||
default: eth0
|
||||
description: |
|
||||
Default network interface on which HA cluster will bind to communication
|
||||
with the other members of the HA Cluster.
|
||||
ha-mcastport:
|
||||
type: int
|
||||
default: 5959
|
||||
description: |
|
||||
Default multicast port number that will be used to communicate between
|
||||
HA Cluster nodes.
|
||||
|
||||
|
1
hooks/cluster-relation-changed
Symbolic link
1
hooks/cluster-relation-changed
Symbolic link
@ -0,0 +1 @@
|
||||
heat_relations.py
|
1
hooks/cluster-relation-departed
Symbolic link
1
hooks/cluster-relation-departed
Symbolic link
@ -0,0 +1 @@
|
||||
heat_relations.py
|
1
hooks/cluster-relation-joined
Symbolic link
1
hooks/cluster-relation-joined
Symbolic link
@ -0,0 +1 @@
|
||||
heat_relations.py
|
1
hooks/ha-relation-changed
Symbolic link
1
hooks/ha-relation-changed
Symbolic link
@ -0,0 +1 @@
|
||||
heat_relations.py
|
1
hooks/ha-relation-joined
Symbolic link
1
hooks/ha-relation-joined
Symbolic link
@ -0,0 +1 @@
|
||||
heat_relations.py
|
@ -19,7 +19,9 @@ from charmhelpers.core.hookenv import (
|
||||
charm_dir,
|
||||
log,
|
||||
relation_ids,
|
||||
relation_get,
|
||||
relation_set,
|
||||
local_unit,
|
||||
open_port,
|
||||
unit_get,
|
||||
status_set,
|
||||
@ -39,6 +41,19 @@ from charmhelpers.fetch import (
|
||||
apt_update
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.hahelpers.cluster import (
|
||||
is_elected_leader,
|
||||
get_hacluster_config,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.network.ip import (
|
||||
get_iface_for_address,
|
||||
get_netmask_for_address,
|
||||
get_address_in_network,
|
||||
get_ipv6_addr,
|
||||
is_ipv6
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.openstack.utils import (
|
||||
configure_installation_source,
|
||||
openstack_upgrade_available,
|
||||
@ -59,6 +74,7 @@ from heat_utils import (
|
||||
determine_packages,
|
||||
migrate_database,
|
||||
register_configs,
|
||||
CLUSTER_RES,
|
||||
HEAT_CONF,
|
||||
REQUIRED_INTERFACES,
|
||||
setup_ipv6,
|
||||
@ -68,6 +84,7 @@ from heat_context import (
|
||||
API_PORTS,
|
||||
)
|
||||
|
||||
from charmhelpers.contrib.openstack.context import ADDRESS_TYPES
|
||||
from charmhelpers.payload.execd import execd_preinstall
|
||||
|
||||
hooks = Hooks()
|
||||
@ -112,6 +129,11 @@ def config_changed():
|
||||
CONFIGS.write_all()
|
||||
configure_https()
|
||||
|
||||
for rid in relation_ids('cluster'):
|
||||
cluster_joined(relation_id=rid)
|
||||
for r_id in relation_ids('ha'):
|
||||
ha_joined(relation_id=r_id)
|
||||
|
||||
|
||||
@hooks.hook('upgrade-charm')
|
||||
def upgrade_charm():
|
||||
@ -140,9 +162,9 @@ def db_joined():
|
||||
config('database-user'),
|
||||
relation_prefix='heat')
|
||||
else:
|
||||
relation_set(heat_database=config('database'),
|
||||
heat_username=config('database-user'),
|
||||
heat_hostname=unit_get('private-address'))
|
||||
relation_set(database=config('database'),
|
||||
username=config('database-user'),
|
||||
hostname=unit_get('private-address'))
|
||||
|
||||
|
||||
@hooks.hook('shared-db-relation-changed')
|
||||
@ -152,7 +174,15 @@ def db_changed():
|
||||
log('shared-db relation incomplete. Peer not ready?')
|
||||
return
|
||||
CONFIGS.write(HEAT_CONF)
|
||||
migrate_database()
|
||||
|
||||
if is_elected_leader(CLUSTER_RES):
|
||||
allowed_units = relation_get('allowed_units')
|
||||
if allowed_units and local_unit() in allowed_units.split():
|
||||
log('Cluster leader, performing db sync')
|
||||
migrate_database()
|
||||
else:
|
||||
log('allowed_units either not presented, or local unit '
|
||||
'not in acl list: %s' % repr(allowed_units))
|
||||
|
||||
|
||||
def configure_https():
|
||||
@ -231,6 +261,100 @@ def leader_elected():
|
||||
leader_set({'heat-domain-admin-passwd': pwgen(32)})
|
||||
|
||||
|
||||
@hooks.hook('cluster-relation-joined')
|
||||
def cluster_joined(relation_id=None):
|
||||
for addr_type in ADDRESS_TYPES:
|
||||
address = get_address_in_network(
|
||||
config('os-{}-network'.format(addr_type))
|
||||
)
|
||||
if address:
|
||||
relation_set(
|
||||
relation_id=relation_id,
|
||||
relation_settings={'{}-address'.format(addr_type): address}
|
||||
)
|
||||
|
||||
if config('prefer-ipv6'):
|
||||
private_addr = get_ipv6_addr(exc_list=[config('vip')])[0]
|
||||
relation_set(relation_id=relation_id,
|
||||
relation_settings={'private-address': private_addr})
|
||||
|
||||
|
||||
@hooks.hook('cluster-relation-changed',
|
||||
'cluster-relation-departed')
|
||||
@restart_on_change(restart_map(), stopstart=True)
|
||||
def cluster_changed():
|
||||
CONFIGS.write_all()
|
||||
|
||||
|
||||
@hooks.hook('ha-relation-joined')
|
||||
def ha_joined(relation_id=None):
|
||||
cluster_config = get_hacluster_config()
|
||||
|
||||
resources = {
|
||||
'res_heat_haproxy': 'lsb:haproxy'
|
||||
}
|
||||
|
||||
resource_params = {
|
||||
'res_heat_haproxy': 'op monitor interval="5s"'
|
||||
}
|
||||
|
||||
vip_group = []
|
||||
for vip in cluster_config['vip'].split():
|
||||
if is_ipv6(vip):
|
||||
res_heat_vip = 'ocf:heartbeat:IPv6addr'
|
||||
vip_params = 'ipv6addr'
|
||||
else:
|
||||
res_heat_vip = 'ocf:heartbeat:IPaddr2'
|
||||
vip_params = 'ip'
|
||||
|
||||
iface = (get_iface_for_address(vip) or
|
||||
config('vip_iface'))
|
||||
netmask = (get_netmask_for_address(vip) or
|
||||
config('vip_cidr'))
|
||||
|
||||
if iface is not None:
|
||||
vip_key = 'res_heat_{}_vip'.format(iface)
|
||||
resources[vip_key] = res_heat_vip
|
||||
resource_params[vip_key] = (
|
||||
'params {ip}="{vip}" cidr_netmask="{netmask}"'
|
||||
' nic="{iface}"'.format(ip=vip_params,
|
||||
vip=vip,
|
||||
iface=iface,
|
||||
netmask=netmask)
|
||||
)
|
||||
vip_group.append(vip_key)
|
||||
|
||||
if len(vip_group) >= 1:
|
||||
relation_set(relation_id=relation_id,
|
||||
groups={'grp_heat_vips': ' '.join(vip_group)})
|
||||
|
||||
init_services = {
|
||||
'res_heat_haproxy': 'haproxy'
|
||||
}
|
||||
clones = {
|
||||
'cl_heat_haproxy': 'res_heat_haproxy'
|
||||
}
|
||||
relation_set(relation_id=relation_id,
|
||||
init_services=init_services,
|
||||
corosync_bindiface=cluster_config['ha-bindiface'],
|
||||
corosync_mcastport=cluster_config['ha-mcastport'],
|
||||
resources=resources,
|
||||
resource_params=resource_params,
|
||||
clones=clones)
|
||||
|
||||
|
||||
@hooks.hook('ha-relation-changed')
|
||||
def ha_changed():
|
||||
clustered = relation_get('clustered')
|
||||
if not clustered or clustered in [None, 'None', '']:
|
||||
log('ha_changed: hacluster subordinate not fully clustered.')
|
||||
else:
|
||||
log('Cluster configured, notifying other services and updating '
|
||||
'keystone endpoint configuration')
|
||||
for rid in relation_ids('identity-service'):
|
||||
identity_joined(rid=rid)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
hooks.execute(sys.argv)
|
||||
|
@ -68,6 +68,8 @@ BASE_SERVICES = [
|
||||
'heat-engine'
|
||||
]
|
||||
|
||||
# Cluster resource used to determine leadership when hacluster'd
|
||||
CLUSTER_RES = 'grp_heat_vips'
|
||||
SVC = 'heat'
|
||||
HEAT_DIR = '/etc/heat'
|
||||
HEAT_CONF = '/etc/heat/heat.conf'
|
||||
@ -82,8 +84,7 @@ CONFIG_FILES = OrderedDict([
|
||||
(HEAT_CONF, {
|
||||
'services': BASE_SERVICES,
|
||||
'contexts': [context.AMQPContext(ssl_dir=HEAT_DIR),
|
||||
context.SharedDBContext(relation_prefix='heat',
|
||||
ssl_dir=HEAT_DIR),
|
||||
context.SharedDBContext(ssl_dir=HEAT_DIR),
|
||||
context.OSConfigFlagContext(),
|
||||
HeatIdentityServiceContext(service=SVC, service_user=SVC),
|
||||
HeatHAProxyContext(),
|
||||
|
@ -14,3 +14,9 @@ requires:
|
||||
interface: rabbitmq
|
||||
identity-service:
|
||||
interface: keystone
|
||||
ha:
|
||||
interface: hacluster
|
||||
scope: container
|
||||
peers:
|
||||
cluster:
|
||||
interface: heat-ha
|
||||
|
@ -345,9 +345,9 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
relation = ['shared-db', 'mysql:shared-db']
|
||||
expected = {
|
||||
'private-address': u.valid_ip,
|
||||
'heat_database': 'heat',
|
||||
'heat_username': 'heat',
|
||||
'heat_hostname': u.valid_ip
|
||||
'database': 'heat',
|
||||
'username': 'heat',
|
||||
'hostname': u.valid_ip
|
||||
}
|
||||
|
||||
ret = u.validate_relation_data(unit, relation, expected)
|
||||
@ -363,8 +363,8 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
expected = {
|
||||
'private-address': u.valid_ip,
|
||||
'db_host': u.valid_ip,
|
||||
'heat_allowed_units': 'heat/0',
|
||||
'heat_password': u.not_null
|
||||
'allowed_units': 'heat/0',
|
||||
'password': u.not_null
|
||||
}
|
||||
|
||||
ret = u.validate_relation_data(unit, relation, expected)
|
||||
@ -468,7 +468,7 @@ class HeatBasicDeployment(OpenStackAmuletDeployment):
|
||||
u.log.debug('mysql:heat relation: {}'.format(mysql_rel))
|
||||
|
||||
db_uri = "mysql://{}:{}@{}/{}".format('heat',
|
||||
mysql_rel['heat_password'],
|
||||
mysql_rel['password'],
|
||||
mysql_rel['db_host'],
|
||||
'heat')
|
||||
|
||||
|
@ -41,6 +41,13 @@ TO_PATCH = [
|
||||
'execd_preinstall',
|
||||
'log',
|
||||
'migrate_database',
|
||||
'is_elected_leader',
|
||||
'relation_ids',
|
||||
'relation_get',
|
||||
'local_unit',
|
||||
'get_hacluster_config',
|
||||
'get_iface_for_address',
|
||||
'get_netmask_for_address',
|
||||
]
|
||||
|
||||
|
||||
@ -91,12 +98,14 @@ class HeatRelationTests(CharmTestCase):
|
||||
def test_db_joined(self):
|
||||
self.unit_get.return_value = 'heat.foohost.com'
|
||||
relations.db_joined()
|
||||
self.relation_set.assert_called_with(heat_database='heat',
|
||||
heat_username='heat',
|
||||
heat_hostname='heat.foohost.com')
|
||||
self.relation_set.assert_called_with(database='heat',
|
||||
username='heat',
|
||||
hostname='heat.foohost.com')
|
||||
self.unit_get.assert_called_with('private-address')
|
||||
|
||||
def _shared_db_test(self, configs):
|
||||
self.relation_get.return_value = 'heat/0 heat/1'
|
||||
self.local_unit.return_value = 'heat/0'
|
||||
configs.complete_contexts = MagicMock()
|
||||
configs.complete_contexts.return_value = ['shared-db']
|
||||
configs.write = MagicMock()
|
||||
@ -242,3 +251,42 @@ class HeatRelationTests(CharmTestCase):
|
||||
relations.db_joined()
|
||||
self.sync_db_with_multi_ipv6_addresses.assert_called_with(
|
||||
'heat', 'heat', relation_prefix='heat')
|
||||
|
||||
@patch.object(relations, 'CONFIGS')
|
||||
def test_non_leader_db_changed(self, configs):
|
||||
self.is_elected_leader.return_value = False
|
||||
configs.complete_contexts.return_value = []
|
||||
self.relation_get.return_value = 'heat/0 heat/1'
|
||||
self.local_unit.return_value = 'heat/0'
|
||||
configs.complete_contexts = MagicMock()
|
||||
configs.complete_contexts.return_value = ['shared-db']
|
||||
configs.write = MagicMock()
|
||||
relations.db_changed()
|
||||
self.assertFalse(self.migrate_database.called)
|
||||
|
||||
@patch.object(relations, 'CONFIGS')
|
||||
def test_ha_joined(self, configs):
|
||||
self.get_hacluster_config.return_value = {
|
||||
'ha-bindiface': 'eth0',
|
||||
'ha-mcastport': '5959',
|
||||
'vip': '10.5.105.3'
|
||||
}
|
||||
self.get_iface_for_address.return_value = 'eth0'
|
||||
self.get_netmask_for_address.return_value = '255.255.255.0'
|
||||
relations.ha_joined()
|
||||
expected = {
|
||||
'relation_id': None,
|
||||
'init_services': {'res_heat_haproxy': 'haproxy'},
|
||||
'corosync_bindiface': 'eth0',
|
||||
'corosync_mcastport': '5959',
|
||||
'resources': {
|
||||
'res_heat_haproxy': 'lsb:haproxy',
|
||||
'res_heat_eth0_vip': 'ocf:heartbeat:IPaddr2'},
|
||||
'resource_params': {
|
||||
'res_heat_haproxy': 'op monitor interval="5s"',
|
||||
'res_heat_eth0_vip': ('params ip="10.5.105.3" '
|
||||
'cidr_netmask="255.255.255.0" '
|
||||
'nic="eth0"')},
|
||||
'clones': {'cl_heat_haproxy': 'res_heat_haproxy'}
|
||||
}
|
||||
self.relation_set.assert_called_with(**expected)
|
||||
|
Loading…
Reference in New Issue
Block a user