From ac83df7815ff3db042875db51a43c107e982ee70 Mon Sep 17 00:00:00 2001 From: Martin Gracik Date: Tue, 24 Mar 2009 11:05:40 +0100 Subject: Initial commit --- anate/__init__.py | 0 anate/baseutils.py | 44 ++++++++++ anate/devices.py | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++ anate/vm.py | 135 +++++++++++++++++++++++++++++++ 4 files changed, 413 insertions(+) create mode 100644 anate/__init__.py create mode 100644 anate/baseutils.py create mode 100644 anate/devices.py create mode 100644 anate/vm.py (limited to 'anate') diff --git a/anate/__init__.py b/anate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/anate/baseutils.py b/anate/baseutils.py new file mode 100644 index 0000000..f084e64 --- /dev/null +++ b/anate/baseutils.py @@ -0,0 +1,44 @@ +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('anate.baseutils') + +import os +import subprocess + + +class Subprocess(object): + def __init__(self, cmd, *args): + self._cmd = [cmd] + self._args = map(str, list(args)) + + self._rc = None + self._stdout = [] + self._stderr = [] + + def run(self): + logger.info('running command %s', self._cmd + self._args) + proc = subprocess.Popen(self._cmd + self._args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + while True: + (out_str, err_str) = proc.communicate() + if out_str: + self._stdout.append(out_str) + if err_str: + self._stderr.append(err_str) + + if proc.returncode is not None: + self._rc = proc.returncode + break + + @property + def rc(self): + return self._rc + + @property + def stdout(self): + return self._stdout + + @property + def stderr(self): + return self._stderr 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 diff --git a/anate/vm.py b/anate/vm.py new file mode 100644 index 0000000..71fcabc --- /dev/null +++ b/anate/vm.py @@ -0,0 +1,135 @@ +import logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('anate.vm') + +import os +import devices + + +class VirtualDisk(devices.DMDevice): + + _LOOP_DEV_MAJOR = 7 + _AVAILABLE_MINORS = [0, 1, 2, 3, 4, 5, 6, 7] + + def getAvailableMinor(self): + return self._AVAILABLE_MINORS[0] + + def useMinor(self, minor): + self._AVAILABLE_MINORS.remove(minor) + + def freeMinor(self, minor): + self._AVAILABLE_MINORS.append(minor) + self._AVAILABLE_MINORS.sort() + + def __init__(self, name, size, usable_size=None): + if not usable_size: + usable_size = size + elif usable_size > size: + logger.error('usable_size cannot be greater than size') + return + + self._remove_node = False + self._loop_node = self._createLoopNode(name) + if not self._loop_node: + raise OSError, 'node error' + + self._loop_dev = self._createLoopDev(name, self._loop_node, usable_size) + + devices.DMDevice.__init__(self, name, self._loop_dev, size) + + def _createLoopNode(self, name): + loop_node = devices.BlockDeviceNode(os.path.join('/dev/', name), major=None, minor=None) + if loop_node.available: + logger.info('using existing node %s', loop_node.path) + loop_node.major, loop_node.minor = devices.getDeviceNumbers(loop_node.path) + if loop_node.major != self._LOOP_DEV_MAJOR: + logger.error('not a loop device node') + return + if loop_node.minor not in self._AVAILABLE_MINORS: + logger.error('node not available') + return + else: + logger.info('creating new node %s', loop_node.path) + loop_node.major = self._LOOP_DEV_MAJOR + loop_node.minor = self.getAvailableMinor() + loop_node.create() + self._remove_node = True + + self.useMinor(loop_node.minor) + logger.info('using minor %d', loop_node.minor) + logger.info('available minors: %s', self._AVAILABLE_MINORS) + + if not loop_node.available: + raise OSError, 'node error' + + return loop_node + + def _createLoopDev(self, name, loop_node, size): + loop_dev = devices.LoopDevice(name, loop_node, os.path.join('/tmp/', name), size) + loop_dev.initialize() + if not loop_dev.active: + raise OSError, 'loop device error' + + return loop_dev + + def create(self): + self.initialize() + if not self.active: + raise OSError, 'dm device error' + + def remove(self): + self.destroy() + if self.active: + raise OSError, 'dm device error' + + self._loop_dev.destroy() + if self._loop_dev.active: + raise OSError, 'loop device error' + + if self._remove_node: + logger.info('removing node %s', self._loop_node.path) + self._loop_node.unlink() + if self._loop_node.available: + raise OSError, 'node error' + + self.freeMinor(self._loop_node.minor) + logger.info('freeing minor %d', self._loop_node.minor) + logger.info('available minors: %s', self._AVAILABLE_MINORS) + + +class VM(object): + def __init__(self): + self._disks = {} + + def addDisk(self, name, size, usable_size=None): + if name in self.disks: + logger.error('disk %s already exists', name) + return + + disk = VirtualDisk(name, size=size, usable_size=usable_size) + disk.create() + self._disks[name] = disk + + def removeDisk(self, name): + if name not in self.disks: + logger.error('disk %s does not exist', name) + return + + disk = self.getDisk(name) + if disk.active: + disk.remove() + del self._disks[name] + + def halt(self): + for disk in self.disks.values(): + if disk.active: + disk.remove() + + self._disks = {} + + @property + def disks(self): + return self._disks + + def getDisk(self, name): + return self.disks.get(name, None) -- cgit