Statistics
| Branch: | Tag: | Revision:

root / lib / block / base.py @ 89ff748d

History | View | Annotate | Download (11 kB)

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
"""Block device abstraction - base class and utility functions"""
23

    
24
import logging
25

    
26
from ganeti import objects
27
from ganeti import constants
28
from ganeti import utils
29
from ganeti import errors
30

    
31

    
32
class BlockDev(object):
33
  """Block device abstract class.
34

35
  A block device can be in the following states:
36
    - not existing on the system, and by `Create()` it goes into:
37
    - existing but not setup/not active, and by `Assemble()` goes into:
38
    - active read-write and by `Open()` it goes into
39
    - online (=used, or ready for use)
40

41
  A device can also be online but read-only, however we are not using
42
  the readonly state (LV has it, if needed in the future) and we are
43
  usually looking at this like at a stack, so it's easier to
44
  conceptualise the transition from not-existing to online and back
45
  like a linear one.
46

47
  The many different states of the device are due to the fact that we
48
  need to cover many device types:
49
    - logical volumes are created, lvchange -a y $lv, and used
50
    - drbd devices are attached to a local disk/remote peer and made primary
51

52
  A block device is identified by three items:
53
    - the /dev path of the device (dynamic)
54
    - a unique ID of the device (static)
55
    - it's major/minor pair (dynamic)
56

57
  Not all devices implement both the first two as distinct items. LVM
58
  logical volumes have their unique ID (the pair volume group, logical
59
  volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
60
  the /dev path is again dynamic and the unique id is the pair (host1,
61
  dev1), (host2, dev2).
62

63
  You can get to a device in two ways:
64
    - creating the (real) device, which returns you
65
      an attached instance (lvcreate)
66
    - attaching of a python instance to an existing (real) device
67

68
  The second point, the attachment to a device, is different
69
  depending on whether the device is assembled or not. At init() time,
70
  we search for a device with the same unique_id as us. If found,
71
  good. It also means that the device is already assembled. If not,
72
  after assembly we'll have our correct major/minor.
73

74
  """
75
  def __init__(self, unique_id, children, size, params):
76
    self._children = children
77
    self.dev_path = None
78
    self.unique_id = unique_id
79
    self.major = None
80
    self.minor = None
81
    self.attached = False
82
    self.size = size
83
    self.params = params
84

    
85
  def Assemble(self):
86
    """Assemble the device from its components.
87

88
    Implementations of this method by child classes must ensure that:
89
      - after the device has been assembled, it knows its major/minor
90
        numbers; this allows other devices (usually parents) to probe
91
        correctly for their children
92
      - calling this method on an existing, in-use device is safe
93
      - if the device is already configured (and in an OK state),
94
        this method is idempotent
95

96
    """
97
    pass
98

    
99
  def Attach(self):
100
    """Find a device which matches our config and attach to it.
101

102
    """
103
    raise NotImplementedError
104

    
105
  def Close(self):
106
    """Notifies that the device will no longer be used for I/O.
107

108
    """
109
    raise NotImplementedError
110

    
111
  @classmethod
112
  def Create(cls, unique_id, children, size, params, excl_stor):
113
    """Create the device.
114

115
    If the device cannot be created, it will return None
116
    instead. Error messages go to the logging system.
117

118
    Note that for some devices, the unique_id is used, and for other,
119
    the children. The idea is that these two, taken together, are
120
    enough for both creation and assembly (later).
121

122
    """
123
    raise NotImplementedError
124

    
125
  def Remove(self):
126
    """Remove this device.
127

128
    This makes sense only for some of the device types: LV and file
129
    storage. Also note that if the device can't attach, the removal
130
    can't be completed.
131

132
    """
133
    raise NotImplementedError
134

    
135
  def Rename(self, new_id):
136
    """Rename this device.
137

138
    This may or may not make sense for a given device type.
139

140
    """
141
    raise NotImplementedError
142

    
143
  def Open(self, force=False):
144
    """Make the device ready for use.
145

146
    This makes the device ready for I/O. For now, just the DRBD
147
    devices need this.
148

149
    The force parameter signifies that if the device has any kind of
150
    --force thing, it should be used, we know what we are doing.
151

152
    """
153
    raise NotImplementedError
154

    
155
  def Shutdown(self):
156
    """Shut down the device, freeing its children.
157

158
    This undoes the `Assemble()` work, except for the child
159
    assembling; as such, the children on the device are still
160
    assembled after this call.
161

162
    """
163
    raise NotImplementedError
164

    
165
  def SetSyncParams(self, params):
166
    """Adjust the synchronization parameters of the mirror.
167

168
    In case this is not a mirroring device, this is no-op.
169

170
    @param params: dictionary of LD level disk parameters related to the
171
    synchronization.
172
    @rtype: list
173
    @return: a list of error messages, emitted both by the current node and by
174
    children. An empty list means no errors.
175

176
    """
177
    result = []
178
    if self._children:
179
      for child in self._children:
180
        result.extend(child.SetSyncParams(params))
181
    return result
182

    
183
  def PauseResumeSync(self, pause):
184
    """Pause/Resume the sync of the mirror.
185

186
    In case this is not a mirroring device, this is no-op.
187

188
    @param pause: Whether to pause or resume
189

190
    """
