Support update parameters of a resource
This patch implements support to update parameters of an already existing resource using "crm configure load update FILE" The parameters of a resource are hashed using md5 and stored in the kv store, when the checksum doesn't match the resource is updated, otherwise discarded. Change-Id: I5735eaa1309c57e3620b0a6f68ffe13ec8165592 Closes-Bug: 1753432
This commit is contained in:
parent
63bb8018b4
commit
639dadb141
|
@ -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,
|
||||
|
@ -357,6 +357,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):
|
||||
|
|
|
@ -13,17 +13,23 @@
|
|||
# limitations under the License.
|
||||
|
||||
import commands
|
||||
import hashlib
|
||||
import re
|
||||
import subprocess
|
||||
import socket
|
||||
import tempfile
|
||||
import time
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
from distutils.version import StrictVersion
|
||||
from StringIO import StringIO
|
||||
from charmhelpers.core import unitdata
|
||||
from charmhelpers.core.hookenv import (
|
||||
log,
|
||||
ERROR,
|
||||
INFO,
|
||||
DEBUG,
|
||||
WARNING,
|
||||
)
|
||||
|
||||
|
||||
|
@ -51,7 +57,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 +227,60 @@ 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, force=False):
|
||||
"""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")
|
||||
"""
|
||||
db = unitdata.kv()
|
||||
res_hash = resource_checksum(res_name, res_type, res_params)
|
||||
|
||||
if not force and db.get('{}-{}'.format(res_name, res_type)) == res_hash:
|
||||
log("Resource {} already defined and parameters haven't changed"
|
||||
.format(res_name))
|
||||
return 0
|
||||
|
||||
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)
|
||||
|
||||
if retcode == 0:
|
||||
db.set('{}-{}'.format(res_name, res_type), res_hash)
|
||||
|
||||
return retcode
|
||||
|
||||
|
||||
def resource_checksum(res_name, res_type, res_params=None):
|
||||
"""Create a md5 checksum of the resource parameters.
|
||||
|
||||
: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")
|
||||
"""
|
||||
|
||||
m = hashlib.md5()
|
||||
m.update(res_type)
|
||||
m.update(res_params)
|
||||
return m.hexdigest()
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import mock
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
@ -32,6 +33,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,
|
||||
is_leader, crm_opt_exists,
|
||||
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
|
||||
is_leader.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,
|
||||
|
|
|
@ -14,8 +14,11 @@
|
|||
|
||||
import mock
|
||||
import pcmk
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from distutils.version import StrictVersion
|
||||
from charmhelpers.core import unitdata
|
||||
|
||||
|
||||
CRM_CONFIGURE_SHOW_XML = '''<?xml version="1.0" ?>
|
||||
|
@ -73,6 +76,12 @@ CRM_CONFIGURE_SHOW_XML_MAINT_MODE_TRUE = '''<?xml version="1.0" ?>
|
|||
|
||||
|
||||
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 +176,53 @@ 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'))
|
||||
|
||||
@mock.patch('subprocess.call')
|
||||
def test_crm_update_resource_exists_in_kv(self, mock_call):
|
||||
db = unitdata.kv()
|
||||
db.set('res_test-IPaddr2', 'ef395293b1b7c29c5bf1c99774f75cf4')
|
||||
|
||||
pcmk.crm_update_resource('res_test', 'IPaddr2',
|
||||
'params ip=1.2.3.4 cidr_netmask=255.0.0.0')
|
||||
|
||||
mock_call.assert_called_once_with([
|
||||
'juju-log',
|
||||
"Resource res_test already defined and parameters haven't changed"
|
||||
])
|
||||
|
||||
@mock.patch('subprocess.call')
|
||||
def test_crm_update_resource_exists_in_kv_force_true(self, mock_call):
|
||||
db = unitdata.kv()
|
||||
db.set('res_test-IPaddr2', 'ef395293b1b7c29c5bf1c99774f75cf4')
|
||||
|
||||
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.0.0.0'),
|
||||
force=True)
|
||||
|
||||
mock_call.assert_any_call(['crm', 'configure', 'load',
|
||||
'update', self.tmpfile.name])
|
||||
|
||||
def test_resource_checksum(self):
|
||||
r = pcmk.resource_checksum('res_test', 'IPaddr2',
|
||||
'params ip=1.2.3.4 cidr_netmask=255.0.0.0')
|
||||
self.assertEqual(r, 'ef395293b1b7c29c5bf1c99774f75cf4')
|
||||
|
|
Loading…
Reference in New Issue