diff --git a/salt/states/postgres_cluster.py b/salt/states/postgres_cluster.py new file mode 100644 index 000000000000..f08d68bd1803 --- /dev/null +++ b/salt/states/postgres_cluster.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +''' +Management of PostgreSQL clusters +================================= + +The postgres_cluster state module is used to manage PostgreSQL clusters. +Clusters can be set as either absent or present + +.. code-block:: yaml + + create cluster 9.3 main: + postgres_cluster.present: + - name: 'main' + - version: '9.3' +''' +from __future__ import absolute_import + + +def __virtual__(): + ''' + Only load if the deb_postgres module is present + ''' + return 'postgres.cluster_exists' in __salt__ + + +def present(version, + name, + port=None, + encoding=None, + locale=None, + datadir=None): + ''' + Ensure that the named cluster is present with the specified properties. + For more information about all of these options see man pg_createcluster(1) + + version + Version of the postgresql cluster + + name + The name of the cluster + + port + Cluster port + + encoding + The character encoding scheme to be used in this database + + locale + Locale with which to create cluster + + datadir + Where the cluster is stored + + .. versionadded:: 2015.XX + ''' + msg = 'Cluster {0}/{1} is already present'.format(version, name) + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': msg} + + if __salt__['postgres.cluster_exists'](version, name): + # check cluster config is correct + infos = __salt__['postgres.cluster_list'](verbose=True) + info = infos['{0}/{1}'.format(version, name)] + # TODO: check locale en encoding configs also + if any((port != info['port'] if port else False, + datadir != info['datadir'] if datadir else False,)): + ret['comment'] = 'Cluster {0}/{1} has wrong parameters ' \ + 'which couldn\'t be changed on fly.' \ + .format(version, name) + ret['result'] = False + return ret + + # The cluster is not present, add it! + if __opts__.get('test'): + ret['result'] = None + msg = 'Cluster {0}/{1} is set to be created' + ret['comment'] = msg.format(version, name) + return ret + cluster = __salt__['postgres.cluster_create']( + version=version, + name=name, + port=port, + locale=locale, + encoding=encoding, + datadir=datadir) + if cluster: + msg = 'The cluster {0}/{1} has been created' + ret['comment'] = msg.format(version, name) + ret['changes']['{0}/{1}'.format(version, name)] = 'Present' + else: + msg = 'Failed to create cluster {0}/{1}' + ret['comment'] = msg.format(version, name) + ret['result'] = False + return ret + + +def absent(version, + name): + ''' + Ensure that the named cluster is absent + + version + Version of the postgresql server of the cluster to remove + + name + The name of the cluster to remove + + .. versionadded:: 2015.XX + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': ''} + + #check if cluster exists and remove it + if __salt__['postgres.cluster_exists'](version, name): + if __opts__.get('test'): + ret['result'] = None + msg = 'Cluster {0}/{1} is set to be removed' + ret['comment'] = msg.format(version, name) + return ret + if __salt__['postgres.cluster_remove'](version, name): + msg = 'Cluster {0}/{1} has been removed' + ret['comment'] = msg.format(version, name) + ret['changes'][name] = 'Absent' + return ret + + # fallback + ret['comment'] = 'Cluster {0}/{1} is not present, so it cannot ' \ + 'be removed'.format(version, name) + return ret diff --git a/tests/unit/states/postgres_cluster_test.py b/tests/unit/states/postgres_cluster_test.py new file mode 100644 index 000000000000..0c1d07278739 --- /dev/null +++ b/tests/unit/states/postgres_cluster_test.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Logilab ` +''' +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch +) + +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.states import postgres_cluster + +postgres_cluster.__opts__ = {} +postgres_cluster.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class PostgresClusterTestCase(TestCase): + ''' + Test cases for salt.states.postgres_cluster + ''' + # 'present' function tests: 1 + + def test_present(self): + ''' + Test to ensure that the named database is present + with the specified properties. + ''' + name = 'main' + version = '9.4' + + ret = {'name': name, + 'changes': {}, + 'result': False, + 'comment': ''} + + mock_t = MagicMock(return_value=True) + mock_f = MagicMock(return_value=False) + infos = {'{0}/{1}'.format(version, name): {}} + mock = MagicMock(return_value=infos) + with patch.dict(postgres_cluster.__salt__, + {'postgres.cluster_list': mock, + 'postgres.cluster_exists': mock_t, + 'postgres.cluster_create': mock_t, + }): + comt = ('Cluster {0}/{1} is already present'.format(version, name)) + ret.update({'comment': comt, 'result': True}) + self.assertDictEqual(postgres_cluster.present(version, name), ret) + infos['{0}/{1}'.format(version, name)]['port'] = 5433 + comt = ('Cluster {0}/{1} has wrong parameters ' + 'which couldn\'t be changed on fly.'.format(version, name)) + ret.update({'comment': comt, 'result': False}) + self.assertDictEqual(postgres_cluster.present(version, name, port=5434), ret) + infos['{0}/{1}'.format(version, name)]['datadir'] = '/tmp/' + comt = ('Cluster {0}/{1} has wrong parameters ' + 'which couldn\'t be changed on fly.'.format(version, name)) + ret.update({'comment': comt, 'result': False}) + self.assertDictEqual(postgres_cluster.present(version, name, port=5434), ret) + + with patch.dict(postgres_cluster.__salt__, + {'postgres.cluster_list': mock, + 'postgres.cluster_exists': mock_f, + 'postgres.cluster_create': mock_t, + }): + comt = 'The cluster {0}/{1} has been created'.format(version, name) + ret.update({'comment': comt, 'result': True, + 'changes': {'{0}/{1}'.format(version, name): 'Present'} + }) + self.assertDictEqual(postgres_cluster.present(version, name), + ret) + with patch.dict(postgres_cluster.__opts__, {'test': True}): + comt = 'Cluster {0}/{1} is set to be created'.format(version, name) + ret.update({'comment': comt, 'result': None, 'changes': {}}) + self.assertDictEqual(postgres_cluster.present(version, name), + ret) + + with patch.dict(postgres_cluster.__salt__, + {'postgres.cluster_list': mock, + 'postgres.cluster_exists': mock_f, + 'postgres.cluster_create': mock_f, + }): + comt = 'Failed to create cluster {0}/{1}'.format(version, name) + ret.update({'comment': comt, 'result': False}) + self.assertDictEqual(postgres_cluster.present(version, name), + ret) + + # 'absent' function tests: 1 + + def test_absent(self): + ''' + Test to ensure that the named database is absent. + ''' + name = 'main' + version = '9.4' + + ret = {'name': name, + 'changes': {}, + 'result': False, + 'comment': ''} + + mock_t = MagicMock(return_value=True) + mock = MagicMock(side_effect=[True, True, False]) + with patch.dict(postgres_cluster.__salt__, + {'postgres.cluster_exists': mock, + 'postgres.cluster_remove': mock_t}): + with patch.dict(postgres_cluster.__opts__, {'test': True}): + comt = ('Cluster {0}/{1} is set to be removed'.format(version, name)) + ret.update({'comment': comt, 'result': None}) + self.assertDictEqual(postgres_cluster.absent(version, name), ret) + + with patch.dict(postgres_cluster.__opts__, {'test': False}): + comt = ('Cluster {0}/{1} has been removed'.format(version, name)) + ret.update({'comment': comt, 'result': True, + 'changes': {name: 'Absent'}}) + self.assertDictEqual(postgres_cluster.absent(version, name), ret) + + comt = ('Cluster {0}/{1} is not present, so it cannot be removed' + .format(version, name)) + ret.update({'comment': comt, 'result': True, 'changes': {}}) + self.assertDictEqual(postgres_cluster.absent(version, name), ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(PostgresClusterTestCase, needs_daemon=False)