Rename dir 'block' to 'storage'
[ganeti-local] / lib / storage / drbd_cmdgen.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
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 2 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, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # 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
19 # 02110-1301, USA.
20
21
22 """DRBD command generating classes"""
23
24 import logging
25 import shlex
26
27 from ganeti import constants
28 from ganeti import errors
29
30
31 class BaseDRBDCmdGenerator(object):
32   """Base class for DRBD command generators.
33
34   This class defines the interface for the command generators and holds shared
35   code.
36
37   """
38   def __init__(self, version):
39     self._version = version
40
41   def GenShowCmd(self, minor):
42     raise NotImplementedError
43
44   def GenInitMetaCmd(self, minor, meta_dev):
45     raise NotImplementedError
46
47   def GenLocalInitCmds(self, minor, data_dev, meta_dev, size_mb, params):
48     raise NotImplementedError
49
50   def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol,
51                     dual_pri, hmac, secret, params):
52     raise NotImplementedError
53
54   def GenSyncParamsCmd(self, minor, params):
55     raise NotImplementedError
56
57   def GenPauseSyncCmd(self, minor):
58     raise NotImplementedError
59
60   def GenResumeSyncCmd(self, minor):
61     raise NotImplementedError
62
63   def GenPrimaryCmd(self, minor, force):
64     raise NotImplementedError
65
66   def GenSecondaryCmd(self, minor):
67     raise NotImplementedError
68
69   def GenDetachCmd(self, minor):
70     raise NotImplementedError
71
72   def GenDisconnectCmd(self, minor, family, lhost, lport, rhost, rport):
73     raise NotImplementedError
74
75   def GenDownCmd(self, minor):
76     raise NotImplementedError
77
78   def GenResizeCmd(self, minor, size_mb):
79     raise NotImplementedError
80
81   @staticmethod
82   def _DevPath(minor):
83     """Return the path to a drbd device for a given minor.
84
85     """
86     return "/dev/drbd%d" % minor
87
88
89 class DRBD83CmdGenerator(BaseDRBDCmdGenerator):
90   """Generates drbdsetup commands suited for the DRBD <= 8.3 syntax.
91
92   """
93   # command line options for barriers
94   _DISABLE_DISK_OPTION = "--no-disk-barrier"  # -a
95   _DISABLE_DRAIN_OPTION = "--no-disk-drain"   # -D
96   _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
97   _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"  # -m
98
99   def __init__(self, version):
100     super(DRBD83CmdGenerator, self).__init__(version)
101
102   def GenShowCmd(self, minor):
103     return ["drbdsetup", self._DevPath(minor), "show"]
104
105   def GenInitMetaCmd(self, minor, meta_dev):
106     return ["drbdmeta", "--force", self._DevPath(minor),
107             "v08", meta_dev, "0", "create-md"]
108
109   def GenLocalInitCmds(self, minor, data_dev, meta_dev, size_mb, params):
110     args = ["drbdsetup", self._DevPath(minor), "disk",
111             data_dev, meta_dev, "0",
112             "-e", "detach",
113             "--create-device"]
114     if size_mb:
115       args.extend(["-d", "%sm" % size_mb])
116
117     vmaj = self._version["k_major"]
118     vmin = self._version["k_minor"]
119     vrel = self._version["k_point"]
120
121     barrier_args = \
122       self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
123                                    params[constants.LDP_BARRIERS],
124                                    params[constants.LDP_NO_META_FLUSH])
125     args.extend(barrier_args)
126
127     if params[constants.LDP_DISK_CUSTOM]:
128       args.extend(shlex.split(params[constants.LDP_DISK_CUSTOM]))
129
130     return [args]
131
132   def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol,
133                     dual_pri, hmac, secret, params):
134     args = ["drbdsetup", self._DevPath(minor), "net",
135             "%s:%s:%s" % (family, lhost, lport),
136             "%s:%s:%s" % (family, rhost, rport), protocol,
137             "-A", "discard-zero-changes",
138             "-B", "consensus",
139             "--create-device",
140             ]
141     if dual_pri:
142       args.append("-m")
143     if hmac and secret:
144       args.extend(["-a", hmac, "-x", secret])
145
146     if params[constants.LDP_NET_CUSTOM]:
147       args.extend(shlex.split(params[constants.LDP_NET_CUSTOM]))
148
149     return args
150
151   def GenSyncParamsCmd(self, minor, params):
152     args = ["drbdsetup", self._DevPath(minor), "syncer"]
153     if params[constants.LDP_DYNAMIC_RESYNC]:
154       vmin = self._version["k_minor"]
155       vrel = self._version["k_point"]
156
157       # By definition we are using 8.x, so just check the rest of the version
158       # number
159       if vmin != 3 or vrel < 9:
160         msg = ("The current DRBD version (8.%d.%d) does not support the "
161                "dynamic resync speed controller" % (vmin, vrel))
162         logging.error(msg)
163         return [msg]
164
165       if params[constants.LDP_PLAN_AHEAD] == 0:
166         msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
167                " controller at DRBD level. If you want to disable it, please"
168                " set the dynamic-resync disk parameter to False.")
169         logging.error(msg)
170         return [msg]
171
172       # add the c-* parameters to args
173       args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
174                    "--c-fill-target", params[constants.LDP_FILL_TARGET],
175                    "--c-delay-target", params[constants.LDP_DELAY_TARGET],
176                    "--c-max-rate", params[constants.LDP_MAX_RATE],
177                    "--c-min-rate", params[constants.LDP_MIN_RATE],
178                    ])
179
180     else:
181       args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
182
183     args.append("--create-device")
184
185     return args
186
187   def GenPauseSyncCmd(self, minor):
188     return ["drbdsetup", self._DevPath(minor), "pause-sync"]
189
190   def GenResumeSyncCmd(self, minor):
191     return ["drbdsetup", self._DevPath(minor), "resume-sync"]
192
193   def GenPrimaryCmd(self, minor, force):
194     cmd = ["drbdsetup", self._DevPath(minor), "primary"]
195
196     if force:
197       cmd.append("-o")
198
199     return cmd
200
201   def GenSecondaryCmd(self, minor):
202     return ["drbdsetup", self._DevPath(minor), "secondary"]
203
204   def GenDetachCmd(self, minor):
205     return ["drbdsetup", self._DevPath(minor), "detach"]
206
207   def GenDisconnectCmd(self, minor, family, lhost, lport, rhost, rport):
208     return ["drbdsetup", self._DevPath(minor), "disconnect"]
209
210   def GenDownCmd(self, minor):
211     return ["drbdsetup", self._DevPath(minor), "down"]
212
213   def GenResizeCmd(self, minor, size_mb):
214     return ["drbdsetup", self._DevPath(minor), "resize", "-s", "%dm" % size_mb]
215
216   @classmethod
217   def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
218                               disable_meta_flush):
219     """Compute the DRBD command line parameters for disk barriers
220
221     Returns a list of the disk barrier parameters as requested via the
222     disabled_barriers and disable_meta_flush arguments, and according to the
223     supported ones in the DRBD version vmaj.vmin.vrel
224
225     If the desired option is unsupported, raises errors.BlockDeviceError.
226
227     """
228     disabled_barriers_set = frozenset(disabled_barriers)
229     if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
230       raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
231                                     " barriers" % disabled_barriers)
232
233     args = []
234
235     # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
236     # does not exist)
237     if not vmaj == 8 and vmin in (0, 2, 3):
238       raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
239                                     (vmaj, vmin, vrel))
240
241     def _AppendOrRaise(option, min_version):
242       """Helper for DRBD options"""
243       if min_version is not None and vrel >= min_version:
244         args.append(option)
245       else:
246         raise errors.BlockDeviceError("Could not use the option %s as the"
247                                       " DRBD version %d.%d.%d does not support"
248                                       " it." % (option, vmaj, vmin, vrel))
249
250     # the minimum version for each feature is encoded via pairs of (minor
251     # version -> x) where x is version in which support for the option was
252     # introduced.
253     meta_flush_supported = disk_flush_supported = {
254       0: 12,
255       2: 7,
256       3: 0,
257       }
258
259     disk_drain_supported = {
260       2: 7,
261       3: 0,
262       }
263
264     disk_barriers_supported = {
265       3: 0,
266       }
267
268     # meta flushes
269     if disable_meta_flush:
270       _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
271                      meta_flush_supported.get(vmin, None))
272
273     # disk flushes
274     if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
275       _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
276                      disk_flush_supported.get(vmin, None))
277
278     # disk drain
279     if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
280       _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
281                      disk_drain_supported.get(vmin, None))
282
283     # disk barriers
284     if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
285       _AppendOrRaise(cls._DISABLE_DISK_OPTION,
286                      disk_barriers_supported.get(vmin, None))
287
288     return args
289
290
291 class DRBD84CmdGenerator(BaseDRBDCmdGenerator):
292   """Generates drbdsetup commands suited for the DRBD >= 8.4 syntax.
293
294   """
295   # command line options for barriers
296   _DISABLE_DISK_OPTION = "--disk-barrier=no"
297   _DISABLE_DRAIN_OPTION = "--disk-drain=no"
298   _DISABLE_FLUSH_OPTION = "--disk-flushes=no"
299   _DISABLE_META_FLUSH_OPTION = "--md-flushes=no"
300
301   def __init__(self, version):
302     super(DRBD84CmdGenerator, self).__init__(version)
303
304   def GenShowCmd(self, minor):
305     return ["drbdsetup", "show", minor]
306
307   def GenInitMetaCmd(self, minor, meta_dev):
308     return ["drbdmeta", "--force", self._DevPath(minor),
309             "v08", meta_dev, "flex-external", "create-md"]
310
311   def GenLocalInitCmds(self, minor, data_dev, meta_dev, size_mb, params):
312     cmds = []
313
314     cmds.append(["drbdsetup", "new-resource", self._GetResource(minor)])
315     cmds.append(["drbdsetup", "new-minor", self._GetResource(minor),
316                  str(minor), "0"])
317     # We need to apply the activity log before attaching the disk else drbdsetup
318     # will fail.
319     cmds.append(["drbdmeta", self._DevPath(minor),
320                  "v08", meta_dev, "flex-external", "apply-al"])
321
322     attach_cmd = ["drbdsetup", "attach", minor, data_dev, meta_dev, "flexible",
323                   "--on-io-error=detach"]
324     if size_mb:
325       attach_cmd.extend(["--size", "%sm" % size_mb])
326
327     barrier_args = \
328       self._ComputeDiskBarrierArgs(params[constants.LDP_BARRIERS],
329                                    params[constants.LDP_NO_META_FLUSH])
330     attach_cmd.extend(barrier_args)
331
332     if params[constants.LDP_DISK_CUSTOM]:
333       attach_cmd.extend(shlex.split(params[constants.LDP_DISK_CUSTOM]))
334
335     cmds.append(attach_cmd)
336
337     return cmds
338
339   def GenNetInitCmd(self, minor, family, lhost, lport, rhost, rport, protocol,
340                     dual_pri, hmac, secret, params):
341     args = ["drbdsetup", "connect", self._GetResource(minor),
342             "%s:%s:%s" % (family, lhost, lport),
343             "%s:%s:%s" % (family, rhost, rport),
344             "--protocol", protocol,
345             "--after-sb-0pri", "discard-zero-changes",
346             "--after-sb-1pri", "consensus"
347             ]
348     if dual_pri:
349       args.append("--allow-two-primaries")
350     if hmac and secret:
351       args.extend(["--cram-hmac-alg", hmac, "--shared-secret", secret])
352
353     if params[constants.LDP_NET_CUSTOM]:
354       args.extend(shlex.split(params[constants.LDP_NET_CUSTOM]))
355
356     return args
357
358   def GenSyncParamsCmd(self, minor, params):
359     args = ["drbdsetup", "disk-options", minor]
360     if params[constants.LDP_DYNAMIC_RESYNC]:
361       if params[constants.LDP_PLAN_AHEAD] == 0:
362         msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
363                " controller at DRBD level. If you want to disable it, please"
364                " set the dynamic-resync disk parameter to False.")
365         logging.error(msg)
366         return [msg]
367
368       # add the c-* parameters to args
369       args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
370                    "--c-fill-target", params[constants.LDP_FILL_TARGET],
371                    "--c-delay-target", params[constants.LDP_DELAY_TARGET],
372                    "--c-max-rate", params[constants.LDP_MAX_RATE],
373                    "--c-min-rate", params[constants.LDP_MIN_RATE],
374                    ])
375
376     else:
377       args.extend(["--resync-rate", "%d" % params[constants.LDP_RESYNC_RATE]])
378
379     return args
380
381   def GenPauseSyncCmd(self, minor):
382     return ["drbdsetup", "pause-sync", minor]
383
384   def GenResumeSyncCmd(self, minor):
385     return ["drbdsetup", "resume-sync", minor]
386
387   def GenPrimaryCmd(self, minor, force):
388     cmd = ["drbdsetup", "primary", minor]
389
390     if force:
391       cmd.append("--force")
392
393     return cmd
394
395   def GenSecondaryCmd(self, minor):
396     return ["drbdsetup", "secondary", minor]
397
398   def GenDetachCmd(self, minor):
399     return ["drbdsetup", "detach", minor]
400
401   def GenDisconnectCmd(self, minor, family, lhost, lport, rhost, rport):
402     return ["drbdsetup", "disconnect",
403             "%s:%s:%s" % (family, lhost, lport),
404             "%s:%s:%s" % (family, rhost, rport)]
405
406   def GenDownCmd(self, minor):
407     return ["drbdsetup", "down", self._GetResource(minor)]
408
409   def GenResizeCmd(self, minor, size_mb):
410     return ["drbdsetup", "resize", minor, "--size", "%dm" % size_mb]
411
412   @staticmethod
413   def _GetResource(minor):
414     """Return the resource name for a given minor.
415
416     Currently we don't support DRBD volumes which share a resource, so we
417     generate the resource name based on the minor the resulting volumes is
418     assigned to.
419
420     """
421     return "resource%d" % minor
422
423   @classmethod
424   def _ComputeDiskBarrierArgs(cls, disabled_barriers, disable_meta_flush):
425     """Compute the DRBD command line parameters for disk barriers
426
427     """
428     disabled_barriers_set = frozenset(disabled_barriers)
429     if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
430       raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
431                                     " barriers" % disabled_barriers)
432
433     args = []
434
435     # meta flushes
436     if disable_meta_flush:
437       args.append(cls._DISABLE_META_FLUSH_OPTION)
438
439     # disk flushes
440     if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
441       args.append(cls._DISABLE_FLUSH_OPTION)
442
443     # disk drain
444     if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
445       args.append(cls._DISABLE_DRAIN_OPTION)
446
447     # disk barriers
448     if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
449       args.append(cls._DISABLE_DISK_OPTION)
450
451     return args