Statistics
| Branch: | Tag: | Revision:

root / lib / storage / base.py @ f3aebf6f

History | View | Annotate | Download (13.1 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
  # pylint: disable=W0613
76
  def __init__(self, unique_id, children, size, params, dyn_params, *args):
77
    self._children = children
78
    self.dev_path = None
79
    self.unique_id = unique_id
80
    self.major = None
81
    self.minor = None
82
    self.attached = False
83
    self.size = size
84
    self.params = params
85
    self.dyn_params = dyn_params
86

    
87
  def Assemble(self):
88
    """Assemble the device from its components.
89

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

98
    """
99
    pass
100

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

104
    """
105
    raise NotImplementedError
106

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

110
    """
111
    raise NotImplementedError
112

    
113
  @classmethod
114
  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
115
             dyn_params, *args):
116
    """Create the device.
117

118
    If the device cannot be created, it will return None
119
    instead. Error messages go to the logging system.
120

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

125
    @type unique_id: 2-element tuple or list
126
    @param unique_id: unique identifier; the details depend on the actual device
127
        type
128
    @type children: list of L{BlockDev}
129
    @param children: for hierarchical devices, the child devices
130
    @type size: float
131
    @param size: size in MiB
132
    @type spindles: int
133
    @param spindles: number of physical disk to dedicate to the device
134
    @type params: dict
135
    @param params: device-specific options/parameters
136
    @type excl_stor: bool
137
    @param excl_stor: whether exclusive_storage is active
138
    @type dyn_params: dict
139
    @param dyn_params: dynamic parameters of the disk only valid for this node.
140
        As set by L{objects.Disk.UpdateDynamicDiskParams}.
141
    @rtype: L{BlockDev}
142
    @return: the created device, or C{None} in case of an error
143

144
    """
145
    raise NotImplementedError
146

    
147
  def Remove(self):
148
    """Remove this device.
149

150
    This makes sense only for some of the device types: LV and file
151
    storage. Also note that if the device can't attach, the removal
152
    can't be completed.
153

154
    """
155
    raise NotImplementedError
156

    
157
  def Rename(self, new_id):
158
    """Rename this device.
159

160
    This may or may not make sense for a given device type.
161

162
    """
163
    raise NotImplementedError
164

    
165
  def Open(self, force=False):
166
    """Make the device ready for use.
167

168
    This makes the device ready for I/O. For now, just the DRBD
169
    devices need this.
170

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

174
    @type force: boolean
175

176
    """
177
    raise NotImplementedError
178

    
179
  def Shutdown(self):
180
    """Shut down the device, freeing its children.
181

182
    This undoes the `Assemble()` work, except for the child
183
    assembling; as such, the children on the device are still
184
    assembled after this call.
185

186
    """
187
    raise NotImplementedError
188

    
189
  def SetSyncParams(self, params):
190
    """Adjust the synchronization parameters of the mirror.
191

192
    In case this is not a mirroring device, this is no-op.
193

194
    @param params: dictionary of LD level disk parameters related to the
195
    synchronization.
196
    @rtype: list
197
    @return: a list of error messages, emitted both by the current node and by
198
    children. An empty list means no errors.
199

200
    """
201
    result = []
202
    if self._children:
203
      for child in self._children:
204
        result.extend(child.SetSyncParams(params))
205
    return result
206

    
207
  def PauseResumeSync(self, pause):
208
    """Pause/Resume the sync of the mirror.
209

210
    In case this is not a mirroring device, this is no-op.
211

212
    @type pause: boolean
213
    @param pause: Whether to pause or resume
214

215
    """
216
    result = True
217
    if self._children:
218
      for child in self._children:
219
        result = result and child.PauseResumeSync(pause)
220
    return result
221

    
222
  def GetSyncStatus(self):
223
    """Returns the sync status of the device.
224

225
    If this device is a mirroring device, this function returns the
226
    status of the mirror.
227

228
    If sync_percent is None, it means the device is not syncing.
229

230
    If estimated_time is None, it means we can't estimate
231
    the time needed, otherwise it's the time left in seconds.
232

233
    If is_degraded is True, it means the device is missing
234
    redundancy. This is usually a sign that something went wrong in
235
    the device setup, if sync_percent is None.
236

237
    The ldisk parameter represents the degradation of the local
238
    data. This is only valid for some devices, the rest will always
239
    return False (not degraded).
240

241
    @rtype: objects.BlockDevStatus
242

243
    """
