[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-changelog] [xen-unstable] [XEND] Beginnings of the Xend Storage Repository implementation.



# HG changeset patch
# User Alastair Tse <atse@xxxxxxxxxxxxx>
# Node ID 1b923f4a788a58b8c7e81d0866058e7e4d18c47c
# Parent  255925ae4127287e2cd49bbc06f33965d31f7fb5
[XEND] Beginnings of the Xend Storage Repository implementation.

Currently it is a basic repository that just creates images on demand
and wraps them as VDIs. Rudimentary support for querying the available
space using 'df'. Creates images via 'qcow-create' after creating an
empty image.

Signed-off-by: Alastair Tse <atse@xxxxxxxxxxxxx>
---
 tools/python/xen/xend/XendNode.py              |    2 
 tools/python/xen/xend/XendStorageRepository.py |  327 +++++++++++++++++++++++++
 tools/python/xen/xend/XendVDI.py               |   44 +++
 3 files changed, 373 insertions(+)

diff -r 255925ae4127 -r 1b923f4a788a tools/python/xen/xend/XendNode.py
--- a/tools/python/xen/xend/XendNode.py Thu Oct 12 18:00:01 2006 +0100
+++ b/tools/python/xen/xend/XendNode.py Thu Oct 12 18:01:32 2006 +0100
@@ -21,6 +21,7 @@ import xen.lowlevel.xc
 import xen.lowlevel.xc
 from xen.xend import uuid
 from xen.xend.XendError import XendError
+from xen.xend.XendStorageRepository import XendStorageRepository
 
 class XendNode:
     """XendNode - Represents a Domain 0 Host."""
@@ -31,6 +32,7 @@ class XendNode:
         self.cpus = {}
         self.name = socket.gethostname()
         self.desc = ""
+        self.sr = XendStorageRepository()
         
         physinfo = self.physinfo_dict()
         cpu_count = physinfo['nr_cpus']
