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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import shutil
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import glob
|
import glob
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
|
||||||
import pcmk
|
import pcmk
|
||||||
import socket
|
|
||||||
|
|
||||||
from charmhelpers.core.hookenv import (
|
from charmhelpers.core.hookenv import (
|
||||||
is_leader,
|
is_leader,
|
||||||
|
@ -357,6 +357,15 @@ def ha_relation_changed():
|
||||||
'-inf: pingd lte 0' % (res_name, res_name))
|
'-inf: pingd lte 0' % (res_name, res_name))
|
||||||
pcmk.commit(cmd)
|
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)
|
log('Configuring Groups: %s' % (groups), level=DEBUG)
|
||||||
for grp_name, grp_params in groups.iteritems():
|
for grp_name, grp_params in groups.iteritems():
|
||||||
if not pcmk.crm_opt_exists(grp_name):
|
if not pcmk.crm_opt_exists(grp_name):
|
||||||
|
|
|
@ -13,17 +13,23 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import commands
|
import commands
|
||||||
|
import hashlib
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import socket
|
import socket
|
||||||
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
from distutils.version import StrictVersion
|
from distutils.version import StrictVersion
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
from charmhelpers.core import unitdata
|
||||||
from charmhelpers.core.hookenv import (
|
from charmhelpers.core.hookenv import (
|
||||||
log,
|
log,
|
||||||
ERROR,
|
ERROR,
|
||||||
|
INFO,
|
||||||
|
DEBUG,
|
||||||
|
WARNING,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,7 +57,7 @@ def wait_for_pcmk(retries=12, sleep=10):
|
||||||
|
|
||||||
|
|
||||||
def commit(cmd):
|
def commit(cmd):
|
||||||
subprocess.call(cmd.split())
|
return subprocess.call(cmd.split())
|
||||||
|
|
||||||
|
|
||||||
def is_resource_present(resource):
|
def is_resource_present(resource):
|
||||||
|
@ -221,3 +227,60 @@ def crm_version():
|
||||||
raise ValueError('error parsin crm version: %s' % ver)
|
raise ValueError('error parsin crm version: %s' % ver)
|
||||||
else:
|
else:
|
||||||
return StrictVersion(matched.group(1))
|
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 mock
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -32,6 +33,11 @@ class TestCorosyncConf(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.tmpdir = tempfile.mkdtemp()
|
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.object(hooks, 'write_maas_dns_address')
|
||||||
@mock.patch('pcmk.wait_for_pcmk')
|
@mock.patch('pcmk.wait_for_pcmk')
|
||||||
|
@ -56,7 +62,13 @@ class TestCorosyncConf(unittest.TestCase):
|
||||||
configure_cluster_global, configure_corosync,
|
configure_cluster_global, configure_corosync,
|
||||||
is_leader, crm_opt_exists,
|
is_leader, crm_opt_exists,
|
||||||
wait_for_pcmk, write_maas_dns_address):
|
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
|
is_leader.return_value = True
|
||||||
related_units.return_value = ['ha/0', 'ha/1', 'ha/2']
|
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']
|
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'},
|
'groups': {'grp_foo': 'res_foo'},
|
||||||
'colocations': {'co_foo': 'inf: grp_foo cl_foo'},
|
'colocations': {'co_foo': 'inf: grp_foo cl_foo'},
|
||||||
'resources': {'res_foo': 'ocf:heartbeat:IPaddr2',
|
'resources': {'res_foo': 'ocf:heartbeat:IPaddr2',
|
||||||
'res_bar': 'ocf:heartbear:IPv6addr'},
|
'res_bar': 'ocf:heartbear:IPv6addr',
|
||||||
'resource_params': {'res_foo': 'params bar'},
|
'res_ubuntu': 'IPaddr2'},
|
||||||
|
'resource_params': {'res_foo': 'params bar',
|
||||||
|
'res_ubuntu': 'params ubuntu=42'},
|
||||||
'ms': {'ms_foo': 'res_foo meta notify=true'},
|
'ms': {'ms_foo': 'res_foo meta notify=true'},
|
||||||
'orders': {'foo_after': 'inf: res_foo ms_foo'}}
|
'orders': {'foo_after': 'inf: res_foo ms_foo'}}
|
||||||
|
|
||||||
|
@ -85,7 +99,10 @@ class TestCorosyncConf(unittest.TestCase):
|
||||||
|
|
||||||
parse_data.side_effect = fake_parse_data
|
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)
|
relation_set.assert_any_call(relation_id='hanode:1', ready=True)
|
||||||
configure_stonith.assert_called_with()
|
configure_stonith.assert_called_with()
|
||||||
configure_monitor_host.assert_called_with()
|
configure_monitor_host.assert_called_with()
|
||||||
|
@ -101,7 +118,11 @@ class TestCorosyncConf(unittest.TestCase):
|
||||||
('ms', 'ms'),
|
('ms', 'ms'),
|
||||||
('order', 'orders')]:
|
('order', 'orders')]:
|
||||||
for name, params in rel_get_data[key].items():
|
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]
|
res_params = rel_get_data['resource_params'][name]
|
||||||
commit.assert_any_call(
|
commit.assert_any_call(
|
||||||
'crm -w -F configure %s %s %s %s' % (kw, name, params,
|
'crm -w -F configure %s %s %s %s' % (kw, name, params,
|
||||||
|
|
|
@ -14,8 +14,11 @@
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
import pcmk
|
import pcmk
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from distutils.version import StrictVersion
|
from distutils.version import StrictVersion
|
||||||
|
from charmhelpers.core import unitdata
|
||||||
|
|
||||||
|
|
||||||
CRM_CONFIGURE_SHOW_XML = '''<?xml version="1.0" ?>
|
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):
|
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')
|
@mock.patch('commands.getstatusoutput')
|
||||||
def test_crm_res_running_true(self, getstatusoutput):
|
def test_crm_res_running_true(self, getstatusoutput):
|
||||||
getstatusoutput.return_value = (0, ("resource res_nova_consoleauth is "
|
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',
|
mock_check_output.assert_called_with(['crm', 'configure', 'property',
|
||||||
'maintenance-mode=false'],
|
'maintenance-mode=false'],
|
||||||
universal_newlines=True)
|
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