summaryrefslogtreecommitdiffstats
path: root/anate
diff options
context:
space:
mode:
authorMartin Gracik <mgracik@redhat.com>2009-03-24 11:05:40 +0100
committerMartin Gracik <mgracik@redhat.com>2009-03-24 14:41:55 +0100
commitac83df7815ff3db042875db51a43c107e982ee70 (patch)
treea8b67d04cf0b00ea92b6fa1d4c162123dc9fa0e3 /anate
downloadanate-master.tar.gz
anate-master.tar.xz
anate-master.zip
Initial commitHEADmaster
Diffstat (limited to 'anate')
-rw-r--r--anate/__init__.py0
-rw-r--r--anate/baseutils.py44
-rw-r--r--anate/devices.py234
-rw-r--r--anate/vm.py135
4 files changed, 413 insertions, 0 deletions
diff --git a/anate/__init__.py b/anate/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/anate/__init__.py
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)