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