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