summaryrefslogtreecommitdiffstats
path: root/anate/devices.py
diff options
context:
space:
mode:
Diffstat (limited to 'anate/devices.py')
-rw-r--r--anate/devices.py234
1 files changed, 234 insertions, 0 deletions
diff --git a/anate/devices.py b/anate/devices.py
new file mode 100644
index 0000000..cdc845e
--- /dev/null
+++ b/anate/devices.py
@@ -0,0 +1,234 @@
+import logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger('anate.devices')
+
+import os
+import stat
+import pwd
+import grp
+import tempfile
+from baseutils import Subprocess
+
+
+def getDeviceNumbers(path):
+ try:
+ statinfo = os.stat(path)
+ except OSError:
+ return (None, None)
+
+ return (os.major(statinfo.st_rdev), os.minor(statinfo.st_rdev))
+
+
+class BlockDeviceNode(object):
+ def __init__(self, path, major, minor):
+ self._path = path
+ self._major = major
+ self._minor = minor
+
+ def create(self, mode=0660, owner='root', group='disk'):
+ try:
+ os.mknod(self.path, mode | stat.S_IFBLK, os.makedev(self.major, self.minor))
+ except OSError:
+ logger.error('unable to create node %s', self.path)
+ return
+
+ try:
+ uid = pwd.getpwnam(owner)[2]
+ except KeyError:
+ logger.warning('user %s does not exist, not changing the owner', owner)
+ uid = -1
+
+ try:
+ gid = grp.getgrnam(group)[2]
+ except KeyError:
+ logger.warning('group %s does not exist, not changing the group', group)
+ gid = -1
+
+ try:
+ os.chown(self.path, uid, gid)
+ except OSError:
+ logger.warning('unable to change the owner and group of node %s', self.path)
+
+ def unlink(self):
+ try:
+ os.unlink(self.path)
+ except OSError:
+ logger.error('unable to unlink node %s', self.path)
+
+ @property
+ def path(self):
+ return self._path
+
+ @property
+ def major(self):
+ return self._major
+
+ @major.setter
+ def major(self, value):
+ self._major = value
+
+ @property
+ def minor(self):
+ return self._minor
+
+ @minor.setter
+ def minor(self, value):
+ self._minor = value
+
+ @property
+ def available(self):
+ return os.access(self.path, os.R_OK | os.W_OK)
+
+
+class BlockDevice(object):
+ def __init__(self, name, node):
+ if not isinstance(node, BlockDeviceNode):
+ raise TypeError, 'node must be an instance of BlockDeviceNode, got %s' % type(node)
+
+ self._name = name
+ self._node = node
+ self._active = False
+
+ def initialize(self):
+ self._active = True
+
+ def destroy(self):
+ self._active = False
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def path(self):
+ return self._node.path
+
+ @property
+ def major(self):
+ return self._node.major
+
+ @property
+ def minor(self):
+ return self._node.minor
+
+ @property
+ def active(self):
+ return self._active
+
+
+class StorageDevice(BlockDevice):
+ def __init__(self, name, node, size=0):
+ BlockDevice.__init__(self, name, node)
+ self._size = size
+
+ @property
+ def size(self):
+ return self._size
+
+
+class LoopDevice(StorageDevice):
+ def __init__(self, name, node, destination_file, size):
+ StorageDevice.__init__(self, name, node, size)
+ self._file = destination_file
+
+ def initialize(self):
+ proc = Subprocess('dd', 'if=/dev/zero', 'of=%s' % self._file, 'bs=1k', 'seek=%d' % (self.size * 1024 - 1), 'count=1')
+ proc.run()
+ if proc.rc:
+ logger.error('%s', proc.stderr)
+ raise OSError, 'dd failed when creating file %s' % self._file
+
+ proc = Subprocess('losetup', self.path, self._file)
+ proc.run()
+ if proc.rc:
+ logger.error('%s', proc.stderr)
+ raise OSError, 'losetup failed when setting up loop device %s' % self.path
+
+ self._active = True
+
+ def destroy(self):
+ proc = Subprocess('losetup', '-d', self.path)
+ proc.run()
+ if proc.rc:
+ logger.error('%s', proc.stderr)
+ raise OSError, 'losetup failed when removing loop device %s' % self.path
+
+ os.unlink(self._file)
+
+ self._active = False
+
+
+class DMDevice(StorageDevice):
+
+ _DM_DEVICES_ROOT_PATH = '/dev/mapper/'
+ _SECTORS_PER_MB = 2048
+
+ def __init__(self, name, destination_device, size=None):
+ if not isinstance(destination_device, StorageDevice):
+ raise TypeError, 'destination_device must be an instance of StorageDevice, got %s' % type(destination_device)
+ if not destination_device.active:
+ destination_device.initialize()
+
+ usable_size = destination_device.size
+ if not size:
+ size = usable_size
+ elif size > usable_size:
+ logger.warning('creating dm device which is bigger than the destination device')
+ else:
+ usable_size = size
+
+ node = BlockDeviceNode(os.path.join(self._DM_DEVICES_ROOT_PATH, name), major=None, minor=None)
+
+ StorageDevice.__init__(self, name, node, size)
+ self._destination_device = destination_device
+ self._usable_size = usable_size
+
+ def initialize(self):
+ table_file = tempfile.NamedTemporaryFile()
+
+ map = {'logical_start_sector': 0,
+ 'num_sectors': self.usable_size * self._SECTORS_PER_MB,
+ 'target_type': 'linear',
+ 'destination_device': self._destination_device.path,
+ 'destination_device_start_sector': 0}
+ string = '%(logical_start_sector)d %(num_sectors)d %(target_type)s %(destination_device)s %(destination_device_start_sector)d\n' % map
+ table_file.write(string)
+ table_file.flush()
+ logger.info('table_file: %s', string.strip())
+
+ if self.size > self.usable_size:
+ map = {'logical_start_sector': self.usable_size * self._SECTORS_PER_MB,
+ 'num_sectors': (self.size - self.usable_size) * self._SECTORS_PER_MB,
+ 'target_type': 'zero'}
+ string = '%(logical_start_sector)d %(num_sectors)d %(target_type)s\n' % map
+ table_file.write(string)
+ table_file.flush()
+ logger.info('table_file: %s', string.strip())
+
+ proc = Subprocess('dmsetup', 'create', self.name, table_file.name)
+ proc.run()
+
+ table_file.close()
+
+ if proc.rc:
+ logger.error('%s', proc.stderr)
+ raise OSError, 'dmsetup failed when setting up dm device %s' % self.name
+
+ major, minor = getDeviceNumbers(self._node.path)
+ self._node.major = major
+ self._node.minor = minor
+
+ self._active = True
+
+ def destroy(self):
+ proc = Subprocess('dmsetup', 'remove', '--force', self.name)
+ proc.run()
+ if proc.rc:
+ logger.error('%s', proc.stderr)
+ raise OSError, 'dmsetup failed when removing dm device %s' % self.name
+
+ self._active = False
+
+ @property
+ def usable_size(self):
+ return self._usable_size