Statistics
| Branch: | Tag: | Revision:

root / lib / storage / base.py @ be9150ea

History | View | Annotate | Download (12.4 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, spindles, 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
    @type unique_id: 2-element tuple or list
123
    @param unique_id: unique identifier; the details depend on the actual device
124
        type
125
    @type children: list of L{BlockDev}
126
    @param children: for hierarchical devices, the child devices
127
    @type size: float
128
    @param size: size in MiB
129
    @type spindles: int
130
    @param spindles: number of physical disk to dedicate to the device
131
    @type params: dict
132
    @param params: device-specific options/parameters
133
    @type excl_stor: bool
134
    @param excl_stor: whether exclusive_storage is active
135
    @rtype: L{BlockDev}
136
    @return: the created device, or C{None} in case of an error
137

138
    """
139
    raise NotImplementedError
140

    
141
  def Remove(self):
142
    """Remove this device.
143

144
    This makes sense only for some of the device types: LV and file
145
    storage. Also note that if the device can't attach, the removal
146
    can't be completed.
147

148
    """
149
    raise NotImplementedError
150

    
151
  def Rename(self, new_id):
152
    """Rename this device.
153

154
    This may or may not make sense for a given device type.
155

156
    """
157
    raise NotImplementedError
158

    
159
  def Open(self, force=False):
160
    """Make the device ready for use.
161

162
    This makes the device ready for I/O. For now, just the DRBD
163
    devices need this.
164

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

168
    @type force: boolean
169

170
    """
171
    raise NotImplementedError
172

    
173
  def Shutdown(self):
174
    """Shut down the device, freeing its children.
175

176
    This undoes the `Assemble()` work, except for the child
177
    assembling; as such, the children on the device are still
178
    assembled after this call.
179

180
    """
181
    raise NotImplementedError
182

    
183
  def SetSyncParams(self, params):
184
    """Adjust the synchronization parameters of the mirror.
185

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

188
    @param params: dictionary of LD level disk parameters related to the
189
    synchronization.
190
    @rtype: list
191
    @return: a list of error messages, emitted both by the current node and by
192
    children. An empty list means no errors.
193

194
    """
195
    result = []
196
    if self._children:
197
      for child in self._children:
198
        result.extend(child.SetSyncParams(params))
199
    return result
200

    
201
  def PauseResumeSync(self, pause):
202
    """Pause/Resume the sync of the mirror.
203

204
    In case this is not a mirroring device, this is no-op.
205

206
    @type pause: boolean
207
    @param pause: Whether to pause or resume
208

209
    """
210
    result = True
211
    if self._children:
212
      for child in self._children:
213
        result = result and child.PauseResumeSync(pause)
214
    return result
215

    
216
  def GetSyncStatus(self):
217
    """Returns the sync status of the device.
218

219
    If this device is a mirroring device, this function returns the
220
    status of the mirror.
221

222
    If sync_percent is None, it means the device is not syncing.
223

224
    If estimated_time is None, it means we can't estimate
225
    the time needed, otherwise it's the time left in seconds.
226

227
    If is_degraded is True, it means the device is missing
228
    redundancy. This is usually a sign that something went wrong in
229
    the device setup, if sync_percent is None.
230

231
    The ldisk parameter represents the degradation of the local
232
    data. This is only valid for some devices, the rest will always
233
    return False (not degraded).
234

235
    @rtype: objects.BlockDevStatus
236

237
    """
238
    return objects.BlockDevStatus(dev_path=self.dev_path,
239
                                  major=self.major,
240
                                  minor=self.minor,
241
                                  sync_percent=None,
242
                                  estimated_time=None,
243
                                  is_degraded=False,
244
                                  ldisk_status=constants.LDS_OKAY)
245

    
246
  def CombinedSyncStatus(self):
247
    """Calculate the mirror status recursively for our children.
248

249
    The return value is the same as for `GetSyncStatus()` except the
250
    minimum percent and maximum time are calculated across our
251
    children.
252

253
    @rtype: objects.BlockDevStatus
254

255
    """
256
    status = self.GetSyncStatus()
257

    
258
    min_percent = status.sync_percent
259
    max_time = status.estimated_time
260
    is_degraded = status.is_degraded
261
    ldisk_status = status.ldisk_status
262

    
263
    if self._children:
264
      for child in self._children:
265
        child_status = child.GetSyncStatus()
266

    
267
        if min_percent is None:
268
          min_percent = child_status.sync_percent
269
        elif child_status.sync_percent is not None:
270
          min_percent = min(min_percent, child_status.sync_percent)
271

    
272
        if max_time is None:
273
          max_time = child_status.estimated_time
274
        elif child_status.estimated_time is not None:
275
          max_time = max(max_time, child_status.estimated_time)
276

    
277
        is_degraded = is_degraded or child_status.is_degraded
278

    
279
        if ldisk_status is None:
280
          ldisk_status = child_status.ldisk_status
281
        elif child_status.ldisk_status is not None:
282
          ldisk_status = max(ldisk_status, child_status.ldisk_status)
283

    
284
    return objects.BlockDevStatus(dev_path=self.dev_path,
285
                                  major=self.major,
286
                                  minor=self.minor,
287
                                  sync_percent=min_percent,
288
                                  estimated_time=max_time,
289
                                  is_degraded=is_degraded,
290
                                  ldisk_status=ldisk_status)
291

    
292
  def SetInfo(self, text):
293
    """Update metadata with info text.
294

295
    Only supported for some device types.
296

297
    """
298
    for child in self._children:
299
      child.SetInfo(text)
300

    
301
  def Grow(self, amount, dryrun, backingstore, excl_stor):
302
    """Grow the block device.
303

304
    @type amount: integer
305
    @param amount: the amount (in mebibytes) to grow with
306
    @type dryrun: boolean
307
    @param dryrun: whether to execute the operation in simulation mode
308
        only, without actually increasing the size
309
    @param backingstore: whether to execute the operation on backing storage
310
        only, or on "logical" storage only; e.g. DRBD is logical storage,
311
        whereas LVM, file, RBD are backing storage
312
    @type excl_stor: boolean
313
    @param excl_stor: Whether exclusive_storage is active
314

315
    """
316
    raise NotImplementedError
317

    
318
  def GetActualSize(self):
319
    """Return the actual disk size.
320

321
    @note: the device needs to be active when this is called
322

323
    """
324
    assert self.attached, "BlockDevice not attached in GetActualSize()"
325
    result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
326
    if result.failed:
327
      ThrowError("blockdev failed (%s): %s",
328
                  result.fail_reason, result.output)
329
    try:
330
      sz = int(result.output.strip())
331
    except (ValueError, TypeError), err:
332
      ThrowError("Failed to parse blockdev output: %s", str(err))
333
    return sz
334

    
335
  def GetActualSpindles(self):
336
    """Return the actual number of spindles used.
337

338
    This is not supported by all devices; if not supported, C{None} is returned.
339

340
    @note: the device needs to be active when this is called
341

342
    """
343
    assert self.attached, "BlockDevice not attached in GetActualSpindles()"
344
    return None
345

    
346
  def GetActualDimensions(self):
347
    """Return the actual disk size and number of spindles used.
348

349
    @rtype: tuple
350
    @return: (size, spindles); spindles is C{None} when they are not supported
351

352
    @note: the device needs to be active when this is called
353

354
    """
355
    return (self.GetActualSize(), self.GetActualSpindles())
356

    
357
  def __repr__(self):
358
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
359
            (self.__class__, self.unique_id, self._children,
360
             self.major, self.minor, self.dev_path))
361

    
362

    
363
def ThrowError(msg, *args):
364
  """Log an error to the node daemon and the raise an exception.
365

366
  @type msg: string
367
  @param msg: the text of the exception
368
  @raise errors.BlockDeviceError
369

370
  """
371
  if args:
372
    msg = msg % args
373
  logging.error(msg)
374
  raise errors.BlockDeviceError(msg)
375

    
376

    
377
def IgnoreError(fn, *args, **kwargs):
378
  """Executes the given function, ignoring BlockDeviceErrors.
379

380
  This is used in order to simplify the execution of cleanup or
381
  rollback functions.
382

383
  @rtype: boolean
384
  @return: True when fn didn't raise an exception, False otherwise
385

386
  """
387
  try:
388
    fn(*args, **kwargs)
389
    return True
390
  except errors.BlockDeviceError, err:
391
    logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
392
    return False