Merge lp:~jk0/nova/lp824034 into lp:~hudson-openstack/nova/trunk

Proposed by Josh Kearney
Status: Merged
Approved by: Matt Dietz
Approved revision: 1428
Merged at revision: 1440
Proposed branch: lp:~jk0/nova/lp824034
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 287 lines (+133/-14)
7 files modified
nova/compute/manager.py (+53/-0)
nova/exception.py (+4/-0)
nova/tests/scheduler/test_scheduler.py (+3/-1)
nova/tests/xenapi/stubs.py (+2/-2)
nova/virt/xenapi/fake.py (+1/-0)
nova/virt/xenapi/vm_utils.py (+67/-8)
nova/virt/xenapi/vmops.py (+3/-3)
To merge this branch: bzr merge lp:~jk0/nova/lp824034
Reviewer Review Type Date Requested Status
Matt Dietz (community) Approve
Rick Harris (community) Approve
Dan Prince (community) Needs Fixing
Review via email: mp+71430@code.launchpad.net

Description of the change

Validate the size of VHD files in OVF containers.

To post a comment you must log in.
Revision history for this message
Dan Prince (dan-prince) wrote :

Hi Josh,

The new rev of the v1.1 OSAPI has image attributes for minDisk and minRam. Is it possible we might be able to use these to predetermine if an image/instance-type combination is invalid? This would probably require an update to Glance as well but I thought it worth mentioning.

--

Shouldn't 'physical_utilisation' be 'physical_utilization'?

--

Lastly, I get a conflict when merging trunk:

nova/virt/xenapi/vm_utils.py

review: Needs Fixing
Revision history for this message
Josh Kearney (jk0) wrote :

> The new rev of the v1.1 OSAPI has image attributes for minDisk and minRam. Is
> it possible we might be able to use these to predetermine if an image
> /instance-type combination is invalid? This would probably require an update
> to Glance as well but I thought it worth mentioning.

To the best of my knowledge, those are used in cases where there is a minimum amount of RAM/disk space required to boot an instance (for example, Windows requires at least 512MB of RAM) -- so a Windows image might have a minRam of 512 and minDisk of 10GB, but that doesn't imply the size of the image is actually 10GB.

> Shouldn't 'physical_utilisation' be 'physical_utilization'?

I believe this was written by Citrix employees in the UK, so it's actually 'physical_utilisation'.

> Lastly, I get a conflict when merging trunk:
>
> nova/virt/xenapi/vm_utils.py

Thanks, I'll merge trunk and re-push.

lp:~jk0/nova/lp824034 updated
1425. By Josh Kearney

Merged trunk.

1426. By Josh Kearney

Fixed merge conflict.

1427. By Josh Kearney

Merged trunk.

1428. By Josh Kearney

Corrected names in TODO/FIXME.

Revision history for this message
Rick Harris (rconradharris) wrote :

LGTM

review: Approve
Revision history for this message
Matt Dietz (cerberus) wrote :

