1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Add info command to redis module (#286)

* Add info command to redis module

* Fix sanity test

* Create a separate redis_info module

* Type of arguments in documentation was determined

* Add redis_info test

* Fix sanity test

* Add integration test

* Add integration platforms (centos7/8,fedora30/31,opensuse15+py2,ubuntu1604/1804)

* Add centos6 support

* Fix suggestions

* Add contact email
This commit is contained in:
Pavlo Bashynskyi 2020-05-12 20:18:24 +03:00 committed by GitHub
parent 2e60bdcdfe
commit 80d41583d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 555 additions and 0 deletions

View file

@ -73,6 +73,8 @@ notes:
- If the redis master instance we are making slave of is password protected - If the redis master instance we are making slave of is password protected
this needs to be in the redis.conf in the masterauth variable this needs to be in the redis.conf in the masterauth variable
seealso:
- module: redis_info
requirements: [ redis ] requirements: [ redis ]
author: "Xabier Larrakoetxea (@slok)" author: "Xabier Larrakoetxea (@slok)"
''' '''

View file

@ -0,0 +1,241 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Pavlo Bashynskyi (@levonet) <levonet@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = r'''
---
module: redis_info
short_description: Gather information about Redis servers
description:
- Gathers information and statistics about Redis servers.
options:
login_host:
description:
- The host running the database.
type: str
default: localhost
login_port:
description:
- The port to connect to.
type: int
default: 6379
login_password:
description:
- The password used to authenticate with, when authentication is enabled for the Redis server.
type: str
notes:
- Requires the redis-py Python package on the remote host. You can
install it with pip (C(pip install redis)) or with a package manager.
U(https://github.com/andymccurdy/redis-py)
seealso:
- module: redis
requirements: [ redis ]
author: "Pavlo Bashynskyi (@levonet)"
'''
EXAMPLES = r'''
- name: Get server information
community.general.redis_info:
register: result
- name: Print server information
debug:
var: result.info
'''
RETURN = r'''
info:
description: The default set of server information sections U(https://redis.io/commands/info).
returned: success
type: dict
sample: {
"active_defrag_hits": 0,
"active_defrag_key_hits": 0,
"active_defrag_key_misses": 0,
"active_defrag_misses": 0,
"active_defrag_running": 0,
"allocator_active": 932409344,
"allocator_allocated": 932062792,
"allocator_frag_bytes": 346552,
"allocator_frag_ratio": 1.0,
"allocator_resident": 947253248,
"allocator_rss_bytes": 14843904,
"allocator_rss_ratio": 1.02,
"aof_current_rewrite_time_sec": -1,
"aof_enabled": 0,
"aof_last_bgrewrite_status": "ok",
"aof_last_cow_size": 0,
"aof_last_rewrite_time_sec": -1,
"aof_last_write_status": "ok",
"aof_rewrite_in_progress": 0,
"aof_rewrite_scheduled": 0,
"arch_bits": 64,
"atomicvar_api": "atomic-builtin",
"blocked_clients": 0,
"client_recent_max_input_buffer": 4,
"client_recent_max_output_buffer": 0,
"cluster_enabled": 0,
"config_file": "",
"configured_hz": 10,
"connected_clients": 4,
"connected_slaves": 0,
"db0": {
"avg_ttl": 1945628530,
"expires": 16,
"keys": 3341411
},
"evicted_keys": 0,
"executable": "/data/redis-server",
"expired_keys": 9,
"expired_stale_perc": 1.72,
"expired_time_cap_reached_count": 0,
"gcc_version": "9.2.0",
"hz": 10,
"instantaneous_input_kbps": 0.0,
"instantaneous_ops_per_sec": 0,
"instantaneous_output_kbps": 0.0,
"keyspace_hits": 0,
"keyspace_misses": 0,
"latest_fork_usec": 0,
"lazyfree_pending_objects": 0,
"loading": 0,
"lru_clock": 11603632,
"master_repl_offset": 118831417,
"master_replid": "0d904704e424e38c3cd896783e9f9d28d4836e5e",
"master_replid2": "0000000000000000000000000000000000000000",
"maxmemory": 0,
"maxmemory_human": "0B",
"maxmemory_policy": "noeviction",
"mem_allocator": "jemalloc-5.1.0",
"mem_aof_buffer": 0,
"mem_clients_normal": 49694,
"mem_clients_slaves": 0,
"mem_fragmentation_bytes": 12355480,
"mem_fragmentation_ratio": 1.01,
"mem_not_counted_for_evict": 0,
"mem_replication_backlog": 1048576,
"migrate_cached_sockets": 0,
"multiplexing_api": "epoll",
"number_of_cached_scripts": 0,
"os": "Linux 3.10.0-862.14.4.el7.x86_64 x86_64",
"process_id": 1,
"pubsub_channels": 0,
"pubsub_patterns": 0,
"rdb_bgsave_in_progress": 0,
"rdb_changes_since_last_save": 671,
"rdb_current_bgsave_time_sec": -1,
"rdb_last_bgsave_status": "ok",
"rdb_last_bgsave_time_sec": -1,
"rdb_last_cow_size": 0,
"rdb_last_save_time": 1588702236,
"redis_build_id": "a31260535f820267",
"redis_git_dirty": 0,
"redis_git_sha1": 0,
"redis_mode": "standalone",
"redis_version": "999.999.999",
"rejected_connections": 0,
"repl_backlog_active": 1,
"repl_backlog_first_byte_offset": 118707937,
"repl_backlog_histlen": 123481,
"repl_backlog_size": 1048576,
"role": "master",
"rss_overhead_bytes": -3051520,
"rss_overhead_ratio": 1.0,
"run_id": "8d252f66c3ef89bd60a060cf8dc5cfe3d511c5e4",
"second_repl_offset": 118830003,
"slave_expires_tracked_keys": 0,
"sync_full": 0,
"sync_partial_err": 0,
"sync_partial_ok": 0,
"tcp_port": 6379,
"total_commands_processed": 885,
"total_connections_received": 10,
"total_net_input_bytes": 802709255,
"total_net_output_bytes": 31754,
"total_system_memory": 135029538816,
"total_system_memory_human": "125.76G",
"uptime_in_days": 53,
"uptime_in_seconds": 4631778,
"used_cpu_sys": 4.668282,
"used_cpu_sys_children": 0.002191,
"used_cpu_user": 4.21088,
"used_cpu_user_children": 0.0,
"used_memory": 931908760,
"used_memory_dataset": 910774306,
"used_memory_dataset_perc": "97.82%",
"used_memory_human": "888.74M",
"used_memory_lua": 37888,
"used_memory_lua_human": "37.00K",
"used_memory_overhead": 21134454,
"used_memory_peak": 932015216,
"used_memory_peak_human": "888.84M",
"used_memory_peak_perc": "99.99%",
"used_memory_rss": 944201728,
"used_memory_rss_human": "900.46M",
"used_memory_scripts": 0,
"used_memory_scripts_human": "0B",
"used_memory_startup": 791264
}
'''
import traceback
REDIS_IMP_ERR = None
try:
from redis import StrictRedis
HAS_REDIS_PACKAGE = True
except ImportError:
REDIS_IMP_ERR = traceback.format_exc()
HAS_REDIS_PACKAGE = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
def redis_client(**client_params):
return StrictRedis(**client_params)
# Module execution.
def main():
module = AnsibleModule(
argument_spec=dict(
login_host=dict(type='str', default='localhost'),
login_port=dict(type='int', default=6379),
login_password=dict(type='str', no_log=True),
),
supports_check_mode=True,
)
if not HAS_REDIS_PACKAGE:
module.fail_json(msg=missing_required_lib('redis'), exception=REDIS_IMP_ERR)
login_host = module.params['login_host']
login_port = module.params['login_port']
login_password = module.params['login_password']
# Connect and check
client = redis_client(host=login_host, port=login_port, password=login_password)
try:
client.ping()
except Exception as e:
module.fail_json(msg="unable to connect to database: %s" % to_native(e), exception=traceback.format_exc())
info = client.info()
module.exit_json(changed=False, info=info)
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
./database/misc/redis_info.py

View file

@ -0,0 +1,5 @@
destructive
shippable/posix/group1
skip/aix
skip/osx
skip/rhel

View file

@ -0,0 +1,4 @@
---
redis_password: PASS
master_port: 6379
slave_port: 6380

View file

@ -0,0 +1,2 @@
dependencies:
- setup_redis_replication

View file

@ -0,0 +1,42 @@
# Copyright: (c) 2020, Pavlo Bashynskyi (@levonet) <levonet@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- name: redis_info - connect to master with default host/port
community.general.redis_info:
login_password: "{{ redis_password }}"
register: result
- assert:
that:
- result is not changed
- result.info is defined
- result.info.tcp_port == master_port
- result.info.role == 'master'
- name: redis_info - connect to master (check)
community.general.redis_info:
login_host: 127.0.0.1
login_port: "{{ master_port }}"
login_password: "{{ redis_password }}"
check_mode: yes
register: result
- assert:
that:
- result is not changed
- result.info is defined
- result.info.tcp_port == master_port
- result.info.role == 'master'
- name: redis_info - connect to slave
community.general.redis_info:
login_port: "{{ slave_port }}"
login_password: "{{ redis_password }}"
register: result
- assert:
that:
- result is not changed
- result.info is defined
- result.info.tcp_port == slave_port
- result.info.role == 'slave'

View file

@ -0,0 +1,35 @@
# General
redis_packages:
Ubuntu:
- redis-server
openSUSE Leap:
- redis
Fedora:
- redis
CentOS:
- redis
FreeBSD:
- redis
redis_bin:
Ubuntu: /usr/bin/redis-server
openSUSE Leap: /usr/sbin/redis-server
Fedora: /usr/bin/redis-server
CentOS: /usr/bin/redis-server
FreeBSD: /usr/local/bin/redis-server
redis_module: "{{ (ansible_python_version is version('2.7', '>=')) | ternary('redis', 'redis==2.10.6') }}"
redis_password: PASS
# Master
master_port: 6379
master_conf: /etc/redis-master.conf
master_datadir: /var/lib/redis-master
master_logdir: /var/log/redis-master
# Slave
slave_port: 6380
slave_conf: /etc/redis-slave.conf
slave_datadir: /var/lib/redis-slave
slave_logdir: /var/log/redis-slave

View file

@ -0,0 +1,34 @@
- name: stop redis services
shell: |
kill -TERM $(cat /var/run/redis_{{ master_port }}.pid)
kill -TERM $(cat /var/run/redis_{{ slave_port }}.pid)
listen: cleanup redis
- name: remove redis packages
action: "{{ ansible_facts.pkg_mgr }}"
args:
name: "{{ item }}"
state: absent
loop: "{{ redis_packages[ansible_distribution] }}"
listen: cleanup redis
- name: remove pip packages
pip:
name: redis
state: absent
listen: cleanup redis
- name: remove redis data
file:
path: "{{ item }}"
state: absent
loop:
- "{{ master_conf }}"
- "{{ master_datadir }}"
- "{{ master_logdir }}"
- /var/run/redis_{{ master_port }}.pid
- "{{ slave_conf }}"
- "{{ slave_datadir }}"
- "{{ slave_logdir }}"
- /var/run/redis_{{ slave_port }}.pid
listen: cleanup redis

View file

@ -0,0 +1,2 @@
dependencies:
- setup_pkg_mgr

View file

@ -0,0 +1,6 @@
# Copyright: (c) 2020, Pavlo Bashynskyi (@levonet) <levonet@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- import_tasks: setup_redis_cluster.yml
when:
- ansible_distribution in ['CentOS', 'Fedora', 'FreeBSD', 'openSUSE Leap', 'Ubuntu']

View file

@ -0,0 +1,105 @@
# We run two servers listening different ports
# to be able to check replication (one server for master, another for slave).
- name: Install redis server apt dependencies
apt:
name: "{{ redis_packages[ansible_distribution] }}"
state: latest
policy_rc_d: 101
when:
- ansible_facts.pkg_mgr == 'apt'
notify: cleanup redis
- name: Install redis server rpm dependencies
yum:
name: "{{ redis_packages[ansible_distribution] }}"
state: latest
when:
- ansible_facts.pkg_mgr == 'yum'
notify: cleanup redis
- name: Install redis rpm dependencies
dnf:
name: "{{ redis_packages[ansible_distribution] }}"
state: latest
when: ansible_facts.pkg_mgr == 'dnf'
notify: cleanup redis
- name: Install redis server zypper dependencies
zypper:
name: "{{ redis_packages[ansible_distribution] }}"
state: latest
when:
- ansible_facts.pkg_mgr == 'community.general.zypper'
notify: cleanup redis
- name: Install redis FreeBSD dependencies
community.general.pkgng:
name: "{{ redis_packages[ansible_distribution] }}"
state: latest
when:
- ansible_facts.pkg_mgr == 'community.general.pkgng'
notify: cleanup redis
- name: Install redis module
pip:
name: "{{ redis_module }}"
state: present
notify: cleanup redis
- name: Create redis directories
file:
path: "{{ item }}"
state: directory
owner: redis
group: redis
loop:
- "{{ master_datadir }}"
- "{{ master_logdir }}"
- "{{ slave_datadir }}"
- "{{ slave_logdir }}"
- name: Create redis configs
copy:
dest: "{{ item.file }}"
content: |
daemonize yes
port {{ item.port }}
pidfile /var/run/redis_{{ item.port }}.pid
logfile {{ item.logdir }}/redis.log
dir {{ item.datadir }}
requirepass {{ redis_password }}
masterauth {{ redis_password }}
loop:
- file: "{{ master_conf }}"
port: "{{ master_port }}"
logdir: "{{ master_logdir }}"
datadir: "{{ master_datadir }}"
- file: "{{ slave_conf }}"
port: "{{ slave_port }}"
logdir: "{{ slave_logdir }}"
datadir: "{{ slave_datadir }}"
- name: Start redis master
shell: "{{ redis_bin[ansible_distribution] }} {{ master_conf }}"
- name: Start redis slave
shell: "{{ redis_bin[ansible_distribution] }} {{ slave_conf }} --slaveof 127.0.0.1 {{ master_port }}"
- name: Wait for redis master to be started
wait_for:
host: 127.0.0.1
port: "{{ master_port }}"
state: started
delay: 1
connect_timeout: 5
timeout: 30
- name: Wait for redis slave to be started
wait_for:
host: 127.0.0.1
port: "{{ slave_port }}"
state: started
delay: 1
connect_timeout: 5
timeout: 30

View file

@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Pavlo Bashynskyi (@levonet) <levonet@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.community.general.tests.unit.compat.mock import patch, MagicMock
from ansible_collections.community.general.plugins.modules.database.misc import redis_info
from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
class FakeRedisClient(MagicMock):
def ping(self):
pass
def info(self):
return {'redis_version': '999.999.999'}
class FakeRedisClientFail(MagicMock):
def ping(self):
raise Exception('Test Error')
def info(self):
pass
class TestRedisInfoModule(ModuleTestCase):
def setUp(self):
super(TestRedisInfoModule, self).setUp()
redis_info.HAS_REDIS_PACKAGE = True
self.module = redis_info
def tearDown(self):
super(TestRedisInfoModule, self).tearDown()
def patch_redis_client(self, **kwds):
return patch('ansible_collections.community.general.plugins.modules.database.misc.redis_info.redis_client', autospec=True, **kwds)
def test_without_parameters(self):
"""Test without parameters"""
with self.patch_redis_client(side_effect=FakeRedisClient) as redis_client:
with self.assertRaises(AnsibleExitJson) as result:
set_module_args({})
self.module.main()
self.assertEqual(redis_client.call_count, 1)
self.assertEqual(redis_client.call_args, ({'host': 'localhost', 'port': 6379, 'password': None},))
self.assertEqual(result.exception.args[0]['info']['redis_version'], '999.999.999')
def test_with_parameters(self):
"""Test with all parameters"""
with self.patch_redis_client(side_effect=FakeRedisClient) as redis_client:
with self.assertRaises(AnsibleExitJson) as result:
set_module_args({
'login_host': 'test',
'login_port': 1234,
'login_password': 'PASS'
})
self.module.main()
self.assertEqual(redis_client.call_count, 1)
self.assertEqual(redis_client.call_args, ({'host': 'test', 'port': 1234, 'password': 'PASS'},))
self.assertEqual(result.exception.args[0]['info']['redis_version'], '999.999.999')
def test_with_fail_client(self):
"""Test failure message"""
with self.patch_redis_client(side_effect=FakeRedisClientFail) as redis_client:
with self.assertRaises(AnsibleFailJson) as result:
set_module_args({})
self.module.main()
self.assertEqual(redis_client.call_count, 1)
self.assertEqual(result.exception.args[0]['msg'], 'unable to connect to database: Test Error')