1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3#
4# Copyright 2015 Nandaja Varma <nvarma@redhat.com>
5# Copyright 2018 Red Hat, Inc.
6#
7# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
8#
9
10from __future__ import absolute_import, division, print_function
11__metaclass__ = type
12
13ANSIBLE_METADATA = {'metadata_version': '1.1',
14                    'status': ['preview'],
15                    'supported_by': 'community'}
16
17DOCUMENTATION = '''
18---
19module: gluster_peer
20short_description: Attach/Detach peers to/from the cluster
21description:
22  - Create or diminish a GlusterFS trusted storage pool. A set of nodes can be
23    added into an existing trusted storage pool or a new storage pool can be
24    formed. Or, nodes can be removed from an existing trusted storage pool.
25author: Sachidananda Urs (@sac)
26options:
27    state:
28       choices: ["present", "absent"]
29       default: "present"
30       description:
31          - Determines whether the nodes should be attached to the pool or
32            removed from the pool. If the state is present, nodes will be
33            attached to the pool. If state is absent, nodes will be detached
34            from the pool.
35       required: true
36       type: str
37    nodes:
38       description:
39          - List of nodes that have to be probed into the pool.
40       required: true
41       type: list
42    force:
43       type: bool
44       default: "false"
45       description:
46          - Applicable only while removing the nodes from the pool. gluster
47            will refuse to detach a node from the pool if any one of the node
48            is down, in such cases force can be used.
49requirements:
50  - GlusterFS > 3.2
51notes:
52  - This module does not support check mode.
53'''
54
55EXAMPLES = '''
56- name: Create a trusted storage pool
57  gluster.gluster.gluster_peer:
58        state: present
59        nodes:
60             - 10.0.1.5
61             - 10.0.1.10
62
63- name: Delete a node from the trusted storage pool
64  gluster.gluster.gluster_peer:
65         state: absent
66         nodes:
67              - 10.0.1.10
68
69- name: Delete a node from the trusted storage pool by force
70  gluster.gluster.gluster_peer:
71         state: absent
72         nodes:
73              - 10.0.0.1
74         force: true
75'''
76
77RETURN = '''
78'''
79
80from ansible.module_utils.basic import AnsibleModule
81from distutils.version import LooseVersion
82
83
84class Peer(object):
85    def __init__(self, module):
86        self.module = module
87        self.state = self.module.params['state']
88        self.nodes = self.module.params['nodes']
89        self.glustercmd = self.module.get_bin_path('gluster', True)
90        self.lang = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')
91        self.action = ''
92        self.force = ''
93
94    def gluster_peer_ops(self):
95        if not self.nodes:
96            self.module.fail_json(msg="nodes list cannot be empty")
97        self.force = 'force' if self.module.params.get('force') else ''
98        if self.state == 'present':
99            self.nodes = self.get_to_be_probed_hosts(self.nodes)
100            self.action = 'probe'
101            # In case of peer probe, we do not need `force'
102            self.force = ''
103        else:
104            self.action = 'detach'
105        self.call_peer_commands()
106
107    def get_to_be_probed_hosts(self, hosts):
108        peercmd = [self.glustercmd, 'pool', 'list', '--mode=script']
109        rc, output, err = self.module.run_command(peercmd,
110                                                  environ_update=self.lang)
111        peers_in_cluster = [line.split('\t')[1].strip() for
112                            line in filter(None, output.split('\n')[1:])]
113        try:
114            peers_in_cluster.remove('localhost')
115        except ValueError:
116            # It is ok not to have localhost in list
117            pass
118        hosts_to_be_probed = [host for host in hosts if host not in
119                              peers_in_cluster]
120        return hosts_to_be_probed
121
122    def call_peer_commands(self):
123        result = {}
124        result['msg'] = ''
125        result['changed'] = False
126
127        for node in self.nodes:
128            peercmd = [self.glustercmd, 'peer', self.action, node, '--mode=script']
129            if self.force:
130                peercmd.append(self.force)
131            rc, out, err = self.module.run_command(peercmd,
132                                                   environ_update=self.lang)
133            if rc:
134                result['rc'] = rc
135                result['msg'] = err
136                # Fail early, do not wait for the loop to finish
137                self.module.fail_json(**result)
138            else:
139                if 'already in peer' in out or \
140                   'localhost not needed' in out:
141                    result['changed'] |= False
142                else:
143                    result['changed'] = True
144        self.module.exit_json(**result)
145
146
147def main():
148    module = AnsibleModule(
149        argument_spec=dict(
150            force=dict(type='bool', required=False),
151            nodes=dict(type='list', required=True),
152            state=dict(type='str', choices=['absent', 'present'],
153                       default='present'),
154        ),
155        supports_check_mode=False
156    )
157    pops = Peer(module)
158    required_version = "3.2"
159    # Verify if required GlusterFS version is installed
160    if is_invalid_gluster_version(module, required_version):
161        module.fail_json(msg="GlusterFS version > %s is required" %
162                         required_version)
163    pops.gluster_peer_ops()
164
165
166def is_invalid_gluster_version(module, required_version):
167    cmd = module.get_bin_path('gluster', True) + ' --version'
168    result = module.run_command(cmd)
169    ver_line = result[1].split('\n')[0]
170    version = ver_line.split(' ')[1]
171    # If the installed version is less than 3.2, it is an invalid version
172    # return True
173    return LooseVersion(version) < LooseVersion(required_version)
174
175
176if __name__ == "__main__":
177    main()
178