From 02d83b2e4ead28c0f144f2e7495e0352843bf39e Mon Sep 17 00:00:00 2001 From: Felipe Reyes Date: Wed, 6 Jun 2018 19:38:01 -0400 Subject: [PATCH] Support update parameters of a resource This patch implements support to update parameters of an already existing resource using "crm configure load update FILE" Change-Id: I22730091d674145db4a1187b0904d9f88d9d8c6d Partial-Bug: #1753432 --- hooks/hooks.py | 17 ++++++++++---- hooks/pcmk.py | 37 +++++++++++++++++++++++++++++- unit_tests/test_hacluster_hooks.py | 31 +++++++++++++++++++++---- unit_tests/test_pcmk.py | 25 ++++++++++++++++++++ 4 files changed, 100 insertions(+), 10 deletions(-) diff --git a/hooks/hooks.py b/hooks/hooks.py index f304590..b6531fe 100755 --- a/hooks/hooks.py +++ b/hooks/hooks.py @@ -14,13 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import shutil -import os -import sys import glob +import os +import shutil +import socket +import sys import pcmk -import socket from charmhelpers.core.hookenv import ( is_leader, @@ -340,6 +340,15 @@ def ha_relation_changed(): '-inf: pingd lte 0' % (res_name, res_name)) pcmk.commit(cmd) + else: + # the resource already exists so it will be updated. + code = pcmk.crm_update_resource(res_name, res_type, + resource_params.get(res_name)) + if code != 0: + msg = "Cannot update pcmkr resource: {}".format(res_name) + status_set('blocked', msg) + raise Exception(msg) + log('Configuring Groups: %s' % (groups), level=DEBUG) for grp_name, grp_params in groups.iteritems(): if not pcmk.crm_opt_exists(grp_name): diff --git a/hooks/pcmk.py b/hooks/pcmk.py index 1562d81..e418f8a 100644 --- a/hooks/pcmk.py +++ b/hooks/pcmk.py @@ -16,6 +16,7 @@ import commands import re import subprocess import socket +import tempfile import time import xml.etree.ElementTree as etree @@ -24,6 +25,9 @@ from StringIO import StringIO from charmhelpers.core.hookenv import ( log, ERROR, + INFO, + DEBUG, + WARNING, ) @@ -51,7 +55,7 @@ def wait_for_pcmk(retries=12, sleep=10): def commit(cmd): - subprocess.call(cmd.split()) + return subprocess.call(cmd.split()) def is_resource_present(resource): @@ -221,3 +225,34 @@ def crm_version(): raise ValueError('error parsin crm version: %s' % ver) else: return StrictVersion(matched.group(1)) + + +def crm_update_resource(res_name, res_type, res_params=None): + """Update a resource using `crm configure load update` + + :param res_name: resource name + :param res_type: resource type (e.g. IPaddr2) + :param res_params: resource's parameters (e.g. "params ip=10.5.250.250") + """ + with tempfile.NamedTemporaryFile() as f: + f.write('primitive {} {}'.format(res_name, res_type)) + + if res_params: + f.write(' \\\n\t{}'.format(res_params)) + else: + f.write('\n') + + f.flush() + f.seek(0) + log('Updating resource {}'.format(res_name), level=INFO) + log('File content:\n{}'.format(f.read()), level=DEBUG) + cmd = "crm configure load update {}".format(f.name) + log('Update command: {}'.format(cmd)) + retcode = commit(cmd) + if retcode == 0: + level = DEBUG + else: + level = WARNING + + log('crm command exit code: {}'.format(retcode), level=level) + return retcode diff --git a/unit_tests/test_hacluster_hooks.py b/unit_tests/test_hacluster_hooks.py index 4034db0..7f2dc05 100644 --- a/unit_tests/test_hacluster_hooks.py +++ b/unit_tests/test_hacluster_hooks.py @@ -14,6 +14,7 @@ import mock import os +import shutil import sys import tempfile import unittest @@ -31,6 +32,11 @@ class TestCorosyncConf(unittest.TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp() + self.tmpfile = tempfile.NamedTemporaryFile(delete=False) + + def tearDown(self): + shutil.rmtree(self.tmpdir) + os.remove(self.tmpfile.name) @mock.patch.object(hooks, 'write_maas_dns_address') @mock.patch('pcmk.wait_for_pcmk') @@ -56,7 +62,13 @@ class TestCorosyncConf(unittest.TestCase): configure_cluster_global, configure_corosync, oldest_peer, crm_opt_exists, peer_units, wait_for_pcmk, write_maas_dns_address): - crm_opt_exists.return_value = False + + def fake_crm_opt_exists(res_name): + # res_ubuntu will take the "update resource" route + return res_name == "res_ubuntu" + + crm_opt_exists.side_effect = fake_crm_opt_exists + commit.return_value = 0 oldest_peer.return_value = True related_units.return_value = ['ha/0', 'ha/1', 'ha/2'] get_cluster_nodes.return_value = ['10.0.3.2', '10.0.3.3', '10.0.3.4'] @@ -75,8 +87,10 @@ class TestCorosyncConf(unittest.TestCase): 'groups': {'grp_foo': 'res_foo'}, 'colocations': {'co_foo': 'inf: grp_foo cl_foo'}, 'resources': {'res_foo': 'ocf:heartbeat:IPaddr2', - 'res_bar': 'ocf:heartbear:IPv6addr'}, - 'resource_params': {'res_foo': 'params bar'}, + 'res_bar': 'ocf:heartbear:IPv6addr', + 'res_ubuntu': 'IPaddr2'}, + 'resource_params': {'res_foo': 'params bar', + 'res_ubuntu': 'params ubuntu=42'}, 'ms': {'ms_foo': 'res_foo meta notify=true'}, 'orders': {'foo_after': 'inf: res_foo ms_foo'}} @@ -85,7 +99,10 @@ class TestCorosyncConf(unittest.TestCase): parse_data.side_effect = fake_parse_data - hooks.ha_relation_changed() + with mock.patch.object(tempfile, "NamedTemporaryFile", + side_effect=lambda: self.tmpfile): + hooks.ha_relation_changed() + relation_set.assert_any_call(relation_id='hanode:1', ready=True) configure_stonith.assert_called_with() configure_monitor_host.assert_called_with() @@ -101,7 +118,11 @@ class TestCorosyncConf(unittest.TestCase): ('ms', 'ms'), ('order', 'orders')]: for name, params in rel_get_data[key].items(): - if name in rel_get_data['resource_params']: + if name == "res_ubuntu": + commit.assert_any_call( + 'crm configure load update %s' % self.tmpfile.name) + + elif name in rel_get_data['resource_params']: res_params = rel_get_data['resource_params'][name] commit.assert_any_call( 'crm -w -F configure %s %s %s %s' % (kw, name, params, diff --git a/unit_tests/test_pcmk.py b/unit_tests/test_pcmk.py index a025c12..25fb526 100644 --- a/unit_tests/test_pcmk.py +++ b/unit_tests/test_pcmk.py @@ -14,6 +14,8 @@ import mock import pcmk +import os +import tempfile import unittest from distutils.version import StrictVersion @@ -73,6 +75,12 @@ CRM_CONFIGURE_SHOW_XML_MAINT_MODE_TRUE = ''' class TestPcmk(unittest.TestCase): + def setUp(self): + self.tmpfile = tempfile.NamedTemporaryFile(delete=False) + + def tearDown(self): + os.remove(self.tmpfile.name) + @mock.patch('commands.getstatusoutput') def test_crm_res_running_true(self, getstatusoutput): getstatusoutput.return_value = (0, ("resource res_nova_consoleauth is " @@ -167,3 +175,20 @@ class TestPcmk(unittest.TestCase): mock_check_output.assert_called_with(['crm', 'configure', 'property', 'maintenance-mode=false'], universal_newlines=True) + + @mock.patch('subprocess.call') + def test_crm_update_resource(self, mock_call): + mock_call.return_value = 0 + + with mock.patch.object(tempfile, "NamedTemporaryFile", + side_effect=lambda: self.tmpfile): + pcmk.crm_update_resource('res_test', 'IPaddr2', + ('params ip=1.2.3.4 ' + 'cidr_netmask=255.255.0.0')) + + mock_call.assert_any_call(['crm', 'configure', 'load', + 'update', self.tmpfile.name]) + with open(self.tmpfile.name, 'r') as f: + self.assertEqual(f.read(), + ('primitive res_test IPaddr2 \\\n' + '\tparams ip=1.2.3.4 cidr_netmask=255.255.0.0'))