244
    return objects.BlockDevStatus(dev_path=self.dev_path,
245
                                  major=self.major,
246
                                  minor=self.minor,
247
                                  sync_percent=None,
248
                                  estimated_time=None,
249
                                  is_degraded=False,
250
                                  ldisk_status=constants.LDS_OKAY)
251

    
252
  def CombinedSyncStatus(self):
253
    """Calculate the mirror status recursively for our children.
254

255
    The return value is the same as for `GetSyncStatus()` except the
256
    minimum percent and maximum time are calculated across our
257
    children.
258

259
    @rtype: objects.BlockDevStatus
260

261
    """
262
    status = self.GetSyncStatus()
263

    
264
    min_percent = status.sync_percent
265
    max_time = status.estimated_time
266
    is_degraded = status.is_degraded
267
    ldisk_status = status.ldisk_status
268

    
269
    if self._children:
270
      for child in self._children:
271
        child_status = child.GetSyncStatus()
272

    
273
        if min_percent is None:
274
          min_percent = child_status.sync_percent
275
        elif child_status.sync_percent is not None:
276
          min_percent = min(min_percent, child_status.sync_percent)
277

    
278
        if max_time is None:
279
          max_time = child_status.estimated_time
280
        elif child_status.estimated_time is not None:
281
          max_time = max(max_time, child_status.estimated_time)
282

    
283
        is_degraded = is_degraded or child_status.is_degraded
284

    
285
        if ldisk_status is None:
286
          ldisk_status = child_status.ldisk_status
287
        elif child_status.ldisk_status is not None:
288
          ldisk_status = max(ldisk_status, child_status.ldisk_status)
289

    
290
    return objects.BlockDevStatus(dev_path=self.dev_path,
291
                                  major=self.major,
292
                                  minor=self.minor,
293
                                  sync_percent=min_percent,
294
                                  estimated_time=max_time,
295
                                  is_degraded=is_degraded,
296
                                  ldisk_status=ldisk_status)
297

    
298
  def SetInfo(self, text):
299
    """Update metadata with info text.
300

301
    Only supported for some device types.
302

303
    """
304
    for child in self._children:
305
      child.SetInfo(text)
306

    
307
  def Grow(self, amount, dryrun, backingstore, excl_stor):
308
    """Grow the block device.
309

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

321
    """
322
    raise NotImplementedError
323

    
324
  def GetActualSize(self):
325
    """Return the actual disk size.
326

327
    @note: the device needs to be active when this is called
328

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

    
341
  def GetActualSpindles(self):
342
    """Return the actual number of spindles used.
343

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

346
    @note: the device needs to be active when this is called
347

348
    """
349
    assert self.attached, "BlockDevice not attached in GetActualSpindles()"
350
    return None
351

    
352
  def GetActualDimensions(self):
353
    """Return the actual disk size and number of spindles used.
354

355
    @rtype: tuple
356
    @return: (size, spindles); spindles is C{None} when they are not supported
357

358
    @note: the device needs to be active when this is called
359

360
    """
361
    return (self.GetActualSize(), self.GetActualSpindles())
362

    
363
  def GetUserspaceAccessUri(self, hypervisor):
364
    """Return URIs hypervisors can use to access disks in userspace mode.
365

366
    @rtype: string
367
    @return: userspace device URI
368
    @raise errors.BlockDeviceError: if userspace access is not supported
369

370
    """
371
    ThrowError("Userspace access with %s block device and %s hypervisor is not "
372
               "supported." % (self.__class__.__name__,
373
                               hypervisor))
374

    
375
  def __repr__(self):
376
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
377
            (self.__class__, self.unique_id, self._children,
378
             self.major, self.minor, self.dev_path))
379

    
380

    
381
def ThrowError(msg, *args):
382
  """Log an error to the node daemon and the raise an exception.
383

384
  @type msg: string
385
  @param msg: the text of the exception
386
  @raise errors.BlockDeviceError
387

388
  """
389
  if args:
390
    msg = msg % args
391
  logging.error(msg)
392
  raise errors.BlockDeviceError(msg)
393

    
394

    
395
def IgnoreError(fn, *args, **kwargs):
396
  """Executes the given function, ignoring BlockDeviceErrors.
397

398
  This is used in order to simplify the execution of cleanup or
399
  rollback functions.
400

401
  @rtype: boolean
402
  @return: True when fn didn't raise an exception, False otherwise
403

404
  """
405
  try:
406
    fn(*args, **kwargs)
407
    return True
408
  except errors.BlockDeviceError, err:
409
    logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
410
    return False