Merge lp:~blake-rouse/maas/fix-migration-181-1.9 into lp:maas/1.9
- fix-migration-181-1.9
- Merge into 1.9
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 4516 |
Proposed branch: | lp:~blake-rouse/maas/fix-migration-181-1.9 |
Merge into: | lp:maas/1.9 |
Diff against target: |
407 lines (+367/-10) 3 files modified
src/maasserver/migrations/0181_initial_storage_layouts.py (+35/-10) src/maasserver/models/migrations/create_default_storage_layout.py (+128/-0) src/maasserver/models/migrations/tests/test_create_default_storage_layout.py (+204/-0) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/fix-migration-181-1.9 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+279197@code.launchpad.net |
Commit message
Fix migration 181 to not call into the current model code. This was originally implement as a short cut and this does not work. As soon as any little thing changes in the model the migration cannot complete.
Includes drive-by fix for "make package".
Description of the change
This doesn't have a bug because it wasn't really a problem in 1.9, but could become one as things get changes as bugs are fixed.
MAAS Lander (maas-lander) wrote : | # |
Blake Rouse (blake-rouse) : | # |
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~blake-rouse/maas/fix-migration-181-1.9 into lp:maas/1.9 failed. Below is the output from the failed tests.
Ign http://
Get:1 http://
Get:2 http://
Hit http://
Hit http://
Hit http://
Get:3 http://
Get:4 http://
Get:5 http://
Get:6 http://
Get:7 http://
Get:8 http://
Get:9 http://
Get:10 http://
Get:11 http://
Hit http://
Hit http://
Get:12 http://
Get:13 http://
Get:14 http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~blake-rouse/maas/fix-migration-181-1.9 into lp:maas/1.9 failed. Below is the output from the failed tests.
Ign http://
Get:1 http://
Get:2 http://
Hit http://
Hit http://
Hit http://
Get:3 http://
Get:4 http://
Get:5 http://
Get:6 http://
Get:7 http://
Get:8 http://
Get:9 http://
Get:10 http://
Get:11 http://
Hit http://
Hit http://
Get:12 http://
Get:13 http://
Get:14 http://
Get:15 http://
Get:16 http://
Get:17 http://
Get:18 http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Preview Diff
1 | === modified file 'src/maasserver/migrations/0181_initial_storage_layouts.py' |
2 | --- src/maasserver/migrations/0181_initial_storage_layouts.py 2015-09-30 07:29:27 +0000 |
3 | +++ src/maasserver/migrations/0181_initial_storage_layouts.py 2015-12-02 15:05:52 +0000 |
4 | @@ -1,6 +1,9 @@ |
5 | from django.db import models |
6 | from maasserver.enum import NODE_STATUS |
7 | -from maasserver.models.node import Node |
8 | +from maasserver.models.migrations.create_default_storage_layout import ( |
9 | + clear_full_storage_configuration, |
10 | + create_lvm_layout, |
11 | +) |
12 | from south.db import db |
13 | from south.utils import datetime_utils as datetime |
14 | from south.v2 import DataMigration |
15 | @@ -20,19 +23,41 @@ |
16 | class Migration(DataMigration): |
17 | |
18 | def forwards(self, orm): |
19 | - # We don't use orm here because we want the current Node model to |
20 | - # include all of the storage methods we require. This will break |
21 | - # if the node these methods get removed from the Node, so we assert |
22 | - # that they at least exists. |
23 | - assert hasattr(Node, "_clear_full_storage_configuration") |
24 | - assert hasattr(Node, "set_default_storage_layout") |
25 | - for node in Node.objects.filter(installable=True): |
26 | + PartitionTable = orm['maasserver.PartitionTable'] |
27 | + Partition = orm['maasserver.Partition'] |
28 | + Filesystem = orm['maasserver.Filesystem'] |
29 | + FilesystemGroup = orm['maasserver.FilesystemGroup'] |
30 | + PhysicalBlockDevice = orm['maasserver.PhysicalBlockDevice'] |
31 | + VirtualBlockDevice = orm['maasserver.VirtualBlockDevice'] |
32 | + for node in orm['maasserver.Node'].objects.filter(installable=True): |
33 | if (node.status in IGNORE_STATUS and |
34 | node.blockdevice_set.count() == 0): |
35 | # Node needs to be the correct status and have storage devices. |
36 | continue |
37 | - node._clear_full_storage_configuration() |
38 | - node.set_default_storage_layout() |
39 | + |
40 | + # Clear the current storage configration just to be sure. |
41 | + clear_full_storage_configuration( |
42 | + node, |
43 | + PhysicalBlockDevice=PhysicalBlockDevice, |
44 | + VirtualBlockDevice=VirtualBlockDevice, |
45 | + PartitionTable=PartitionTable, |
46 | + Filesystem=Filesystem, |
47 | + FilesystemGroup=FilesystemGroup) |
48 | + |
49 | + # Create the LVM layout on the boot disk. |
50 | + boot_device = node.boot_disk |
51 | + if boot_device is None: |
52 | + boot_device = PhysicalBlockDevice.objects.filter( |
53 | + node=node).order_by('id').first() |
54 | + if boot_device is not None: |
55 | + create_lvm_layout( |
56 | + node, |
57 | + boot_device, |
58 | + PartitionTable=PartitionTable, |
59 | + Partition=Partition, |
60 | + Filesystem=Filesystem, |
61 | + FilesystemGroup=FilesystemGroup, |
62 | + VirtualBlockDevice=VirtualBlockDevice) |
63 | |
64 | def backwards(self, orm): |
65 | # No need to go backward. |
66 | |
67 | === added file 'src/maasserver/models/migrations/create_default_storage_layout.py' |
68 | --- src/maasserver/models/migrations/create_default_storage_layout.py 1970-01-01 00:00:00 +0000 |
69 | +++ src/maasserver/models/migrations/create_default_storage_layout.py 2015-12-02 15:05:52 +0000 |
70 | @@ -0,0 +1,128 @@ |
71 | +# Copyright 2015 Canonical Ltd. This software is licensed under the |
72 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
73 | + |
74 | +"""Migration to create the default storage layout on the boot disk. |
75 | + |
76 | +WARNING: Although these methods will become obsolete very quickly, they |
77 | +cannot be removed, since they are used by the |
78 | +0181_initial_storage_layouts DataMigration. (changing them might also |
79 | +be futile unless a customer restores from a backup, since any bugs that occur |
80 | +will have already occurred, and this code will not be executed again.) |
81 | + |
82 | +Note: Each helper must have its dependencies on any model classes injected, |
83 | +since the migration environment is a skeletal replication of the 'real' |
84 | +database model. So each function takes as parameters the model classes it |
85 | +requires. Importing from the model is not allowed here. (but the unit tests |
86 | +do it, to ensure that the migrations meet validation requirements.) |
87 | +""" |
88 | + |
89 | +from __future__ import ( |
90 | + absolute_import, |
91 | + print_function, |
92 | + unicode_literals, |
93 | + ) |
94 | + |
95 | +str = None |
96 | + |
97 | +__metaclass__ = type |
98 | +__all__ = [ |
99 | + "clear_full_storage_configuration", |
100 | + "create_lvm_layout", |
101 | +] |
102 | + |
103 | +from datetime import datetime |
104 | + |
105 | +from maasserver.enum import ( |
106 | + FILESYSTEM_GROUP_TYPE, |
107 | + FILESYSTEM_TYPE, |
108 | + PARTITION_TABLE_TYPE, |
109 | +) |
110 | +from maasserver.models.filesystemgroup import LVM_PE_SIZE |
111 | +from maasserver.models.partition import ( |
112 | + MAX_PARTITION_SIZE_FOR_MBR, |
113 | + MIN_PARTITION_SIZE, |
114 | + PARTITION_ALIGNMENT_SIZE, |
115 | +) |
116 | +from maasserver.models.partitiontable import PARTITION_TABLE_EXTRA_SPACE |
117 | +from maasserver.utils.converters import round_size_to_nearest_block |
118 | + |
119 | + |
120 | +def clear_full_storage_configuration( |
121 | + node, |
122 | + PhysicalBlockDevice, VirtualBlockDevice, |
123 | + PartitionTable, Filesystem, FilesystemGroup): |
124 | + """Clear's the full storage configuration for this node.""" |
125 | + physical_block_devices = PhysicalBlockDevice.objects.filter(node=node) |
126 | + PartitionTable.objects.filter( |
127 | + block_device__in=physical_block_devices).delete() |
128 | + Filesystem.objects.filter( |
129 | + block_device__in=physical_block_devices).delete() |
130 | + for block_device in VirtualBlockDevice.objects.filter(node=node): |
131 | + try: |
132 | + block_device.filesystem_group.virtual_devices.all().delete() |
133 | + block_device.filesystem_group.delete() |
134 | + except FilesystemGroup.DoesNotExist: |
135 | + # When a filesystem group has multiple virtual block devices |
136 | + # it is possible that accessing `filesystem_group` will |
137 | + # result in it already being deleted. |
138 | + pass |
139 | + |
140 | + |
141 | +def create_lvm_layout( |
142 | + node, boot_disk, |
143 | + PartitionTable, Partition, Filesystem, FilesystemGroup, |
144 | + VirtualBlockDevice): |
145 | + """Create the lvm layout for the boot disk.""" |
146 | + # Create the partition table and root partition. |
147 | + now = datetime.now() |
148 | + partition_table = PartitionTable.objects.create( |
149 | + block_device=boot_disk, table_type=PARTITION_TABLE_TYPE.MBR, |
150 | + created=now, updated=now) |
151 | + total_size = 0 |
152 | + available_size = boot_disk.size - PARTITION_TABLE_EXTRA_SPACE |
153 | + partition_size = round_size_to_nearest_block( |
154 | + available_size, PARTITION_ALIGNMENT_SIZE, False) |
155 | + max_mbr_size = round_size_to_nearest_block( |
156 | + MAX_PARTITION_SIZE_FOR_MBR, PARTITION_ALIGNMENT_SIZE, False) |
157 | + if partition_size > max_mbr_size: |
158 | + partition_size = max_mbr_size |
159 | + available_size -= partition_size |
160 | + total_size += partition_size |
161 | + root_partition = Partition.objects.create( |
162 | + partition_table=partition_table, size=partition_size, bootable=True, |
163 | + created=now, updated=now) |
164 | + |
165 | + # Add the extra partitions if there is more space. |
166 | + partitions = [root_partition] |
167 | + while available_size > MIN_PARTITION_SIZE: |
168 | + size = round_size_to_nearest_block( |
169 | + available_size, PARTITION_ALIGNMENT_SIZE, False) |
170 | + if size > max_mbr_size: |
171 | + size = max_mbr_size |
172 | + partitions.append( |
173 | + Partition.objects.create( |
174 | + partition_table=partition_table, size=size, bootable=False, |
175 | + created=now, updated=now)) |
176 | + available_size -= size |
177 | + total_size += size |
178 | + |
179 | + # Create the volume group and logical volume. |
180 | + volume_group = FilesystemGroup.objects.create( |
181 | + name="vgroot", group_type=FILESYSTEM_GROUP_TYPE.LVM_VG, |
182 | + created=now, updated=now) |
183 | + for partition in partitions: |
184 | + Filesystem.objects.create( |
185 | + fstype=FILESYSTEM_TYPE.LVM_PV, partition=partition, |
186 | + filesystem_group=volume_group, created=now, updated=now) |
187 | + number_of_extents, _ = divmod(total_size, LVM_PE_SIZE) |
188 | + lv_size = (number_of_extents - len(partitions)) * LVM_PE_SIZE |
189 | + logical_volume = VirtualBlockDevice.objects.create( |
190 | + node=node, name="lvroot", size=lv_size, block_size=4096, |
191 | + filesystem_group=volume_group, created=now, updated=now) |
192 | + Filesystem.objects.create( |
193 | + block_device=logical_volume, |
194 | + fstype=FILESYSTEM_TYPE.EXT4, |
195 | + label="root", |
196 | + mount_point="/", |
197 | + created=now, |
198 | + updated=now) |
199 | |
200 | === added file 'src/maasserver/models/migrations/tests/test_create_default_storage_layout.py' |
201 | --- src/maasserver/models/migrations/tests/test_create_default_storage_layout.py 1970-01-01 00:00:00 +0000 |
202 | +++ src/maasserver/models/migrations/tests/test_create_default_storage_layout.py 2015-12-02 15:05:52 +0000 |
203 | @@ -0,0 +1,204 @@ |
204 | +# Copyright 2015 Canonical Ltd. This software is licensed under the |
205 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
206 | + |
207 | +"""Tests for the `0181_initial_storage_layouts` migration. |
208 | + |
209 | +WARNING: These tests will become obsolete very quickly, as they are testing |
210 | +migrations against fields that may be removed. When these tests become |
211 | +obsolete, they should be skipped. The tests should be kept until at least |
212 | +the next release cycle (through MAAS 1.9) in case any bugs with this migration |
213 | +occur. |
214 | +""" |
215 | + |
216 | +from __future__ import ( |
217 | + absolute_import, |
218 | + print_function, |
219 | + unicode_literals, |
220 | + ) |
221 | + |
222 | +str = None |
223 | + |
224 | +__metaclass__ = type |
225 | +__all__ = [] |
226 | + |
227 | +from maasserver.enum import ( |
228 | + FILESYSTEM_GROUP_TYPE, |
229 | + FILESYSTEM_TYPE, |
230 | +) |
231 | +from maasserver.models import ( |
232 | + Filesystem, |
233 | + FilesystemGroup, |
234 | + Partition, |
235 | + PartitionTable, |
236 | + PhysicalBlockDevice, |
237 | + VirtualBlockDevice, |
238 | + VolumeGroup, |
239 | +) |
240 | +from maasserver.models.migrations.create_default_storage_layout import ( |
241 | + clear_full_storage_configuration, |
242 | + create_lvm_layout, |
243 | +) |
244 | +from maasserver.testing.factory import factory |
245 | +from maasserver.testing.orm import reload_object |
246 | +from maasserver.testing.testcase import MAASServerTestCase |
247 | +from testtools.matchers import ( |
248 | + Is, |
249 | + MatchesStructure, |
250 | + Not, |
251 | +) |
252 | + |
253 | + |
254 | +class TestClearFullStorageConfigration(MAASServerTestCase): |
255 | + |
256 | + def test__clears_all_objects(self): |
257 | + node = factory.make_Node() |
258 | + physical_block_devices = [ |
259 | + factory.make_PhysicalBlockDevice(node=node, size=10 * 1000 ** 3) |
260 | + for _ in range(3) |
261 | + ] |
262 | + filesystem = factory.make_Filesystem( |
263 | + block_device=physical_block_devices[0]) |
264 | + partition_table = factory.make_PartitionTable( |
265 | + block_device=physical_block_devices[1]) |
266 | + partition = factory.make_Partition(partition_table=partition_table) |
267 | + fslvm = factory.make_Filesystem( |
268 | + block_device=physical_block_devices[2], |
269 | + fstype=FILESYSTEM_TYPE.LVM_PV) |
270 | + vgroup = factory.make_FilesystemGroup( |
271 | + group_type=FILESYSTEM_GROUP_TYPE.LVM_VG, filesystems=[fslvm]) |
272 | + vbd1 = factory.make_VirtualBlockDevice( |
273 | + filesystem_group=vgroup, size=2 * 1000 ** 3) |
274 | + vbd2 = factory.make_VirtualBlockDevice( |
275 | + filesystem_group=vgroup, size=3 * 1000 ** 3) |
276 | + filesystem_on_vbd1 = factory.make_Filesystem( |
277 | + block_device=vbd1, fstype=FILESYSTEM_TYPE.LVM_PV) |
278 | + vgroup_on_vgroup = factory.make_FilesystemGroup( |
279 | + group_type=FILESYSTEM_GROUP_TYPE.LVM_VG, |
280 | + filesystems=[filesystem_on_vbd1]) |
281 | + vbd3_on_vbd1 = factory.make_VirtualBlockDevice( |
282 | + filesystem_group=vgroup_on_vgroup, size=1 * 1000 ** 3) |
283 | + clear_full_storage_configuration( |
284 | + node, |
285 | + PhysicalBlockDevice=PhysicalBlockDevice, |
286 | + VirtualBlockDevice=VirtualBlockDevice, |
287 | + PartitionTable=PartitionTable, |
288 | + Filesystem=Filesystem, |
289 | + FilesystemGroup=FilesystemGroup) |
290 | + for pbd in physical_block_devices: |
291 | + self.expectThat( |
292 | + reload_object(pbd), Not(Is(None)), |
293 | + "Physical block device should not have been deleted.") |
294 | + self.expectThat( |
295 | + reload_object(filesystem), Is(None), |
296 | + "Filesystem should have been removed.") |
297 | + self.expectThat( |
298 | + reload_object(partition_table), Is(None), |
299 | + "PartitionTable should have been removed.") |
300 | + self.expectThat( |
301 | + reload_object(partition), Is(None), |
302 | + "Partition should have been removed.") |
303 | + self.expectThat( |
304 | + reload_object(fslvm), Is(None), |
305 | + "LVM PV Filesystem should have been removed.") |
306 | + self.expectThat( |
307 | + reload_object(vgroup), Is(None), |
308 | + "Volume group should have been removed.") |
309 | + self.expectThat( |
310 | + reload_object(vbd1), Is(None), |
311 | + "Virtual block device should have been removed.") |
312 | + self.expectThat( |
313 | + reload_object(vbd2), Is(None), |
314 | + "Virtual block device should have been removed.") |
315 | + self.expectThat( |
316 | + reload_object(filesystem_on_vbd1), Is(None), |
317 | + "Filesystem on virtual block device should have been removed.") |
318 | + self.expectThat( |
319 | + reload_object(vgroup_on_vgroup), Is(None), |
320 | + "Volume group on virtual block device should have been removed.") |
321 | + self.expectThat( |
322 | + reload_object(vbd3_on_vbd1), Is(None), |
323 | + "Virtual block device on another virtual block device should have " |
324 | + "been removed.") |
325 | + |
326 | + |
327 | +class TestCreateLVMLayout(MAASServerTestCase): |
328 | + |
329 | + def test__creates_layout_for_1TiB_disk(self): |
330 | + node = factory.make_Node(with_boot_disk=False) |
331 | + boot_disk = factory.make_PhysicalBlockDevice( |
332 | + node=node, size=1024 ** 4, block_size=512) |
333 | + create_lvm_layout( |
334 | + node, |
335 | + boot_disk, |
336 | + PartitionTable=PartitionTable, |
337 | + Partition=Partition, |
338 | + Filesystem=Filesystem, |
339 | + FilesystemGroup=FilesystemGroup, |
340 | + VirtualBlockDevice=VirtualBlockDevice) |
341 | + |
342 | + # Validate the volume group on root partition. |
343 | + partition_table = boot_disk.get_partitiontable() |
344 | + partitions = partition_table.partitions.order_by('id').all() |
345 | + root_partition = partitions[0] |
346 | + volume_group = VolumeGroup.objects.get( |
347 | + filesystems__partition=root_partition) |
348 | + self.assertIsNotNone(volume_group) |
349 | + self.assertEquals("vgroot", volume_group.name) |
350 | + |
351 | + # Validate one logical volume on volume group. |
352 | + self.assertEquals( |
353 | + 1, volume_group.virtual_devices.count(), |
354 | + "Should have only 1 logical volume.") |
355 | + logical_volume = volume_group.virtual_devices.first() |
356 | + self.assertEquals(volume_group.get_size(), logical_volume.size) |
357 | + self.assertEquals("lvroot", logical_volume.name) |
358 | + self.assertThat( |
359 | + logical_volume.get_effective_filesystem(), |
360 | + MatchesStructure.byEquality( |
361 | + fstype=FILESYSTEM_TYPE.EXT4, |
362 | + label="root", |
363 | + mount_point="/", |
364 | + )) |
365 | + |
366 | + def test__creates_layout_for_3TiB_disk(self): |
367 | + node = factory.make_Node(with_boot_disk=False) |
368 | + boot_disk = factory.make_PhysicalBlockDevice( |
369 | + node=node, size=3 * (1024 ** 4), block_size=512) |
370 | + create_lvm_layout( |
371 | + node, |
372 | + boot_disk, |
373 | + PartitionTable=PartitionTable, |
374 | + Partition=Partition, |
375 | + Filesystem=Filesystem, |
376 | + FilesystemGroup=FilesystemGroup, |
377 | + VirtualBlockDevice=VirtualBlockDevice) |
378 | + |
379 | + # Validate the volume group on root partition. |
380 | + partition_table = boot_disk.get_partitiontable() |
381 | + partitions = partition_table.partitions.order_by('id').all() |
382 | + root_partition = partitions[0] |
383 | + volume_group = VolumeGroup.objects.get( |
384 | + filesystems__partition=root_partition) |
385 | + self.assertIsNotNone(volume_group) |
386 | + self.assertEquals("vgroot", volume_group.name) |
387 | + |
388 | + # Since its a 3TiB disk it should have 2 partitions in |
389 | + # the volume group. |
390 | + self.assertEquals( |
391 | + 2, volume_group.filesystems.count(), |
392 | + "Should have 2 partitions in volume group.") |
393 | + |
394 | + # Validate one logical volume on volume group. |
395 | + self.assertEquals( |
396 | + 1, volume_group.virtual_devices.count(), |
397 | + "Should have only 1 logical volume.") |
398 | + logical_volume = volume_group.virtual_devices.first() |
399 | + self.assertEquals(volume_group.get_size(), logical_volume.size) |
400 | + self.assertEquals("lvroot", logical_volume.name) |
401 | + self.assertThat( |
402 | + logical_volume.get_effective_filesystem(), |
403 | + MatchesStructure.byEquality( |
404 | + fstype=FILESYSTEM_TYPE.EXT4, |
405 | + label="root", |
406 | + mount_point="/", |
407 | + )) |
Voting does not meet specified criteria. Required: Approve >= 1, Disapprove == 0. Got: 1 Pending.