191
    result = True
192
    if self._children:
193
      for child in self._children:
194
        result = result and child.PauseResumeSync(pause)
195
    return result
196

    
197
  def GetSyncStatus(self):
198
    """Returns the sync status of the device.
199

200
    If this device is a mirroring device, this function returns the
201
    status of the mirror.
202

203
    If sync_percent is None, it means the device is not syncing.
204

205
    If estimated_time is None, it means we can't estimate
206
    the time needed, otherwise it's the time left in seconds.
207

208
    If is_degraded is True, it means the device is missing
209
    redundancy. This is usually a sign that something went wrong in
210
    the device setup, if sync_percent is None.
211

212
    The ldisk parameter represents the degradation of the local
213
    data. This is only valid for some devices, the rest will always
214
    return False (not degraded).
215

216
    @rtype: objects.BlockDevStatus
217

218
    """
219
    return objects.BlockDevStatus(dev_path=self.dev_path,
220
                                  major=self.major,
221
                                  minor=self.minor,
222
                                  sync_percent=None,
223
                                  estimated_time=None,
224
                                  is_degraded=False,
225
                                  ldisk_status=constants.LDS_OKAY)
226

    
227
  def CombinedSyncStatus(self):
228
    """Calculate the mirror status recursively for our children.
229

230
    The return value is the same as for `GetSyncStatus()` except the
231
    minimum percent and maximum time are calculated across our
232
    children.
233

234
    @rtype: objects.BlockDevStatus
235

236
    """
237
    status = self.GetSyncStatus()
238

    
239
    min_percent = status.sync_percent
240
    max_time = status.estimated_time
241
    is_degraded = status.is_degraded
242
    ldisk_status = status.ldisk_status
243

    
244
    if self._children:
245
      for child in self._children:
246
        child_status = child.GetSyncStatus()
247

    
248
        if min_percent is None:
249
          min_percent = child_status.sync_percent
250
        elif child_status.sync_percent is not None:
251
          min_percent = min(min_percent, child_status.sync_percent)
252

    
253
        if max_time is None:
254
          max_time = child_status.estimated_time
255
        elif child_status.estimated_time is not None:
256
          max_time = max(max_time, child_status.estimated_time)
257

    
258
        is_degraded = is_degraded or child_status.is_degraded
259

    
260
        if ldisk_status is None:
261
          ldisk_status = child_status.ldisk_status
262
        elif child_status.ldisk_status is not None:
263
          ldisk_status = max(ldisk_status, child_status.ldisk_status)
264

    
265
    return objects.BlockDevStatus(dev_path=self.dev_path,
266
                                  major=self.major,
267
                                  minor=self.minor,
268
                                  sync_percent=min_percent,
269
                                  estimated_time=max_time,
270
                                  is_degraded=is_degraded,
271
                                  ldisk_status=ldisk_status)
272

    
273
  def SetInfo(self, text):
274
    """Update metadata with info text.
275

276
    Only supported for some device types.
277

278
    """
279
    for child in self._children:
280
      child.SetInfo(text)
281

    
282
  def Grow(self, amount, dryrun, backingstore):
283
    """Grow the block device.
284

285
    @type amount: integer
286
    @param amount: the amount (in mebibytes) to grow with
287
    @type dryrun: boolean
288
    @param dryrun: whether to execute the operation in simulation mode
289
        only, without actually increasing the size
290
    @param backingstore: whether to execute the operation on backing storage
291
        only, or on "logical" storage only; e.g. DRBD is logical storage,
292
        whereas LVM, file, RBD are backing storage
293

294
    """
295
    raise NotImplementedError
296

    
297
  def GetActualSize(self):
298
    """Return the actual disk size.
299

300
    @note: the device needs to be active when this is called
301

302
    """
303
    assert self.attached, "BlockDevice not attached in GetActualSize()"
304
    result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
305
    if result.failed:
306
      ThrowError("blockdev failed (%s): %s",
307
                  result.fail_reason, result.output)
308
    try:
309
      sz = int(result.output.strip())
310
    except (ValueError, TypeError), err:
311
      ThrowError("Failed to parse blockdev output: %s", str(err))
312
    return sz
313

    
314
  def __repr__(self):
315
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
316
            (self.__class__, self.unique_id, self._children,
317
             self.major, self.minor, self.dev_path))
318

    
319

    
320
def ThrowError(msg, *args):
321
  """Log an error to the node daemon and the raise an exception.
322

323
  @type msg: string
324
  @param msg: the text of the exception
325
  @raise errors.BlockDeviceError
326

327
  """
328
  if args:
329
    msg = msg % args
330
  logging.error(msg)
331
  raise errors.BlockDeviceError(msg)
332

    
333

    
334
def IgnoreError(fn, *args, **kwargs):
335
  """Executes the given function, ignoring BlockDeviceErrors.
336

337
  This is used in order to simplify the execution of cleanup or
338
  rollback functions.
339

340
  @rtype: boolean
341
  @return: True when fn didn't raise an exception, False otherwise
342

343
  """
344
  try:
345
    fn(*args, **kwargs)
346
    return True
347
  except errors.BlockDeviceError, err:
348
    logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
349
    return False