1#!/usr/local/bin/python3.8
2# -*- coding: utf-8 -*-
3#
4# Copyright 2015 Nandaja Varma <nvarma@redhat.com>
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU Library General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
19# USA.
20
21from __future__ import absolute_import, division, print_function
22__metaclass__ = type
23
24DOCUMENTATION = """
25module: geo_rep
26short_description: Manage geo-replication sessions
27description:
28  - Create, stop, delete and configure geo-replication session
29author: Sachidananda Urs (@sac)
30options:
31  action:
32    description:
33      - Action to be performed on geo-replication session.
34    required: true
35    choices: ['create', 'start', 'stop', 'delete', 'pause', 'resume', 'config']
36    type: str
37  mastervol:
38    description:
39      - Master volume name.
40    type: str
41  slavevol:
42    description:
43      - Slave volume name.
44    type: str
45  force:
46    description:
47      - force the system to perform the action.
48    type: str
49  georepuser:
50    description:
51      - Username to be used for the action being performed.
52    type: str
53  gluster_log_file:
54    description:
55      - The path to the geo-replication glusterfs log file.
56    type: str
57  gluster_log_level:
58    description:
59      - The log level for glusterfs processes.
60    type: str
61  log_file:
62    description:
63      - The path to the geo-replication log file.
64    type: str
65  log_level:
66    description:
67      - The log level for geo-replication.
68    type: str
69  changelog_log_level:
70    description:
71      - The log level for the changelog.
72    type: str
73  ssh_command:
74    description:
75      - The SSH command to connect to the remote machine.
76    type: str
77  rsync_command:
78    description:
79      - The command to use for setting synchronizing method for the files.
80    type: str
81  use_tarssh:
82    description:
83      - To use tar over ssh.
84    type: str
85  volume_id:
86    description:
87      - deletes the existing master UID for the intermediate/slave node.
88    type: str
89  timeout:
90    description:
91      - timeout period.
92    type: str
93  sync_jobs:
94    description:
95      - number of sync-jobs .
96    type: str
97  ignore_deletes:
98    description:
99      - file deletion on the master will not trigger a delete operation on the slave.
100    type: str
101  checkpoint:
102    description:
103      - Sets a checkpoint with the given option.
104    type: str
105  sync_acls:
106    description:
107      - Syncs acls to the Slave cluster.
108    type: str
109  sync_xattrs:
110    description:
111      - Syncs extended attributes to the Slave cluster.
112    type: str
113  log_rsync_performance:
114    description:
115      - for recording the rsync performance in log files.
116    type: str
117  rsync_options:
118    description:
119      - Additional options to rsync.
120    type: str
121  use_meta_volume:
122    description:
123      - to use meta volume in Geo-replication.
124    type: str
125  meta_volume_mnt:
126    description:
127      - The path of the meta volume mount point.
128    type: str
129"""
130
131EXAMPLES = """
132- name: Create the geo-rep session
133  gluster.gluster.geo_rep:
134    action: create
135    mastervol: 10.70.42.122:mastervolume
136    slavevol: 10.70.43.48:slavevolume
137    force: true
138    georepuser: staff
139- name: Starts the geo-rep session
140  gluster.gluster.geo_rep:
141    action: start
142    mastervol: 10.70.42.122:mastervolume
143    slavevol: 10.70.43.48:slavevolume
144    force: true
145    georepuser: staff
146- name: Pause the geo-rep session
147  gluster.gluster.geo_rep:
148    action: pause
149    mastervol: 10.70.42.122:mastervolume
150    slavevol: 10.70.43.48:slavevolume
151    force: true
152    georepuser: staff
153- name: Resume the geo-rep session
154  gluster.gluster.geo_rep:
155    action: resume
156    mastervol: 10.70.42.122:mastervolume
157    slavevol: 10.70.43.48:slavevolume
158    force: true
159    georepuser: staff
160- name: Stop the geo-rep session
161  gluster.gluster.geo_rep:
162    action: stop
163    mastervol: 10.70.42.122:mastervolume
164    slavevol: 10.70.43.48:slavevolume
165    force: true
166    georepuser: staff
167- name: Configures the geo-rep session
168  gluster.gluster.geo_rep:
169    action: config
170    mastervol: 10.70.42.122:mastervolume
171    slavevol: 10.70.43.48:slavevolume
172    gluster_log_file: /var/log/glusterfs/geo-replication/gluster.log
173    gluster_log_level: INFO
174    log_file: /var/log/glusterfs/geo-replication/file.log
175    log_level: INFO
176    changelog_log_level: INFO
177    ssh_command: SSH
178    rsync_command: rsync
179    use_tarssh: true
180    volume_id: 6a071cfa-b150-4f0b-b1ed-96ab5d4bd671
181    timeout: 60
182    sync_jobs: 3
183    ignore_deletes: 1
184    checkpoint: now
185    sync_acls: true
186    sync_xattr: true
187    log_rsync_performance: true
188    rsync_options: --compress-level=0
189    use_meta_volume: true
190    meta_volume_mnt: /var/run/gluster/shared_storage/
191- name: Delete the geo-rep session
192  gluster.gluster.geo_rep:
193    action: delete
194    mastervol: 10.70.42.122:mastervolume
195    slavevol: 10.70.43.48:slavevolume
196    georepuser: staff
197"""
198
199import re
200from ansible.module_utils.basic import AnsibleModule
201
202
203class GeoRep(object):
204    def __init__(self, module):
205        self.module = module
206        self.action = self._validated_params('action')
207        self.gluster_georep_ops()
208
209    def get_playbook_params(self, opt):
210        return self.module.params[opt]
211
212    def _validated_params(self, opt):
213        value = self.get_playbook_params(opt)
214        if value is None:
215            msg = "Please provide %s option in the playbook!" % opt
216            self.module.fail_json(msg=msg)
217        return value
218
219    def gluster_georep_ops(self):
220        mastervol = self._validated_params('mastervol')
221        slavevol = self._validated_params('slavevol')
222        slavevol = self.check_pool_exclusiveness(mastervol, slavevol)
223        if self.action in ['delete', 'config']:
224            force = ''
225        else:
226            force = self._validated_params('force')
227            force = 'force' if force == 'yes' else ' '
228        options = 'no-verify' if self.action == 'create' \
229            else self.config_georep()
230        if isinstance(options, list):
231            for opt in options:
232                rc, output, err = self.call_gluster_cmd('volume',
233                                                        'geo-replication',
234                                                        mastervol, slavevol,
235                                                        self.action, opt,
236                                                        force)
237        else:
238            rc, output, err = self.call_gluster_cmd('volume',
239                                                    'geo-replication',
240                                                    mastervol, slavevol,
241                                                    self.action, options,
242                                                    force)
243        self._get_output(rc, output, err)
244        if self.action in ['stop', 'delete'] and self.user == 'root':
245            self.user = 'geoaccount'
246            rc, output, err = self.call_gluster_cmd('volume', 'geo-replication',
247                                                    mastervol, slavevol.replace(
248                                                        'root', 'geoaccount'),
249                                                    self.action, options, force)
250            self._get_output(rc, output, err)
251
252    def config_georep(self):
253        if self.action != 'config':
254            return ''
255        options = ['gluster_log_file', 'gluster_log_level', 'log_file',
256                   'log_level', 'changelog_log_level', 'ssh_command',
257                   'rsync_command', 'use_tarssh', 'volume_id', 'timeout',
258                   'sync_jobs', 'ignore_deletes', 'checkpoint', 'sync_acls',
259                   'sync_xattrs', 'log_rsync_performance', 'rsync_options',
260                   'use_meta_volume', 'meta_volume_mnt']
261        configs = []
262        for opt in options:
263            value = self._validated_params(opt)
264            if value:
265                if value == 'reset':
266                    configs.append("'!" + opt.replace('_', '-') + "'")
267                configs.append(opt.replace('_', '-') + ' ' + value)
268        if configs:
269            return configs
270        value = self._validated_params('config')
271        op = self._validated_params('op')
272        return value + ' ' + op
273
274    def check_pool_exclusiveness(self, mastervol, slavevol):
275        rc, output, err = self.module.run_command(
276            "gluster pool list")
277        peers_in_cluster = [line.split('\t')[1].strip() for
278                            line in filter(None, output.split('\n')[1:])]
279        val_group = re.search("(.*):(.*)", slavevol)
280        if not val_group:
281            self.module.fail_json(msg="Slave volume in Unknown format. "
282                                  "Correct format: <hostname>:<volume name>")
283        if val_group.group(1) in peers_in_cluster:
284            self.module.fail_json(msg="slave volume is in the trusted "
285                                  "storage pool of master")
286        self.user = 'root' if self.module.params['georepuser'] is None \
287            else self.module.params['georepuser']
288        return self.user + '@' + val_group.group(1) + '::' + val_group.group(2)
289
290    def call_gluster_cmd(self, *args, **kwargs):
291        params = ' '.join(opt for opt in args)
292        key_value_pair = ' '.join(' %s %s ' % (key, value)
293                                  for key, value in kwargs)
294        return self._run_command('gluster', ' ' + params + ' ' + key_value_pair)
295
296    def _get_output(self, rc, output, err):
297        carryon = True if self.action in ['stop',
298                                          'delete', 'resume'] else False
299        changed = 0 if (carryon and rc) else 1
300        if self.action in ['stop', 'delete'] and (
301                self.user == 'root' and changed == 0):
302            return
303        if not rc or carryon:
304            self.module.exit_json(stdout=output, changed=changed)
305        else:
306            self.module.fail_json(msg=err)
307
308    def _run_command(self, op, opts):
309        cmd = self.module.get_bin_path(op, True) + opts
310        return self.module.run_command(cmd)
311
312
313def main():
314    module = AnsibleModule(
315        argument_spec=dict(
316            action=dict(required=True, choices=['create', 'start',
317                                                'stop', 'delete', 'pause', 'resume', 'config']),
318            mastervol=dict(),
319            slavevol=dict(),
320            force=dict(),
321            georepuser=dict(),
322            gluster_log_file=dict(),
323            gluster_log_level=dict(),
324            log_file=dict(),
325            log_level=dict(),
326            changelog_log_level=dict(),
327            ssh_command=dict(),
328            rsync_command=dict(),
329            use_tarssh=dict(),
330            volume_id=dict(),
331            timeout=dict(),
332            sync_jobs=dict(),
333            ignore_deletes=dict(),
334            checkpoint=dict(),
335            sync_acls=dict(),
336            sync_xattrs=dict(),
337            log_rsync_performance=dict(),
338            rsync_options=dict(),
339            use_meta_volume=dict(),
340            meta_volume_mnt=dict()
341        ),
342    )
343    GeoRep(module)
344
345
346if __name__ == "__main__":
347    main()
348