diff --git a/config.yaml b/config.yaml index 409d0156..9f801b29 100644 --- a/config.yaml +++ b/config.yaml @@ -59,4 +59,27 @@ options: ovs - OpenvSwitch Plugin nvp - Nicira Network Virtualization Platform . - + # HA configuration settings + vip: + type: string + description: "Virtual IP to use to front API services in ha configuration" + vip_iface: + type: string + default: eth0 + description: "Network Interface where to place the Virtual IP" + vip_cidr: + type: int + default: 24 + description: "Netmask that will be used for the Virtual IP" + 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: 5404 + description: | + Default multicast port number that will be used to communicate between + HA Cluster nodes. diff --git a/hooks/charmhelpers/contrib/hahelpers/cluster.py b/hooks/charmhelpers/contrib/hahelpers/cluster.py index bf832f7d..ccb20661 100644 --- a/hooks/charmhelpers/contrib/hahelpers/cluster.py +++ b/hooks/charmhelpers/contrib/hahelpers/cluster.py @@ -28,11 +28,19 @@ class HAIncompleteConfig(Exception): def is_clustered(): + log('** LY ** In is_clustered') + if relation_ids('ha'): + log('** LY ** In is_clustered len(relation_ids(ha)):' + str(len(relation_ids('ha')))) + else: + log('** LY ** relation_ids(ha) is false') for r_id in (relation_ids('ha') or []): + log('** LY ** r_id: ' + str(r_id)) for unit in (relation_list(r_id) or []): + log('** LY ** unit: ' + str(unit)) clustered = relation_get('clustered', rid=r_id, unit=unit) + log('** LY ** clustered: ' + str(clustered)) if clustered: return True return False @@ -56,18 +64,27 @@ def is_leader(resource): def peer_units(): peers = [] + log('** LY ** In peer_units') for r_id in (relation_ids('cluster') or []): + log('** LY ** In peer_units r_id:' + str(r_id)) for unit in (relation_list(r_id) or []): + log('** LY ** In peer_units unit:' + str(unit)) peers.append(unit) return peers def oldest_peer(peers): + log('** LY ** In oldest_peer') local_unit_no = int(os.getenv('JUJU_UNIT_NAME').split('/')[1]) + log('** LY ** In oldest_peer local_unit_no:' + str(local_unit_no)) for peer in peers: remote_unit_no = int(peer.split('/')[1]) + log('** LY ** In oldest_peer remote_unit_no:' + str(remote_unit_no)) if remote_unit_no < local_unit_no: + log('** LY ** In oldest_peer %s < %s:' % (str(remote_unit_no), str(local_unit_no))) return False + else: + log('** LY ** In oldest_peer %s >= %s:' % (str(remote_unit_no), str(local_unit_no))) return True @@ -173,11 +190,14 @@ def canonical_url(configs, vip_setting='vip'): :vip_setting: str: Setting in charm config that specifies VIP address. ''' + log('** LY ** In canonical_url') scheme = 'http' if 'https' in configs.complete_contexts(): scheme = 'https' if is_clustered(): + log('** LY ** is_clustered is true, returning vip') addr = config_get(vip_setting) else: + log('** LY ** is_clustered is false, returning private-address') addr = unit_get('private-address') return '%s://%s' % (scheme, addr) diff --git a/hooks/cluster-relation-changed b/hooks/cluster-relation-changed new file mode 120000 index 00000000..1fb10fd5 --- /dev/null +++ b/hooks/cluster-relation-changed @@ -0,0 +1 @@ +neutron_api_hooks.py \ No newline at end of file diff --git a/hooks/cluster-relation-departed b/hooks/cluster-relation-departed new file mode 120000 index 00000000..1fb10fd5 --- /dev/null +++ b/hooks/cluster-relation-departed @@ -0,0 +1 @@ +neutron_api_hooks.py \ No newline at end of file diff --git a/hooks/ha-relation-changed b/hooks/ha-relation-changed new file mode 120000 index 00000000..1fb10fd5 --- /dev/null +++ b/hooks/ha-relation-changed @@ -0,0 +1 @@ +neutron_api_hooks.py \ No newline at end of file diff --git a/hooks/ha-relation-joined b/hooks/ha-relation-joined new file mode 120000 index 00000000..1fb10fd5 --- /dev/null +++ b/hooks/ha-relation-joined @@ -0,0 +1 @@ +neutron_api_hooks.py \ No newline at end of file diff --git a/hooks/neutron_api_hooks.py b/hooks/neutron_api_hooks.py index e408e8af..8d3c627f 100755 --- a/hooks/neutron_api_hooks.py +++ b/hooks/neutron_api_hooks.py @@ -44,10 +44,14 @@ from neutron_api_utils import ( api_port, auth_token_config, keystone_ca_cert_b64, + CLUSTER_RES, ) from charmhelpers.contrib.hahelpers.cluster import ( canonical_url, + eligible_leader, + get_hacluster_config, + is_leader, ) from charmhelpers.payload.execd import execd_preinstall @@ -70,6 +74,8 @@ def install(): def config_changed(): global CONFIGS CONFIGS.write_all() + for r_id in relation_ids('neutron-api'): + neutron_api_relation_joined(rid=r_id) @hooks.hook('amqp-relation-joined') @@ -203,9 +209,12 @@ def _get_keystone_info(): @hooks.hook('neutron-api-relation-joined') def neutron_api_relation_joined(rid=None): + log('** LY ** neutron_api_relation_joined') manager = network_manager() base_url = canonical_url(CONFIGS) + log('** LY ** neutron_api_relation_joined base_url:' + base_url) neutron_url = '%s:%s' % (base_url, api_port('neutron-server')) + log('** LY ** neutron_api_relation_joined neutron_url:' + neutron_url) relation_data = { 'network_manager': manager, 'default_floating_pool': config('neutron-external-network'), @@ -214,6 +223,7 @@ def neutron_api_relation_joined(rid=None): manager + '_url': neutron_url, manager + '_security_groups': config('neutron-security-groups') } + log('** LY ** neutron_api_relation_joined neutron_url (from relation_data):' + relation_data['neutron_url']) keystone_info = _get_keystone_info() if is_relation_made('identity-service') and keystone_info: relation_data.update({ @@ -233,6 +243,59 @@ def neutron_api_relation_joined(rid=None): def neutron_api_relation_changed(): CONFIGS.write(NEUTRON_CONF) +@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(): + log('** LY ** IN HA JOINED') + config = get_hacluster_config() + resources = { + 'res_neutron_vip': 'ocf:heartbeat:IPaddr2', + 'res_neutron_haproxy': 'lsb:haproxy', + } + vip_params = 'params ip="%s" cidr_netmask="%s" nic="%s"' % \ + (config['vip'], config['vip_cidr'], config['vip_iface']) + resource_params = { + 'res_neutron_vip': vip_params, + 'res_neutron_haproxy': 'op monitor interval="5s"' + } + init_services = { + 'res_neutron_haproxy': 'haproxy' + } + clones = { + 'cl_nova_haproxy': 'res_neutron_haproxy' + } + log('** LY ** HA JOINED ha-bindiface: '+ str(config['ha-bindiface'])) + log('** LY ** HA JOINED ha-mcastport: '+ str(config['ha-mcastport'])) + relation_set(init_services=init_services, + corosync_bindiface=config['ha-bindiface'], + corosync_mcastport=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.') + return + if not is_leader(CLUSTER_RES): + log('ha_changed: hacluster complete but we are not leader.') + return + log('Cluster configured, notifying other services and updating ' + 'keystone endpoint configuration') + for rid in relation_ids('identity-service'): + identity_joined(rid=rid) + for rid in relation_ids('neutron-api'): + neutron_api_relation_joined(rid=rid) + def main(): try: hooks.execute(sys.argv) diff --git a/hooks/neutron_api_utils.py b/hooks/neutron_api_utils.py index 8b7b5d88..41226fc4 100644 --- a/hooks/neutron_api_utils.py +++ b/hooks/neutron_api_utils.py @@ -19,7 +19,7 @@ import neutron_api_context TEMPLATES = 'templates/' -CLUSTER_RES = 'res_nova_vip' +CLUSTER_RES = 'res_neutron_vip' # removed from original: charm-helper-sh BASE_PACKAGES = [ diff --git a/metadata.yaml b/metadata.yaml index 9d78a664..9ffe48b8 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -21,3 +21,9 @@ requires: interface: rabbitmq identity-service: interface: keystone + ha: + interface: hacluster + scope: container +peers: + cluster: + interface: neutron-api-ha