ctdb_mutex_ceph_rados_helper: Set SIGINT signal handler
[amitay/samba.git] / ctdb / utils / ceph / ctdb_mutex_ceph_rados_helper.c
1 /*
2    CTDB mutex helper using Ceph librados locks
3
4    Copyright (C) David Disseldorp 2016
5
6    Based on ctdb_mutex_fcntl_helper.c, which is:
7    Copyright (C) Martin Schwenke 2015
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "replace.h"
24
25 #include "tevent.h"
26 #include "talloc.h"
27 #include "rados/librados.h"
28
29 #define CTDB_MUTEX_CEPH_LOCK_NAME       "ctdb_reclock_mutex"
30 #define CTDB_MUTEX_CEPH_LOCK_COOKIE     CTDB_MUTEX_CEPH_LOCK_NAME
31 #define CTDB_MUTEX_CEPH_LOCK_DESC       "CTDB recovery lock"
32
33 #define CTDB_MUTEX_STATUS_HOLDING "0"
34 #define CTDB_MUTEX_STATUS_CONTENDED "1"
35 #define CTDB_MUTEX_STATUS_TIMEOUT "2"
36 #define CTDB_MUTEX_STATUS_ERROR "3"
37
38 static char *progname = NULL;
39
40 static int ctdb_mutex_rados_ctx_create(const char *ceph_cluster_name,
41                                        const char *ceph_auth_name,
42                                        const char *pool_name,
43                                        rados_t *_ceph_cluster,
44                                        rados_ioctx_t *_ioctx)
45 {
46         rados_t ceph_cluster = NULL;
47         rados_ioctx_t ioctx = NULL;
48         int ret;
49
50         ret = rados_create2(&ceph_cluster, ceph_cluster_name, ceph_auth_name, 0);
51         if (ret < 0) {
52                 fprintf(stderr, "%s: failed to initialise Ceph cluster %s as %s"
53                         " - (%s)\n", progname, ceph_cluster_name, ceph_auth_name,
54                         strerror(-ret));
55                 return ret;
56         }
57
58         /* path=NULL tells librados to use default locations */
59         ret = rados_conf_read_file(ceph_cluster, NULL);
60         if (ret < 0) {
61                 fprintf(stderr, "%s: failed to parse Ceph cluster config"
62                         " - (%s)\n", progname, strerror(-ret));
63                 rados_shutdown(ceph_cluster);
64                 return ret;
65         }
66
67         ret = rados_connect(ceph_cluster);
68         if (ret < 0) {
69                 fprintf(stderr, "%s: failed to connect to Ceph cluster %s as %s"
70                         " - (%s)\n", progname, ceph_cluster_name, ceph_auth_name,
71                         strerror(-ret));
72                 rados_shutdown(ceph_cluster);
73                 return ret;
74         }
75
76
77         ret = rados_ioctx_create(ceph_cluster, pool_name, &ioctx);
78         if (ret < 0) {
79                 fprintf(stderr, "%s: failed to create Ceph ioctx for pool %s"
80                         " - (%s)\n", progname, pool_name, strerror(-ret));
81                 rados_shutdown(ceph_cluster);
82                 return ret;
83         }
84
85         *_ceph_cluster = ceph_cluster;
86         *_ioctx = ioctx;
87
88         return 0;
89 }
90
91 static void ctdb_mutex_rados_ctx_destroy(rados_t ceph_cluster,
92                                          rados_ioctx_t ioctx)
93 {
94         rados_ioctx_destroy(ioctx);
95         rados_shutdown(ceph_cluster);
96 }
97
98 static int ctdb_mutex_rados_lock(rados_ioctx_t *ioctx,
99                                  const char *oid)
100 {
101         int ret;
102
103         ret = rados_lock_exclusive(ioctx, oid,
104                                    CTDB_MUTEX_CEPH_LOCK_NAME,
105                                    CTDB_MUTEX_CEPH_LOCK_COOKIE,
106                                    CTDB_MUTEX_CEPH_LOCK_DESC,
107                                    NULL, /* infinite duration */
108                                    0);
109         if ((ret == -EEXIST) || (ret == -EBUSY)) {
110                 /* lock contention */
111                 return ret;
112         } else if (ret < 0) {
113                 /* unexpected failure */
114                 fprintf(stderr,
115                         "%s: Failed to get lock on RADOS object '%s' - (%s)\n",
116                         progname, oid, strerror(-ret));
117                 return ret;
118         }
119
120         /* lock obtained */
121         return 0;
122 }
123
124 static int ctdb_mutex_rados_unlock(rados_ioctx_t *ioctx,
125                                    const char *oid)
126 {
127         int ret;
128
129         ret = rados_unlock(ioctx, oid,
130                            CTDB_MUTEX_CEPH_LOCK_NAME,
131                            CTDB_MUTEX_CEPH_LOCK_COOKIE);
132         if (ret < 0) {
133                 fprintf(stderr,
134                         "%s: Failed to drop lock on RADOS object '%s' - (%s)\n",
135                         progname, oid, strerror(-ret));
136                 return ret;
137         }
138
139         return 0;
140 }
141
142 struct ctdb_mutex_rados_state {
143         bool holding_mutex;
144         const char *ceph_cluster_name;
145         const char *ceph_auth_name;
146         const char *pool_name;
147         const char *object;
148         int ppid;
149         struct tevent_context *ev;
150         struct tevent_signal *sigterm_ev;
151         struct tevent_signal *sigint_ev;
152         struct tevent_timer *timer_ev;
153         rados_t ceph_cluster;
154         rados_ioctx_t ioctx;
155 };
156
157 static void ctdb_mutex_rados_sigterm_cb(struct tevent_context *ev,
158                                         struct tevent_signal *se,
159                                         int signum,
160                                         int count,
161                                         void *siginfo,
162                                         void *private_data)
163 {
164         struct ctdb_mutex_rados_state *cmr_state = private_data;
165         int ret;
166
167         if (!cmr_state->holding_mutex) {
168                 fprintf(stderr, "Sigterm callback invoked without mutex!\n");
169                 ret = -EINVAL;
170                 goto err_ctx_cleanup;
171         }
172
173         ret = ctdb_mutex_rados_unlock(cmr_state->ioctx, cmr_state->object);
174 err_ctx_cleanup:
175         ctdb_mutex_rados_ctx_destroy(cmr_state->ceph_cluster,
176                                      cmr_state->ioctx);
177         talloc_free(cmr_state);
178         exit(ret ? 1 : 0);
179 }
180
181 static void ctdb_mutex_rados_timer_cb(struct tevent_context *ev,
182                                       struct tevent_timer *te,
183                                       struct timeval current_time,
184                                       void *private_data)
185 {
186         struct ctdb_mutex_rados_state *cmr_state = private_data;
187         int ret;
188
189         if (!cmr_state->holding_mutex) {
190                 fprintf(stderr, "Timer callback invoked without mutex!\n");
191                 ret = -EINVAL;
192                 goto err_ctx_cleanup;
193         }
194
195         if ((kill(cmr_state->ppid, 0) == 0) || (errno != ESRCH)) {
196                 /* parent still around, keep waiting */
197                 cmr_state->timer_ev = tevent_add_timer(cmr_state->ev, cmr_state,
198                                                tevent_timeval_current_ofs(5, 0),
199                                                       ctdb_mutex_rados_timer_cb,
200                                                        cmr_state);
201                 if (cmr_state->timer_ev == NULL) {
202                         fprintf(stderr, "Failed to create timer event\n");
203                         /* rely on signal cb */
204                 }
205                 return;
206         }
207
208         /* parent ended, drop lock and exit */
209         ret = ctdb_mutex_rados_unlock(cmr_state->ioctx, cmr_state->object);
210 err_ctx_cleanup:
211         ctdb_mutex_rados_ctx_destroy(cmr_state->ceph_cluster,
212                                      cmr_state->ioctx);
213         talloc_free(cmr_state);
214         exit(ret ? 1 : 0);
215 }
216
217 int main(int argc, char *argv[])
218 {
219         int ret;
220         struct ctdb_mutex_rados_state *cmr_state;
221
222         progname = argv[0];
223
224         if (argc != 5) {
225                 fprintf(stderr, "Usage: %s <Ceph Cluster> <Ceph user> "
226                                 "<RADOS pool> <RADOS object>\n",
227                         progname);
228                 ret = -EINVAL;
229                 goto err_out;
230         }
231
232         ret = setvbuf(stdout, NULL, _IONBF, 0);
233         if (ret != 0) {
234                 fprintf(stderr, "Failed to configure unbuffered stdout I/O\n");
235         }
236
237         cmr_state = talloc_zero(NULL, struct ctdb_mutex_rados_state);
238         if (cmr_state == NULL) {
239                 fprintf(stdout, CTDB_MUTEX_STATUS_ERROR);
240                 ret = -ENOMEM;
241                 goto err_out;
242         }
243
244         cmr_state->ceph_cluster_name = argv[1];
245         cmr_state->ceph_auth_name = argv[2];
246         cmr_state->pool_name = argv[3];
247         cmr_state->object = argv[4];
248
249         cmr_state->ppid = getppid();
250         if (cmr_state->ppid == 1) {
251                 /*
252                  * The original parent is gone and the process has
253                  * been reparented to init.  This can happen if the
254                  * helper is started just as the parent is killed
255                  * during shutdown.  The error message doesn't need to
256                  * be stellar, since there won't be anything around to
257                  * capture and log it...
258                  */
259                 fprintf(stderr, "%s: PPID == 1\n", progname);
260                 ret = -EPIPE;
261                 goto err_state_free;
262         }
263
264         cmr_state->ev = tevent_context_init(cmr_state);
265         if (cmr_state->ev == NULL) {
266                 fprintf(stderr, "tevent_context_init failed\n");
267                 fprintf(stdout, CTDB_MUTEX_STATUS_ERROR);
268                 ret = -ENOMEM;
269                 goto err_state_free;
270         }
271
272         /* wait for sigterm */
273         cmr_state->sigterm_ev = tevent_add_signal(cmr_state->ev, cmr_state, SIGTERM, 0,
274                                               ctdb_mutex_rados_sigterm_cb,
275                                               cmr_state);
276         if (cmr_state->sigterm_ev == NULL) {
277                 fprintf(stderr, "Failed to create term signal event\n");
278                 fprintf(stdout, CTDB_MUTEX_STATUS_ERROR);
279                 ret = -ENOMEM;
280                 goto err_state_free;
281         }
282
283         cmr_state->sigint_ev = tevent_add_signal(cmr_state->ev, cmr_state, SIGINT, 0,
284                                               ctdb_mutex_rados_sigterm_cb,
285                                               cmr_state);
286         if (cmr_state->sigint_ev == NULL) {
287                 fprintf(stderr, "Failed to create int signal event\n");
288                 fprintf(stdout, CTDB_MUTEX_STATUS_ERROR);
289                 ret = -ENOMEM;
290                 goto err_state_free;
291         }
292
293         /* periodically check parent */
294         cmr_state->timer_ev = tevent_add_timer(cmr_state->ev, cmr_state,
295                                                tevent_timeval_current_ofs(5, 0),
296                                                ctdb_mutex_rados_timer_cb,
297                                                cmr_state);
298         if (cmr_state->timer_ev == NULL) {
299                 fprintf(stderr, "Failed to create timer event\n");
300                 fprintf(stdout, CTDB_MUTEX_STATUS_ERROR);
301                 ret = -ENOMEM;
302                 goto err_state_free;
303         }
304
305         ret = ctdb_mutex_rados_ctx_create(cmr_state->ceph_cluster_name,
306                                           cmr_state->ceph_auth_name,
307                                           cmr_state->pool_name,
308                                           &cmr_state->ceph_cluster,
309                                           &cmr_state->ioctx);
310         if (ret < 0) {
311                 fprintf(stdout, CTDB_MUTEX_STATUS_ERROR);
312                 goto err_state_free;
313         }
314
315         ret = ctdb_mutex_rados_lock(cmr_state->ioctx, cmr_state->object);
316         if ((ret == -EEXIST) || (ret == -EBUSY)) {
317                 fprintf(stdout, CTDB_MUTEX_STATUS_CONTENDED);
318                 goto err_ctx_cleanup;
319         } else if (ret < 0) {
320                 fprintf(stdout, CTDB_MUTEX_STATUS_ERROR);
321                 goto err_ctx_cleanup;
322         }
323
324         cmr_state->holding_mutex = true;
325         fprintf(stdout, CTDB_MUTEX_STATUS_HOLDING);
326
327         /* wait for the signal / timer events to do their work */
328         ret = tevent_loop_wait(cmr_state->ev);
329         if (ret < 0) {
330                 goto err_ctx_cleanup;
331         }
332 err_ctx_cleanup:
333         ctdb_mutex_rados_ctx_destroy(cmr_state->ceph_cluster,
334                                      cmr_state->ioctx);
335 err_state_free:
336         talloc_free(cmr_state);
337 err_out:
338         return ret ? 1 : 0;
339 }