ia64/xen-unstable

view tools/python/xen/xend/XendStorageRepository.py @ 13220:c731c158f63c

Remove a few more explicit type conversions, now that stringify is fixed.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Thu Dec 28 16:52:44 2006 +0000 (2006-12-28)
parents 11b9ccdc9417
children 665be23d7fe9
line source
1 #!/usr/bin/python
2 #============================================================================
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of version 2.1 of the GNU Lesser General Public
5 # License as published by the Free Software Foundation.
6 #
7 # This library is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 # Lesser General Public License for more details.
11 #
12 # You should have received a copy of the GNU Lesser General Public
13 # License along with this library; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15 #============================================================================
16 # Copyright (C) 2006 XenSource Ltd.
17 #============================================================================
18 #
19 # The default QCOW Xen API Storage Repository
20 #
22 import commands
23 import logging
24 import os
25 import stat
26 import threading
27 import re
28 import sys
29 import struct
31 from xen.util import mkdir
32 from xen.xend import uuid
33 from xen.xend.XendError import XendError
34 from xen.xend.XendVDI import *
37 XEND_STORAGE_NO_MAXIMUM = sys.maxint
38 XEND_STORAGE_DIR = "/var/lib/xend/storage/"
39 XEND_STORAGE_QCOW_FILENAME = "%s.qcow"
40 XEND_STORAGE_VDICFG_FILENAME = "%s.vdi.xml"
41 QCOW_CREATE_COMMAND = "/usr/sbin/qcow-create -r %d %s"
43 MB = 1024 * 1024
45 log = logging.getLogger("xend.XendStorageRepository")
48 def qcow_virtual_size(qcow_file):
49 """Read the first 32 bytes of the QCoW header to determine its size.
51 See: http://www.gnome.org/~markmc/qcow-image-format.html.
52 """
53 try:
54 qcow_header = open(qcow_file, 'rb').read(32)
55 parts = struct.unpack('>IIQIIQ', qcow_header)
56 return parts[-1]
57 except IOError:
58 return -1
60 class XendStorageRepository:
61 """A simple file backed QCOW Storage Repository.
63 This class exposes the interface to create VDI's via the
64 Xen API. The backend is a file-backed QCOW format that is stored
65 in XEND_STORAGE_DIR or any that is specified in the constructor.
67 The actual images are created in the format <uuid>.img and <uuid>.qcow.
68 """
70 def __init__(self, uuid,
71 sr_type = "qcow_file",
72 name_label = "Local",
73 name_description = "Xend Storage Repository",
74 location = XEND_STORAGE_DIR,
75 storage_max = XEND_STORAGE_NO_MAXIMUM):
76 """
77 @keyword storage_max: Maximum disk space to use in bytes.
78 @type storage_max: int
80 @ivar storage_free: storage space free for this repository
81 @ivar images: mapping of all the images.
82 @type images: dictionary by image uuid.
83 @ivar lock: lock to provide thread safety.
84 """
86 # XenAPI Parameters
87 self.uuid = uuid
88 self.type = sr_type
89 self.location = location
90 self.name_label = name_label
91 self.name_description = name_description
92 self.images = {}
94 self.storage_max = storage_max
95 self.storage_free = 0
96 self.storage_used = 0
97 self.storage_alloc = 0
99 self.lock = threading.RLock()
100 self._refresh()
102 def get_record(self):
103 retval = {'uuid': self.uuid,
104 'name_label': self.name_label,
105 'name_description': self.name_description,
106 'virtual_allocation': self.storage_alloc,
107 'physical_utilisation': self.storage_used,
108 'physical_size': self.storage_max,
109 'type': self.type,
110 'location': self.location,
111 'VDIs': self.images.keys()}
113 if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
114 stfs = os.statvfs(self.location)
115 retval['physical_size'] = stfs.f_blocks * stfs.f_frsize
117 return retval
119 def _refresh(self):
120 """Internal function that refreshes the state of the disk and
121 updates the list of images available.
122 """
123 self.lock.acquire()
124 try:
125 mkdir.parents(self.location, stat.S_IRWXU)
127 # scan the directory and populate self.images
128 virtual_alloc = 0
129 physical_used = 0
130 seen_images = []
131 for filename in os.listdir(self.location):
132 if filename[-5:] == XEND_STORAGE_QCOW_FILENAME[-5:]:
133 image_uuid = filename[:-5]
134 seen_images.append(image_uuid)
136 qcow_file = XEND_STORAGE_QCOW_FILENAME % image_uuid
137 cfg_file = XEND_STORAGE_VDICFG_FILENAME % image_uuid
138 qcow_path = os.path.join(self.location, qcow_file)
139 cfg_path = os.path.join(self.location, cfg_file)
141 phys_size = os.stat(qcow_path).st_size
142 virt_size = qcow_virtual_size(qcow_path)
144 # add this image if we haven't seen it before
145 if image_uuid not in self.images:
146 vdi = XendQCOWVDI(image_uuid, self.uuid,
147 qcow_path, cfg_path,
148 virt_size, phys_size)
150 if cfg_path and os.path.exists(cfg_path):
151 try:
152 vdi.load_config(cfg_path)
153 except:
154 log.error('Corrupt VDI configuration file %s' %
155 cfg_path)
157 self.images[image_uuid] = vdi
159 physical_used += phys_size
160 virtual_alloc += virt_size
162 # remove images that aren't valid
163 for image_uuid in self.images.keys():
164 if image_uuid not in seen_images:
165 try:
166 os.unlink(self.images[image_uuid].qcow_path)
167 except OSError:
168 pass
169 del self.images[image_uuid]
171 self.storage_alloc = virtual_alloc
172 self.storage_used = physical_used
174 # update free storage if we have to track that
175 if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
176 self.storage_free = self._get_free_space()
177 else:
178 self.storage_free = self.storage_max - self.storage_alloc
180 finally:
181 self.lock.release()
183 def _get_free_space(self):
184 """Returns the amount of free space in bytes available in the storage
185 partition. Note that this may not be used if the storage repository
186 is initialised with a maximum size in storage_max.
188 @rtype: int
189 """
190 stfs = os.statvfs(self.location)
191 return stfs.f_bavail * stfs.f_frsize
193 def _has_space_available_for(self, size_bytes):
194 """Returns whether there is enough space for an image in the
195 partition which the storage_dir resides on.
197 @rtype: bool
198 """
199 if self.storage_max != XEND_STORAGE_NO_MAXIMUM:
200 return self.storage_free > size_bytes
202 bytes_free = self._get_free_space()
203 if size_bytes < bytes_free:
204 return True
205 return False
207 def _create_image_files(self, desired_size_bytes):
208 """Create an image and return its assigned UUID.
210 @param desired_size_bytes: Desired image size in bytes
211 @type desired_size_bytes: int
212 @rtype: string
213 @return: uuid
215 @raises XendError: If an error occurs.
216 """
217 self.lock.acquire()
218 try:
219 if not self._has_space_available_for(desired_size_bytes):
220 raise XendError("Not enough space")
222 image_uuid = uuid.createString()
223 qcow_path = os.path.join(self.location,
224 XEND_STORAGE_QCOW_FILENAME % image_uuid)
226 if qcow_path and os.path.exists(qcow_path):
227 raise XendError("Image with same UUID alreaady exists:" %
228 image_uuid)
230 cmd = QCOW_CREATE_COMMAND % (desired_size_bytes/MB, qcow_path)
231 rc, output = commands.getstatusoutput(cmd)
233 if rc != 0:
234 # cleanup the image file
235 os.unlink(qcow_path)
236 raise XendError("Failed to create QCOW Image: %s" % output)
238 self._refresh()
239 return image_uuid
240 finally:
241 self.lock.release()
243 def destroy_image(self, image_uuid):
244 """Destroy an image that is managed by this storage repository.
246 @param image_uuid: Image UUID
247 @type image_uuid: String
248 @rtype: String
249 """
250 self.lock.acquire()
251 try:
252 if image_uuid in self.images:
253 # TODO: check if it is being used?
254 qcow_path = self.images[image_uuid].qcow_path
255 cfg_path = self.images[image_uuid].cfg_path
256 try:
257 os.unlink(qcow_path)
258 if cfg_path and os.path.exists(cfg_path):
259 os.unlink(cfg_path)
260 except OSError:
261 log.exception("Failed to destroy image")
262 del self.images[image_uuid]
263 return True
264 finally:
265 self.lock.release()
267 return False
269 def list_images(self):
270 """ List all the available images by UUID.
272 @rtype: list of strings.
273 @return: list of UUIDs
274 """
275 self.lock.acquire()
276 try:
277 return self.images.keys()
278 finally:
279 self.lock.release()
281 def free_space_bytes(self):
282 """Returns the amount of available space in KB.
283 @rtype: int
284 """
285 self.lock.acquire()
286 try:
287 return self.storage_free
288 finally:
289 self.lock.release()
291 def total_space_bytes(self):
292 """Returns the total usable space of the storage repo in KB.
293 @rtype: int
294 """
295 self.lock.acquire()
296 try:
297 if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
298 stfs = os.statvfs(self.location)
299 return stfs.f_blocks * stfs.f_frsize
300 else:
301 return self.storage_max
302 finally:
303 self.lock.release()
305 def used_space_bytes(self):
306 """Returns the total amount of space used by this storage repository.
307 @rtype: int
308 """
309 self.lock.acquire()
310 try:
311 return self.storage_used
312 finally:
313 self.lock.release()
315 def virtual_allocation(self):
316 """Returns the total virtual space allocated within the storage repo.
317 @rtype: int
318 """
319 self.lock.acquire()
320 try:
321 return self.storage_alloc
322 finally:
323 self.lock.release()
325 def is_valid_vdi(self, vdi_uuid):
326 return (vdi_uuid in self.images)
328 def create_image(self, vdi_struct):
329 image_uuid = None
330 try:
331 sector_count = int(vdi_struct.get('virtual_size', 0))
332 sector_size = int(vdi_struct.get('sector_size', 1024))
333 size_bytes = (sector_count * sector_size)
335 image_uuid = self._create_image_files(size_bytes)
336 image = self.images[image_uuid]
337 image_cfg = {
338 'sector_size': sector_size,
339 'virtual_size': sector_count,
340 'type': vdi_struct.get('type', 'system'),
341 'name_label': vdi_struct.get('name_label', ''),
342 'name_description': vdi_struct.get('name_description', ''),
343 'sharable': bool(vdi_struct.get('sharable', False)),
344 'read_only': bool(vdi_struct.get('read_only', False)),
345 }
347 # load in configuration from vdi_struct
348 image.load_config_dict(image_cfg)
350 # save configuration to file
351 cfg_filename = XEND_STORAGE_VDICFG_FILENAME % image_uuid
352 cfg_path = os.path.join(self.location, cfg_filename)
353 image.save_config(cfg_path)
355 except Exception, e:
356 # cleanup before raising exception
357 if image_uuid:
358 self.destroy_image(image_uuid)
360 raise
362 return image_uuid
364 def xen_api_get_by_name_label(self, label):
365 self.lock.acquire()
366 try:
367 for image_uuid, val in self.images.items():
368 if val.name_label == label:
369 return image_uuid
370 return None
371 finally:
372 self.lock.release()
374 def xen_api_get_by_uuid(self, image_uuid):
375 self.lock.acquire()
376 try:
377 return self.images.get(image_uuid)
378 finally:
379 self.lock.release()
382 # remove everything below this line!!
383 if __name__ == "__main__":
384 xsr = XendStorageRepository()
385 print 'Free Space: %d MB' % (xsr.free_space_bytes()/MB)
386 print "Create Image:",
387 print xsr._create_image_files(10 * MB)
388 print 'Delete all images:'
389 for image_uuid in xsr.list_images():
390 print image_uuid,
391 xsr._destroy_image_files(image_uuid)
393 print