Pass data to hacluster charm using JSON

Encode dicts passed to hacluster charm using JSON serialization,
supporting consistent data presentation under Python 3 where
dict key iteration is non-deterministic.

This is supported by prefixing json based data items with 'json_'
and encoding with keys sorted.

The charm will also clear any unprefixed based data items for
upgrades.

Change-Id: I21c6acff4a4a22cbcc5e6ea4e78394ce076e79d9
Closes-Bug: 1741304
Depends-On: I364a60ca7b91327fe88ee729cf49ff8ab3f5e2b6
This commit is contained in:
James Page 2018-01-04 18:12:14 +00:00
parent 7a61202a9d
commit 852d1f2f3a
2 changed files with 96 additions and 40 deletions

View File

@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import sys
import uuid
from subprocess import (
@ -298,6 +299,8 @@ def config_changed():
amqp_joined(relation_id=r_id)
for r_id in relation_ids('identity-service'):
identity_joined(rid=r_id)
for r_id in relation_ids('ha'):
ha_joined(relation_id=r_id)
[cluster_joined(rid) for rid in relation_ids('cluster')]
@ -574,7 +577,12 @@ def ha_joined(relation_id=None):
vip_group.append(vip_key)
if len(vip_group) >= 1:
relation_set(groups={'grp_neutron_vips': ' '.join(vip_group)})
relation_set(
relation_id=relation_id,
json_groups=json.dumps({
'grp_neutron_vips': ' '.join(vip_group)
}, sort_keys=True)
)
init_services = {
'res_neutron_haproxy': 'haproxy'
@ -583,12 +591,22 @@ def ha_joined(relation_id=None):
'cl_nova_haproxy': 'res_neutron_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)
json_init_services=json.dumps(init_services,
sort_keys=True),
json_resources=json.dumps(resources,
sort_keys=True),
json_resource_params=json.dumps(resource_params,
sort_keys=True),
json_clones=json.dumps(clones,
sort_keys=True))
# NOTE(jamespage): Clear any non-json based keys
relation_set(relation_id=relation_id,
groups=None, init_services=None,
resources=None, resource_params=None,
clones=None)
@hooks.hook('ha-relation-changed')

View File

@ -15,6 +15,7 @@
import sys
import yaml
import json
from mock import MagicMock, patch, call
from test_utils import CharmTestCase
@ -233,12 +234,14 @@ class NeutronAPIHooksTests(CharmTestCase):
_amqp_rel_joined = self.patch('amqp_joined')
_id_rel_joined = self.patch('identity_joined')
_id_cluster_joined = self.patch('cluster_joined')
_id_ha_joined = self.patch('ha_joined')
self._call_hook('config-changed')
self.assertTrue(_n_api_rel_joined.called)
self.assertTrue(_n_plugin_api_rel_joined.called)
self.assertTrue(_amqp_rel_joined.called)
self.assertTrue(_id_rel_joined.called)
self.assertTrue(_id_cluster_joined.called)
self.assertTrue(_id_ha_joined.called)
self.assertTrue(self.CONFIGS.write_all.called)
self.assertTrue(self.do_openstack_upgrade.called)
self.assertTrue(self.apt_install.called)
@ -275,6 +278,7 @@ class NeutronAPIHooksTests(CharmTestCase):
_amqp_rel_joined = self.patch('amqp_joined')
_id_rel_joined = self.patch('identity_joined')
_id_cluster_joined = self.patch('cluster_joined')
_id_ha_joined = self.patch('ha_joined')
repo = 'cloud:trusty-juno'
openstack_origin_git = {
'repositories': [
@ -304,6 +308,7 @@ class NeutronAPIHooksTests(CharmTestCase):
self.assertTrue(_amqp_rel_joined.called)
self.assertTrue(_id_rel_joined.called)
self.assertTrue(_id_cluster_joined.called)
self.assertTrue(_id_ha_joined.called)
@patch.object(hooks, 'git_install_requested')
def test_config_changed_with_openstack_upgrade_action(self, git_requested):
@ -769,23 +774,30 @@ class NeutronAPIHooksTests(CharmTestCase):
self.get_netmask_for_address.return_value = '255.255.255.0'
_relation_data = {
'relation_id': None,
'init_services': {'res_neutron_haproxy': 'haproxy'},
'corosync_bindiface': _ha_config['ha-bindiface'],
'corosync_mcastport': _ha_config['ha-mcastport'],
'resources': {
'json_init_services': json.dumps({
'res_neutron_haproxy': 'haproxy'
}, sort_keys=True),
'json_resources': json.dumps({
'res_neutron_eth0_vip': 'ocf:heartbeat:IPaddr2',
'res_neutron_haproxy': 'lsb:haproxy'
},
'resource_params': {
}, sort_keys=True),
'json_resource_params': json.dumps({
'res_neutron_eth0_vip': vip_params,
'res_neutron_haproxy': 'op monitor interval="5s"'
},
'clones': {'cl_nova_haproxy': 'res_neutron_haproxy'}
}, sort_keys=True),
'json_clones': json.dumps({
'cl_nova_haproxy': 'res_neutron_haproxy'
}, sort_keys=True),
}
self._call_hook('ha-relation-joined')
self.relation_set.assert_called_with(
**_relation_data
)
self.relation_set.assert_has_calls([
call(**_relation_data),
call(clones=None, groups=None,
init_services=None, relation_id=None,
resource_params=None, resources=None),
])
@patch.object(hooks, 'get_hacluster_config')
def test_ha_joined_no_bound_ip(self, _get_ha_config):
@ -802,23 +814,30 @@ class NeutronAPIHooksTests(CharmTestCase):
self.get_netmask_for_address.return_value = None
_relation_data = {
'relation_id': None,
'init_services': {'res_neutron_haproxy': 'haproxy'},
'json_init_services': json.dumps({
'res_neutron_haproxy': 'haproxy'
}, sort_keys=True),
'corosync_bindiface': _ha_config['ha-bindiface'],
'corosync_mcastport': _ha_config['ha-mcastport'],
'resources': {
'json_resources': json.dumps({
'res_neutron_eth120_vip': 'ocf:heartbeat:IPaddr2',
'res_neutron_haproxy': 'lsb:haproxy'
},
'resource_params': {
}, sort_keys=True),
'json_resource_params': json.dumps({
'res_neutron_eth120_vip': vip_params,
'res_neutron_haproxy': 'op monitor interval="5s"'
},
'clones': {'cl_nova_haproxy': 'res_neutron_haproxy'}
}, sort_keys=True),
'json_clones': json.dumps({
'cl_nova_haproxy': 'res_neutron_haproxy'
}, sort_keys=True),
}
self._call_hook('ha-relation-joined')
self.relation_set.assert_called_with(
**_relation_data
)
self.relation_set.assert_has_calls([
call(**_relation_data),
call(clones=None, groups=None,
init_services=None, relation_id=None,
resource_params=None, resources=None),
])
@patch.object(hooks, 'get_hacluster_config')
def test_ha_joined_with_ipv6(self, _get_ha_config):
@ -839,23 +858,30 @@ class NeutronAPIHooksTests(CharmTestCase):
self.get_netmask_for_address.return_value = 'ffff.ffff.ffff.ffff'
_relation_data = {
'relation_id': None,
'init_services': {'res_neutron_haproxy': 'haproxy'},
'json_init_services': json.dumps({
'res_neutron_haproxy': 'haproxy'
}, sort_keys=True),
'corosync_bindiface': _ha_config['ha-bindiface'],
'corosync_mcastport': _ha_config['ha-mcastport'],
'resources': {
'json_resources': json.dumps({
'res_neutron_eth0_vip': 'ocf:heartbeat:IPv6addr',
'res_neutron_haproxy': 'lsb:haproxy'
},
'resource_params': {
}, sort_keys=True),
'json_resource_params': json.dumps({
'res_neutron_eth0_vip': vip_params,
'res_neutron_haproxy': 'op monitor interval="5s"'
},
'clones': {'cl_nova_haproxy': 'res_neutron_haproxy'}
}, sort_keys=True),
'json_clones': json.dumps({
'cl_nova_haproxy': 'res_neutron_haproxy'
}, sort_keys=True),
}
self._call_hook('ha-relation-joined')
self.relation_set.assert_called_with(
**_relation_data
)
self.relation_set.assert_has_calls([
call(**_relation_data),
call(clones=None, groups=None,
init_services=None, relation_id=None,
resource_params=None, resources=None),
])
@patch.object(hooks, 'get_hacluster_config')
def test_ha_joined_dns_ha(self, _get_hacluster_config):
@ -875,24 +901,36 @@ class NeutronAPIHooksTests(CharmTestCase):
'os-internal-hostname': None,
'os-public-hostname': 'neutron-api.maas',
}
args = {
_relation_data = {
'relation_id': None,
'corosync_bindiface': 'em0',
'corosync_mcastport': '8080',
'init_services': {'res_neutron_haproxy': 'haproxy'},
'resources': {'res_neutron_public_hostname': 'ocf:maas:dns',
'res_neutron_haproxy': 'lsb:haproxy'},
'resource_params': {
'json_init_services': json.dumps({
'res_neutron_haproxy': 'haproxy'
}, sort_keys=True),
'json_resources': json.dumps({
'res_neutron_public_hostname': 'ocf:maas:dns',
'res_neutron_haproxy': 'lsb:haproxy'
}, sort_keys=True),
'json_resource_params': json.dumps({
'res_neutron_public_hostname':
'params fqdn="neutron-api.maas" ip_address="10.0.0.1"',
'res_neutron_haproxy': 'op monitor interval="5s"'},
'clones': {'cl_nova_haproxy': 'res_neutron_haproxy'}
'res_neutron_haproxy': 'op monitor interval="5s"'
}, sort_keys=True),
'json_clones': json.dumps({
'cl_nova_haproxy': 'res_neutron_haproxy'
}, sort_keys=True),
}
self.update_dns_ha_resource_params.side_effect = _fake_update
hooks.ha_joined()
self.assertTrue(self.update_dns_ha_resource_params.called)
self.relation_set.assert_called_with(**args)
self.relation_set.assert_has_calls([
call(**_relation_data),
call(clones=None, groups=None,
init_services=None, relation_id=None,
resource_params=None, resources=None),
])
def test_ha_changed(self):
self.test_relation.set({