charm-hacluster/unit_tests/test_pcmk.py

247 lines
11 KiB
Python

# Copyright 2016 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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" ?>
<cib num_updates="1" dc-uuid="1002" update-origin="juju-34fde5-0" crm_feature_set="3.0.7" validate-with="pacemaker-1.2" update-client="cibadmin" epoch="1103" admin_epoch="0" cib-last-written="Fri Aug 4 13:45:06 2017" have-quorum="1">
<configuration>
<crm_config>
<cluster_property_set id="cib-bootstrap-options">
<nvpair id="cib-bootstrap-options-dc-version" name="dc-version" value="1.1.10-42f2063"/>
<nvpair id="cib-bootstrap-options-cluster-infrastructure" name="cluster-infrastructure" value="corosync"/>
<nvpair name="no-quorum-policy" value="stop" id="cib-bootstrap-options-no-quorum-policy"/>
<nvpair name="stonith-enabled" value="false" id="cib-bootstrap-options-stonith-enabled"/>
</cluster_property_set>
</crm_config>
<nodes>
<node id="1002" uname="juju-34fde5-0"/>
</nodes>
<resources/>
<constraints/>
<rsc_defaults>
<meta_attributes id="rsc-options">
<nvpair name="resource-stickiness" value="100" id="rsc-options-resource-stickiness"/>
</meta_attributes>
</rsc_defaults>
</configuration>
</cib>
''' # noqa
CRM_CONFIGURE_SHOW_XML_MAINT_MODE_TRUE = '''<?xml version="1.0" ?>
<cib num_updates="1" dc-uuid="1002" update-origin="juju-34fde5-0" crm_feature_set="3.0.7" validate-with="pacemaker-1.2" update-client="cibadmin" epoch="1103" admin_epoch="0" cib-last-written="Fri Aug 4 13:45:06 2017" have-quorum="1">
<configuration>
<crm_config>
<cluster_property_set id="cib-bootstrap-options">
<nvpair id="cib-bootstrap-options-dc-version" name="dc-version" value="1.1.10-42f2063"/>
<nvpair id="cib-bootstrap-options-cluster-infrastructure" name="cluster-infrastructure" value="corosync"/>
<nvpair name="no-quorum-policy" value="stop" id="cib-bootstrap-options-no-quorum-policy"/>
<nvpair name="stonith-enabled" value="false" id="cib-bootstrap-options-stonith-enabled"/>
<nvpair name="maintenance-mode" value="true" id="cib-bootstrap-options-maintenance-mode"/>
</cluster_property_set>
</crm_config>
<nodes>
<node id="1002" uname="juju-34fde5-0"/>
</nodes>
<resources/>
<constraints/>
<rsc_defaults>
<meta_attributes id="rsc-options">
<nvpair name="resource-stickiness" value="100" id="rsc-options-resource-stickiness"/>
</meta_attributes>
</rsc_defaults>
</configuration>
</cib>
''' # noqa
CRM_NODE_STATUS_XML = b'''
<nodes>
<node id="1000" uname="juju-982848-zaza-ce47c58f6c88-10"/>
<node id="1001" uname="juju-982848-zaza-ce47c58f6c88-9"/>
<node id="1002" uname="juju-982848-zaza-ce47c58f6c88-11"/>
</nodes>
'''
class TestPcmk(unittest.TestCase):
def setUp(self):
self.tmpfile = tempfile.NamedTemporaryFile(delete=False)
def tearDown(self):
os.remove(self.tmpfile.name)
@mock.patch('subprocess.getstatusoutput')
def test_crm_res_running_true(self, getstatusoutput):
getstatusoutput.return_value = (0, ("resource res_nova_consoleauth is "
"running on: juju-xxx-machine-6"))
self.assertTrue(pcmk.crm_res_running('res_nova_consoleauth'))
@mock.patch('subprocess.getstatusoutput')
def test_crm_res_running_stopped(self, getstatusoutput):
getstatusoutput.return_value = (0, ("resource res_nova_consoleauth is "
"NOT running"))
self.assertFalse(pcmk.crm_res_running('res_nova_consoleauth'))
@mock.patch('subprocess.getstatusoutput')
def test_crm_res_running_undefined(self, getstatusoutput):
getstatusoutput.return_value = (1, "foobar")
self.assertFalse(pcmk.crm_res_running('res_nova_consoleauth'))
@mock.patch('socket.gethostname')
@mock.patch('subprocess.getstatusoutput')
def test_wait_for_pcmk(self, getstatusoutput, gethostname):
# Pacemaker is down
gethostname.return_value = 'hanode-1'
getstatusoutput.return_value = (1, 'Not the hostname')
with self.assertRaises(pcmk.ServicesNotUp):
pcmk.wait_for_pcmk(retries=2, sleep=0)
# Pacemaker is up
gethostname.return_value = 'hanode-1'
getstatusoutput.return_value = (0, 'Hosname: hanode-1')
self.assertTrue(pcmk.wait_for_pcmk(retries=2, sleep=0))
@mock.patch('subprocess.check_output')
def test_crm_version(self, mock_check_output):
# xenial
mock_check_output.return_value = "crm 2.2.0\n"
ret = pcmk.crm_version()
self.assertEqual(StrictVersion('2.2.0'), ret)
mock_check_output.assert_called_with(['crm', '--version'],
universal_newlines=True)
# trusty
mock_check_output.mock_reset()
mock_check_output.return_value = (
"1.2.5 (Build f2f315daf6a5fd7ddea8e564cd289aa04218427d)\n")
ret = pcmk.crm_version()
self.assertEqual(StrictVersion('1.2.5'), ret)
mock_check_output.assert_called_with(['crm', '--version'],
universal_newlines=True)
@mock.patch('subprocess.check_output')
@mock.patch.object(pcmk, 'crm_version')
def test_get_property(self, mock_crm_version, mock_check_output):
mock_crm_version.return_value = StrictVersion('2.2.0') # xenial
mock_check_output.return_value = 'false\n'
self.assertEqual('false\n', pcmk.get_property('maintenance-mode'))
mock_check_output.assert_called_with(['crm', 'configure',
'show-property',
'maintenance-mode'],
universal_newlines=True)
mock_crm_version.return_value = StrictVersion('2.4.0')
mock_check_output.reset_mock()
self.assertEqual('false\n', pcmk.get_property('maintenance-mode'))
mock_check_output.assert_called_with(['crm', 'configure',
'get-property',
'maintenance-mode'],
universal_newlines=True)
@mock.patch('subprocess.check_output')
@mock.patch.object(pcmk, 'crm_version')
def test_get_property_from_xml(self, mock_crm_version, mock_check_output):
mock_crm_version.return_value = StrictVersion('1.2.5') # trusty
mock_check_output.return_value = CRM_CONFIGURE_SHOW_XML
self.assertRaises(pcmk.PropertyNotFound, pcmk.get_property,
'maintenance-mode')
mock_check_output.assert_called_with(['crm', 'configure',
'show', 'xml'],
universal_newlines=True)
mock_check_output.reset_mock()
mock_check_output.return_value = CRM_CONFIGURE_SHOW_XML_MAINT_MODE_TRUE
self.assertEqual('true', pcmk.get_property('maintenance-mode'))
mock_check_output.assert_called_with(['crm', 'configure',
'show', 'xml'],
universal_newlines=True)
@mock.patch('subprocess.check_call')
def test_set_property(self, mock_check_output):
pcmk.set_property('maintenance-mode', 'false')
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, 'rt') 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')
@mock.patch('subprocess.check_output', return_value=CRM_NODE_STATUS_XML)
def test_list_nodes(self, mock_check_output):
self.assertSequenceEqual(
pcmk.list_nodes(),
[
'juju-982848-zaza-ce47c58f6c88-10',
'juju-982848-zaza-ce47c58f6c88-11',
'juju-982848-zaza-ce47c58f6c88-9'])
mock_check_output.assert_called_once_with(['crm', 'node', 'status'])