Is bueno

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/compute/manager.py'
2--- nova/compute/manager.py 2011-08-09 09:54:51 +0000
3+++ nova/compute/manager.py 2011-08-15 18:37:26 +0000
4@@ -321,10 +321,63 @@
5
6 def _run_instance(self, context, instance_id, **kwargs):
7 """Launch a new instance with specified options."""
8+ def _check_image_size():
9+ """Ensure image is smaller than the maximum size allowed by the
10+ instance_type.
11+
12+ The image stored in Glance is potentially compressed, so we use two
13+ checks to ensure that the size isn't exceeded:
14+
15+ 1) This one - checks compressed size, this a quick check to
16+ eliminate any images which are obviously too large
17+
18+ 2) Check uncompressed size in nova.virt.xenapi.vm_utils. This
19+ is a slower check since it requires uncompressing the entire
20+ image, but is accurate because it reflects the image's
21+ actual size.
22+ """
23+ # NOTE(jk0): image_ref is defined in the DB model, image_href is
24+ # used by the image service. This should be refactored to be
25+ # consistent.
26+ image_href = instance['image_ref']
27+ image_service, image_id = nova.image.get_image_service(image_href)
28+ image_meta = image_service.show(context, image_id)
29+
30+ try:
31+ size_bytes = image_meta['size']
32+ except KeyError:
33+ # Size is not a required field in the image service (yet), so
34+ # we are unable to rely on it being there even though it's in
35+ # glance.
36+
37+ # TODO(jk0): Should size be required in the image service?
38+ return
39+
40+ instance_type_id = instance['instance_type_id']
41+ instance_type = self.db.instance_type_get(context,
42+ instance_type_id)
43+ allowed_size_gb = instance_type['local_gb']
44+ allowed_size_bytes = allowed_size_gb * 1024 * 1024 * 1024
45+
46+ LOG.debug(_("image_id=%(image_id)d, image_size_bytes="
47+ "%(size_bytes)d, allowed_size_bytes="
48+ "%(allowed_size_bytes)d") % locals())
49+
50+ if size_bytes > allowed_size_bytes:
51+ LOG.info(_("Image '%(image_id)d' size %(size_bytes)d exceeded"
52+ " instance_type allowed size "
53+ "%(allowed_size_bytes)d")
54+ % locals())
55+ raise exception.ImageTooLarge()
56+
57 context = context.elevated()
58 instance = self.db.instance_get(context, instance_id)
59+
60 if instance['name'] in self.driver.list_instances():
61 raise exception.Error(_("Instance has already been created"))
62+
63+ _check_image_size()
64+
65 LOG.audit(_("instance %s: starting..."), instance_id,
66 context=context)
67 updates = {}
68
69=== modified file 'nova/exception.py'
70--- nova/exception.py 2011-08-09 23:59:51 +0000
71+++ nova/exception.py 2011-08-15 18:37:26 +0000
72@@ -721,3 +721,7 @@
73
74 class CannotResizeToSmallerSize(NovaException):
75 message = _("Resizing to a smaller size is not supported.")
76+
77+
78+class ImageTooLarge(NovaException):
79+ message = _("Image is larger than instance type allows")
80
81=== modified file 'nova/tests/scheduler/test_scheduler.py'
82--- nova/tests/scheduler/test_scheduler.py 2011-08-11 23:26:26 +0000
83+++ nova/tests/scheduler/test_scheduler.py 2011-08-15 18:37:26 +0000
84@@ -257,7 +257,9 @@
85 def _create_instance(self, **kwargs):
86 """Create a test instance"""
87 inst = {}
88- inst['image_id'] = 1
89+ # NOTE(jk0): If an integer is passed as the image_ref, the image
90+ # service will use the default image service (in this case, the fake).
91+ inst['image_ref'] = '1'
92 inst['reservation_id'] = 'r-fakeres'
93 inst['user_id'] = self.user_id
94 inst['project_id'] = self.project_id
95
96=== modified file 'nova/tests/xenapi/stubs.py'
97--- nova/tests/xenapi/stubs.py 2011-08-09 22:46:57 +0000
98+++ nova/tests/xenapi/stubs.py 2011-08-15 18:37:26 +0000
99@@ -28,10 +28,10 @@
100
101 def stubout_instance_snapshot(stubs):
102 @classmethod
103- def fake_fetch_image(cls, context, session, instance_id, image, user,
104+ def fake_fetch_image(cls, context, session, instance, image, user,
105 project, type):
106 from nova.virt.xenapi.fake import create_vdi
107- name_label = "instance-%s" % instance_id
108+ name_label = "instance-%s" % instance.id
109 #TODO: create fake SR record
110 sr_ref = "fakesr"
111 vdi_ref = create_vdi(name_label=name_label, read_only=False,
112
113=== modified file 'nova/virt/xenapi/fake.py'
114--- nova/virt/xenapi/fake.py 2011-06-28 11:33:12 +0000
115+++ nova/virt/xenapi/fake.py 2011-08-15 18:37:26 +0000
116@@ -140,6 +140,7 @@
117 'location': '',
118 'xenstore_data': '',
119 'sm_config': {},
120+ 'physical_utilisation': '123',
121 'VBDs': {}})
122
123
124
125=== modified file 'nova/virt/xenapi/vm_utils.py'
126--- nova/virt/xenapi/vm_utils.py 2011-08-14 04:25:46 +0000
127+++ nova/virt/xenapi/vm_utils.py 2011-08-15 18:37:26 +0000
128@@ -31,6 +31,7 @@
129 from xml.dom import minidom
130
131 import glance.client
132+from nova import db
133 from nova import exception
134 from nova import flags
135 import nova.image
136@@ -413,7 +414,7 @@
137 return vdi_ref
138
139 @classmethod
140- def fetch_image(cls, context, session, instance_id, image, user_id,
141+ def fetch_image(cls, context, session, instance, image, user_id,
142 project_id, image_type):
143 """Fetch image from glance based on image type.
144
145@@ -422,18 +423,19 @@
146 """
147 if image_type == ImageType.DISK_VHD:
148 return cls._fetch_image_glance_vhd(context,
149- session, instance_id, image, image_type)
150+ session, instance, image, image_type)
151 else:
152 return cls._fetch_image_glance_disk(context,
153- session, instance_id, image, image_type)
154+ session, instance, image, image_type)
155
156 @classmethod
157- def _fetch_image_glance_vhd(cls, context, session, instance_id, image,
158+ def _fetch_image_glance_vhd(cls, context, session, instance, image,
159 image_type):
160 """Tell glance to download an image and put the VHDs into the SR
161
162 Returns: A list of dictionaries that describe VDIs
163 """
164+ instance_id = instance.id
165 LOG.debug(_("Asking xapi to fetch vhd image %(image)s")
166 % locals())
167 sr_ref = safe_find_sr(session)
168@@ -467,17 +469,58 @@
169
170 cls.scan_sr(session, instance_id, sr_ref)
171
172- # Pull out the UUID of the first VDI
173- vdi_uuid = vdis[0]['vdi_uuid']
174+ # Pull out the UUID of the first VDI (which is the os VDI)
175+ os_vdi_uuid = vdis[0]['vdi_uuid']
176+
177 # Set the name-label to ease debugging
178- vdi_ref = session.get_xenapi().VDI.get_by_uuid(vdi_uuid)
179+ vdi_ref = session.get_xenapi().VDI.get_by_uuid(os_vdi_uuid)
180 primary_name_label = get_name_label_for_image(image)
181 session.get_xenapi().VDI.set_name_label(vdi_ref, primary_name_label)
182
183+ cls._check_vdi_size(context, session, instance, os_vdi_uuid)
184 return vdis
185
186 @classmethod
187- def _fetch_image_glance_disk(cls, context, session, instance_id, image,
188+ def _get_vdi_chain_size(cls, context, session, vdi_uuid):
189+ """Compute the total size of a VDI chain, starting with the specified
190+ VDI UUID.
191+
192+ This will walk the VDI chain to the root, add the size of each VDI into
193+ the total.
194+ """
195+ size_bytes = 0
196+ for vdi_rec in walk_vdi_chain(session, vdi_uuid):
197+ cur_vdi_uuid = vdi_rec['uuid']
198+ vdi_size_bytes = int(vdi_rec['physical_utilisation'])
199+ LOG.debug(_('vdi_uuid=%(cur_vdi_uuid)s vdi_size_bytes='
200+ '%(vdi_size_bytes)d' % locals()))
201+ size_bytes += vdi_size_bytes
202+ return size_bytes
203+
204+ @classmethod
205+ def _check_vdi_size(cls, context, session, instance, vdi_uuid):
206+ size_bytes = cls._get_vdi_chain_size(context, session, vdi_uuid)
207+
208+ # FIXME(jk0): this was copied directly from compute.manager.py, let's
209+ # refactor this to a common area
210+ instance_type_id = instance['instance_type_id']
211+ instance_type = db.instance_type_get(context,
212+ instance_type_id)
213+ allowed_size_gb = instance_type['local_gb']
214+ allowed_size_bytes = allowed_size_gb * 1024 * 1024 * 1024
215+
216+ LOG.debug(_("image_size_bytes=%(size_bytes)d, allowed_size_bytes="
217+ "%(allowed_size_bytes)d") % locals())
218+
219+ if size_bytes > allowed_size_bytes:
220+ LOG.info(_("Image size %(size_bytes)d exceeded"
221+ " instance_type allowed size "
222+ "%(allowed_size_bytes)d")
223+ % locals())
224+ raise exception.ImageTooLarge()
225+
226+ @classmethod
227+ def _fetch_image_glance_disk(cls, context, session, instance, image,
228 image_type):
229 """Fetch the image from Glance
230
231@@ -489,6 +532,7 @@
232 Returns: A single filename if image_type is KERNEL_RAMDISK
233 A list of dictionaries that describe VDIs, otherwise
234 """
235+ instance_id = instance.id
236 # FIXME(sirp): Since the Glance plugin seems to be required for the
237 # VHD disk, it may be worth using the plugin for both VHD and RAW and
238 # DISK restores
239@@ -807,6 +851,21 @@
240 return None
241
242
243+def walk_vdi_chain(session, vdi_uuid):
244+ """Yield vdi_recs for each element in a VDI chain"""
245+ # TODO(jk0): perhaps make get_vhd_parent use this
246+ while True:
247+ vdi_ref = session.get_xenapi().VDI.get_by_uuid(vdi_uuid)
248+ vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref)
249+ yield vdi_rec
250+
251+ parent_uuid = vdi_rec['sm_config'].get('vhd-parent')
252+ if parent_uuid:
253+ vdi_uuid = parent_uuid
254+ else:
255+ break
256+
257+
258 def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref,
259 original_parent_uuid):
260 """ Spin until the parent VHD is coalesced into its parent VHD
261
262=== modified file 'nova/virt/xenapi/vmops.py'
263--- nova/virt/xenapi/vmops.py 2011-08-14 04:25:46 +0000
264+++ nova/virt/xenapi/vmops.py 2011-08-15 18:37:26 +0000
265@@ -137,7 +137,7 @@
266 def _create_disks(self, context, instance):
267 disk_image_type = VMHelper.determine_disk_image_type(instance)
268 vdis = VMHelper.fetch_image(context, self._session,
269- instance.id, instance.image_ref,
270+ instance, instance.image_ref,
271 instance.user_id, instance.project_id,
272 disk_image_type)
273 return vdis
274@@ -182,11 +182,11 @@
275 try:
276 if instance.kernel_id:
277 kernel = VMHelper.fetch_image(context, self._session,
278- instance.id, instance.kernel_id, instance.user_id,
279+ instance, instance.kernel_id, instance.user_id,
280 instance.project_id, ImageType.KERNEL)[0]
281 if instance.ramdisk_id:
282 ramdisk = VMHelper.fetch_image(context, self._session,
283- instance.id, instance.ramdisk_id, instance.user_id,
284+ instance, instance.ramdisk_id, instance.user_id,
285 instance.project_id, ImageType.RAMDISK)[0]
286 # Create the VM ref and attach the first disk
287 first_vdi_ref = self._session.call_xenapi('VDI.get_by_uuid',