diff -r 255925ae4127 -r 1b923f4a788a 
tools/python/xen/xend/XendStorageRepository.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/python/xen/xend/XendStorageRepository.py    Thu Oct 12 18:01:32 
2006 +0100
@@ -0,0 +1,327 @@
+#!/usr/bin/python
+#============================================================================
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#============================================================================
+# Copyright (C) 2006 XenSource Ltd.
+#============================================================================
+#
+# The default QCOW Xen API Storage Repository
+#
+
+import os
+import commands
+import threading
+
+from xen.xend import uuid
+from xen.xend.XendError import XendError
+from xen.xend.XendVDI import *
+
+XEND_STORAGE_MAX_IGNORE = -1
+XEND_STORAGE_DIR = "/var/lib/xend/storage/"
+XEND_STORAGE_QCOW_FILENAME = "%s.qcow"
+XEND_STORAGE_IMG_FILENAME = "%s.img"
+DF_COMMAND = "df -kl"
+QCOW_CREATE_COMMAND = "/usr/sbin/qcow-create %d %s %s"
+
+KB = 1024
+MB = 1024 *1024
+
+class DeviceInvalidError(Exception):
+    pass
+
+class XendStorageRepository:
+    """A simple file backed QCOW Storage Repository.
+
+    This class exposes the interface to create VDI's via the
+    Xen API. The backend is a file-backed QCOW format that is stored
+    in XEND_STORAGE_DIR or any that is specified in the constructor.
+
+    The actual images are created in the format <uuid>.img and <uuid>.qcow.
+    """
+    
+    def __init__(self, storage_dir = XEND_STORAGE_DIR,
+                 storage_max = XEND_STORAGE_MAX_IGNORE):
+        """
+        @keyword storage_dir: Where the images will be stored.
+        @type    storage_dir: string
+        @keyword storage_max: Maximum disk space to use in KB.
+        @type    storage_max: int
+
+        @ivar    storage_free: storage space free for this repository
+        @ivar    images: mapping of all the images.
+        @type    images: dictionary by image uuid.
+        @ivar    lock:   lock to provide thread safety.
+        """
+        
+        self.storage_dir = storage_dir
+        self.storage_max = storage_max
+        self.storage_free = 0
+        self.images = {}
+
+        # XenAPI Parameters
+        self.uuid = self._sr_uuid()
+        self.type = "qcow-file"
+        self.location = self.storage_dir
+        self.name_label = "Local"
+        self.name_description = "Xend Storage Repository"
+
+        self.lock = threading.RLock()
+        self._refresh()        
+
+    def _sr_uuid(self):
+        uuid_file = os.path.join(XEND_STORAGE_DIR, 'uuid')
+        try:
+            if os.path.exists(uuid_file):
+                return open(uuid_file, 'r').read().strip()
+            else:
+                new_uuid = uuid.createString()
+                open(uuid_file, 'w').write(new_uuid + '\n')
+                return new_uuid
+        except IOError:
+            # TODO: log warning
+            pass
+
+        return uuid.createString()
+
+    def _refresh(self):
+        """Internal function that refreshes the state of the disk and
+        updates the list of images available.
+        """
+        self.lock.acquire()
+        try:
+            if not os.path.exists(XEND_STORAGE_DIR):
+                os.makedirs(XEND_STORAGE_DIR)
+                os.chmod(XEND_STORAGE_DIR, 0700)
+
+            # scan the directory and populate self.images
+            total_used = 0
+            seen_images = []
+            for filename in os.listdir(XEND_STORAGE_DIR):
+                if filename[-5:] == XEND_STORAGE_QCOW_FILENAME[-5:]:
+                    image_uuid = filename[:-5]
+                    seen_images.append(image_uuid)
+                    if image_uuid not in self.images:
+                        image_file = XEND_STORAGE_IMG_FILENAME % image_uuid
+                        qcow_file = XEND_STORAGE_QCOW_FILENAME % image_uuid
+                        image_path = os.path.join(XEND_STORAGE_DIR,
+                                                  image_file)
+                        qcow_path = os.path.join(XEND_STORAGE_DIR, qcow_file)
+                        image_size_kb = (os.stat(image_path).st_size)/1024
+
+                        vdi = XendQCOWVDI(image_uuid, self.uuid,
+                                          qcow_path, image_path,
+                                          image_size_kb, image_size_kb)
+                        self.images[image_uuid] = vdi
+                        total_used += image_size_kb
+
+            # remove images that aren't valid
+            for image_uuid in self.images.keys():
+                if image_uuid not in seen_images:
+                    try:
+                        os.unlink(self.images[image_uuid].qcow_path)
+                        os.unlink(self.images[image_uuid].image_path)
+                    except OSError:
+                        pass
+                    del self.images[image_uuid]
+
+            # update free storage if we have to track that
+            if self.storage_max != XEND_STORAGE_MAX_IGNORE:
+                self.storage_free = self.storage_max - total_used
+            else:
+                self.storage_free = self._get_free_space()
+                        
+        finally:
+            self.lock.release()
+
+    def _get_df(self):
+        """Returns the output of 'df' in a dictionary where the keys
+        are the Linux device numbers, and the values are it's corresponding
+        free space in KB.
+
+        @rtype: dictionary
+        """
+        df = commands.getoutput(DF_COMMAND)
+        devnum_free = {}
+        for line in df.split('\n')[1:]:
+            words = line.split()
+            mount_point = words[-1]
+            dev_no = os.stat(mount_point).st_dev
+            free_blks = int(words[3])
+            devnum_free[dev_no] = free_blks
+        return devnum_free
+
+    def _get_free_space(self):
+        """Returns the amount of free space in KB available in the storage
+        partition. Note that this may not be used if the storage repository
+        is initialised with a maximum size in storage_max.
+
+        @rtype: int
+        """
+        df = self._get_df()
+        devnum = os.stat(self.storage_dir).st_dev
+        if df.has_key(devnum):
+            return df[devnum]
+        raise DeviceInvalidError("Device not found for storage path: %s" %
+                                 self.storage_dir)
+
+    def _has_space_available_for(self, size_kb):
+        """Returns whether there is enough space for an image in the
+        partition which the storage_dir resides on.
+
+        @rtype: bool
+        """
+        if self.storage_max != -1:
+            return self.storage_free
+        
+        kb_free = self._get_free_space()
+        try:
+            if size_kb < kb_free:
+                return True
+        except DeviceInvalidError:
+            pass
+        return False
+
+    def create_image(self, desired_size_kb):
+        """Create an image and return its assigned UUID.
+
+        @param desired_size_kb: Desired image size in KB.
+        @type  desired_size_kb: int
+        @rtype: string
+        @return: uuid
+
+        @raises XendError: If an error occurs.
+        """
+        self.lock.acquire()
+        try:
+            if not self._has_space_available_for(desired_size_kb):
+                raise XendError("Not enough space")
+
+            image_uuid = uuid.createString()
+            # create file based image
+            image_path = os.path.join(XEND_STORAGE_DIR,
+                                      XEND_STORAGE_IMG_FILENAME % image_uuid)
+            block = '\x00' * 1024
+            img = open(image_path, 'w')
+            for i in range(desired_size_kb):
+                img.write(block)
+            img.close()
+            
+            # TODO: create qcow image
+            qcow_path = os.path.join(XEND_STORAGE_DIR,
+                                     XEND_STORAGE_QCOW_FILENAME % image_uuid)
+            cmd = QCOW_CREATE_COMMAND % (desired_size_kb/1024,
+                                         qcow_path, image_path)
+
+            rc, output = commands.getstatusoutput(cmd)
+            if rc != 0:
+                # cleanup the image file
+                os.unlink(image_path)
+                raise XendError("Failed to create QCOW Image: %s" % output)
+
+            self._refresh()
+            return image_uuid
+        finally:
+            self.lock.release()
+        
+    def destroy_image(self, image_uuid):
+        """Destroy an image that is managed by this storage repository.
+
+        @param image_uuid: Image UUID
+        @type  image_uuid: String
+        @rtype: String
+        """
+        self.lock.acquire()
+        try:
+            if image_uuid in self.images:
+                # TODO: check if it is being used?
+                qcow_path = self.images[image_uuid].qcow_path
+                image_path = self.images[image_uuid].image_path
+                try:
+                    os.unlink(qcow_path)
+                    os.unlink(image_path)
+                except OSError:
+                    # TODO: log warning
+                    pass
+                del self.images[image_uuid]
+                return True
+        finally:
+            self.lock.release()
+        
+        return False
+
+    def list_images(self):
+        """ List all the available images by UUID.
+
+        @rtype: list of strings.
+        @return: list of UUIDs
+        """
+        self.lock.acquire()
+        try:
+            return self.images.keys()
+        finally:
+            self.lock.release()
+
+    def free_space_kb(self):
+        """Returns the amount of available space in KB.
+        @rtype: int
+        """
+        self.lock.acquire()
+        try:
+            return self.storage_free
+        finally:
+            self.lock.release()
+            
+    def total_space_kb(self):
+        """Returns the total usable space of the storage repo in KB.
+        @rtype: int
+        """
+        self.lock.acquire()
+        try:
+            if self.storage_max != XEND_STORAGE_MAX_IGNORE:
+                return self.storage_max
+            else:
+                return self.free_space_kb() + self.used_space_kb()
+        finally:
+            self.lock.release()
+            
+    def used_space_kb(self):
+        """Returns the total amount of space used by this storage repository.
+        @rtype: int
+        """
+        self.lock.acquire()
+        try:
+            total_used = 0
+            for val in self.images.values():
+                total_used += val.get_physical_utilisation()
+            return total_used
+        finally:
+            self.lock.release()
+
+    def used_space_bytes(self):
+        return self.used_space_kb() * KB
+    def free_space_bytes(self):
+        return self.free_space_kb() * KB
+    def total_space_bytes(self):
+        return self.total_space_kb() * KB
+
+
+# remove everything below this line!!
+if __name__ == "__main__":
+    xsr = XendStorageRepository()
+    print 'Free Space: %d MB' % (xsr.free_space_kb()/1024)
+    print "Create Image:",
+    print xsr.create_image(10 * 1024)
+    print 'Delete all images:'
+    for image_uuid in xsr.list_images():
+        xsr.destroy_image(image_uuid)
diff -r 255925ae4127 -r 1b923f4a788a tools/python/xen/xend/XendVDI.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/python/xen/xend/XendVDI.py  Thu Oct 12 18:01:32 2006 +0100
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+#============================================================================
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#============================================================================
+# Copyright (C) 2006 XenSource Ltd.
+#============================================================================
+#
+# Representation of a Xen API VDI
+#
+
+KB = 1024
+MB = 1024 * 1024
+
+class XendVDI:
+    def __init__(self, uuid, sr_uuid):
+        self.uuid = uuid
+        self.sr_uuid = sr_uuid
+
+class XendQCOWVDI(XendVDI):
+    vdi_type = "system"
+
+    def __init__(self, uuid, sr_uuid, qcow_path, image_path, vsize, psize):
+        XendVDI.__init__(self, uuid, sr_uuid)
+        self.qcow_path = qcow_path
+        self.image_path = image_path
+        self.vsize = vsize
+        self.psize = psize
+
+    def get_physical_utilisation(self):
+        return self.psize * KB
+
+    def get_virtual_size(self):
+        return self.vsize * KB

_______________________________________________
Xen-changelog mailing list
Xen-changelog@xxxxxxxxxxxxxxxxxxx
http://lists.xensource.com/xen-changelog


 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.