ia64/xen-unstable

view tools/python/xen/xend/XendStorageRepository.py @ 12725:36fe7ca48e54

Tidy up the creation of directories that Xend needs. This avoids potential
races in this creation.

Signed-off-by: Ewan Mellor <ewan@xensource.com>
author Ewan Mellor <ewan@xensource.com>
date Fri Dec 01 11:32:32 2006 +0000 (2006-12-01)
parents 2511ac1f1e21
children d0f682ada0b2
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 os
24 import stat
25 import threading
27 from xen.util import mkdir
28 from xen.xend import uuid
29 from xen.xend.XendError import XendError
30 from xen.xend.XendVDI import *
32 XEND_STORAGE_MAX_IGNORE = -1
33 XEND_STORAGE_DIR = "/var/lib/xend/storage/"
34 XEND_STORAGE_QCOW_FILENAME = "%s.qcow"
35 XEND_STORAGE_VDICFG_FILENAME = "%s.vdi.xml"
36 QCOW_CREATE_COMMAND = "/usr/sbin/qcow-create %d %s"
38 MB = 1024 *1024
40 class DeviceInvalidError(Exception):
41 pass
43 class XendStorageRepository:
44 """A simple file backed QCOW Storage Repository.
46 This class exposes the interface to create VDI's via the
47 Xen API. The backend is a file-backed QCOW format that is stored
48 in XEND_STORAGE_DIR or any that is specified in the constructor.
50 The actual images are created in the format <uuid>.img and <uuid>.qcow.
51 """
53 def __init__(self, storage_dir = XEND_STORAGE_DIR,
54 storage_max = XEND_STORAGE_MAX_IGNORE):
55 """
56 @keyword storage_dir: Where the images will be stored.
57 @type storage_dir: string
58 @keyword storage_max: Maximum disk space to use in bytes.
59 @type storage_max: int
61 @ivar storage_free: storage space free for this repository
62 @ivar images: mapping of all the images.
63 @type images: dictionary by image uuid.
64 @ivar lock: lock to provide thread safety.
65 """
67 self.storage_dir = storage_dir
68 self.storage_max = storage_max
69 self.storage_free = 0
70 self.images = {}
72 # XenAPI Parameters
73 self.uuid = self._sr_uuid()
74 self.type = "qcow-file"
75 self.location = self.storage_dir
76 self.name_label = "Local"
77 self.name_description = "Xend Storage Repository"
79 self.lock = threading.RLock()
80 self._refresh()
82 def _sr_uuid(self):
83 uuid_file = os.path.join(XEND_STORAGE_DIR, 'uuid')
84 try:
85 if uuid_file and os.path.exists(uuid_file):
86 return open(uuid_file, 'r').read().strip()
87 else:
88 new_uuid = uuid.createString()
89 open(uuid_file, 'w').write(new_uuid + '\n')
90 return new_uuid
91 except IOError:
92 # TODO: log warning
93 pass
95 return uuid.createString()
97 def _refresh(self):
98 """Internal function that refreshes the state of the disk and
99 updates the list of images available.
100 """
101 self.lock.acquire()
102 try:
103 mkdir.parents(XEND_STORAGE_DIR, stat.S_IRWXU)
105 # scan the directory and populate self.images
106 total_used = 0
107 seen_images = []
108 for filename in os.listdir(XEND_STORAGE_DIR):
109 if filename[-5:] == XEND_STORAGE_QCOW_FILENAME[-5:]:
110 image_uuid = filename[:-5]
111 seen_images.append(image_uuid)
113 # add this image if we haven't seen it before
114 if image_uuid not in self.images:
115 qcow_file = XEND_STORAGE_QCOW_FILENAME % image_uuid
116 cfg_file = XEND_STORAGE_VDICFG_FILENAME % image_uuid
117 qcow_path = os.path.join(XEND_STORAGE_DIR, qcow_file)
118 cfg_path = os.path.join(XEND_STORAGE_DIR, cfg_file)
120 qcow_size = os.stat(qcow_path).st_size
122 # TODO: no way to stat virtual size of qcow
123 vdi = XendQCOWVDI(image_uuid, self.uuid,
124 qcow_path, cfg_path,
125 qcow_size, qcow_size)
127 if cfg_path and os.path.exists(cfg_path):
128 vdi.load_config(cfg_path)
130 self.images[image_uuid] = vdi
131 total_used += qcow_size
133 # remove images that aren't valid
134 for image_uuid in self.images.keys():
135 if image_uuid not in seen_images:
136 try:
137 os.unlink(self.images[image_uuid].qcow_path)
138 except OSError:
139 pass
140 del self.images[image_uuid]
142 # update free storage if we have to track that
143 if self.storage_max != XEND_STORAGE_MAX_IGNORE:
144 self.storage_free = self.storage_max - total_used
145 else:
146 self.storage_free = self._get_free_space()
148 finally:
149 self.lock.release()
151 def _get_free_space(self):
152 """Returns the amount of free space in bytes available in the storage
153 partition. Note that this may not be used if the storage repository
154 is initialised with a maximum size in storage_max.
156 @rtype: int
157 """
158 stfs = os.statvfs(self.storage_dir)
159 return stfs.f_bavail * stfs.f_frsize
161 def _has_space_available_for(self, size_bytes):
162 """Returns whether there is enough space for an image in the
163 partition which the storage_dir resides on.
165 @rtype: bool
166 """
167 if self.storage_max != -1:
168 return self.storage_free
170 bytes_free = self._get_free_space()
171 try:
172 if size_bytes < bytes_free:
173 return True
174 except DeviceInvalidError:
175 pass
176 return False
178 def _create_image_files(self, desired_size_bytes):
179 """Create an image and return its assigned UUID.
181 @param desired_size_kb: Desired image size in KB.
182 @type desired_size_kb: int
183 @rtype: string
184 @return: uuid
186 @raises XendError: If an error occurs.
187 """
188 self.lock.acquire()
189 try:
190 if not self._has_space_available_for(desired_size_bytes):
191 raise XendError("Not enough space")
193 image_uuid = uuid.createString()
194 qcow_path = os.path.join(XEND_STORAGE_DIR,
195 XEND_STORAGE_QCOW_FILENAME % image_uuid)
197 if qcow_path and os.path.exists(qcow_path):
198 raise XendError("Image with same UUID alreaady exists:" %
199 image_uuid)
201 cmd = QCOW_CREATE_COMMAND % (desired_size_bytes/MB, qcow_path)
202 rc, output = commands.getstatusoutput(cmd)
204 if rc != 0:
205 # cleanup the image file
206 os.unlink(qcow_path)
207 raise XendError("Failed to create QCOW Image: %s" % output)
209 self._refresh()
210 return image_uuid
211 finally:
212 self.lock.release()
214 def destroy_image(self, image_uuid):
215 """Destroy an image that is managed by this storage repository.
217 @param image_uuid: Image UUID
218 @type image_uuid: String
219 @rtype: String
220 """
221 self.lock.acquire()
222 try:
223 if image_uuid in self.images:
224 # TODO: check if it is being used?
225 qcow_path = self.images[image_uuid].qcow_path
226 cfg_path = self.images[image_uuid].cfg_path
227 try:
228 os.unlink(qcow_path)
229 if cfg_path and os.path.exists(cfg_path):
230 os.unlink(cfg_path)
231 except OSError:
232 # TODO: log warning
233 pass
234 del self.images[image_uuid]
235 return True
236 finally:
237 self.lock.release()
239 return False
241 def list_images(self):
242 """ List all the available images by UUID.
244 @rtype: list of strings.
245 @return: list of UUIDs
246 """
247 self.lock.acquire()
248 try:
249 return self.images.keys()
250 finally:
251 self.lock.release()
253 def free_space_bytes(self):
254 """Returns the amount of available space in KB.
255 @rtype: int
256 """
257 self.lock.acquire()
258 try:
259 return self.storage_free
260 finally:
261 self.lock.release()
263 def total_space_bytes(self):
264 """Returns the total usable space of the storage repo in KB.
265 @rtype: int
266 """
267 self.lock.acquire()
268 try:
269 if self.storage_max != XEND_STORAGE_MAX_IGNORE:
270 return self.storage_max
271 else:
272 return self.free_space_bytes() + self.used_space_bytes()
273 finally:
274 self.lock.release()
276 def used_space_bytes(self):
277 """Returns the total amount of space used by this storage repository.
278 @rtype: int
279 """
280 self.lock.acquire()
281 try:
282 total_used = 0
283 for val in self.images.values():
284 total_used += val.physical_utilisation
285 return total_used
286 finally:
287 self.lock.release()
289 def is_valid_vdi(self, vdi_uuid):
290 return (vdi_uuid in self.images)
292 def create_image(self, vdi_struct):
293 image_uuid = None
294 try:
295 sector_count = int(vdi_struct.get('virtual_size', 0))
296 sector_size = int(vdi_struct.get('sector_size', 1024))
297 size_bytes = (sector_count * sector_size)
299 image_uuid = self._create_image_files(size_bytes)
300 image = self.images[image_uuid]
301 image_cfg = {
302 'sector_size': sector_size,
303 'virtual_size': sector_count,
304 'type': vdi_struct.get('type', 'system'),
305 'name_label': vdi_struct.get('name_label', ''),
306 'name_description': vdi_struct.get('name_description', ''),
307 'sharable': bool(vdi_struct.get('sharable', False)),
308 'read_only': bool(vdi_struct.get('read_only', False)),
309 }
311 # load in configuration from vdi_struct
312 image.load_config_dict(image_cfg)
314 # save configuration to file
315 cfg_filename = XEND_STORAGE_VDICFG_FILENAME % image_uuid
316 cfg_path = os.path.join(XEND_STORAGE_DIR, cfg_filename)
317 image.save_config(cfg_path)
319 except Exception, e:
320 # cleanup before raising exception
321 if image_uuid:
322 self.destroy_image(image_uuid)
324 raise
326 return image_uuid
328 def xen_api_get_by_label(self, label):
329 self.lock.acquire()
330 try:
331 for image_uuid, val in self.images.values():
332 if val.name_label == label:
333 return image_uuid
334 return None
335 finally:
336 self.lock.release()
338 def xen_api_get_by_uuid(self, image_uuid):
339 self.lock.acquire()
340 try:
341 return self.images.get(image_uuid)
342 finally:
343 self.lock.release()
346 # remove everything below this line!!
347 if __name__ == "__main__":
348 xsr = XendStorageRepository()
349 print 'Free Space: %d MB' % (xsr.free_space_bytes()/MB)
350 print "Create Image:",
351 print xsr._create_image_files(10 * MB)
352 print 'Delete all images:'
353 for image_uuid in xsr.list_images():
354 print image_uuid,
355 xsr._destroy_image_files(image_uuid)
357 print