]> git.ipfire.org Git - ipfire-3.x.git/blob - pkgs/core/pomona/src/storage/partitioning.py
Merge branch 'installer-v2'
[ipfire-3.x.git] / pkgs / core / pomona / src / storage / partitioning.py
1 #!/usr/bin/python
2
3 import os
4 import parted
5
6 from constants import *
7 from errors import *
8
9 import gettext
10 _ = lambda x: gettext.ldgettext("pomona", x)
11
12 def doAutoPartition(installer):
13 if installer.dispatch.dir == DISPATCH_BACK:
14 installer.ds.storage.reset()
15 return
16
17 disks = []
18 devs = []
19
20 if installer.ds.storage.doAutoPart:
21 clearPartitions(installer)
22 (disks, devs) = _createFreeSpacePartitions(installer)
23
24 if disks == []:
25 installer.intf.messageWindow(_("Error Partitioning"),
26 _("Could not find enough free space "
27 "for automatic partitioning, please "
28 "use another partitioning method."))
29 return DISPATCH_BACK
30
31 _schedulePartitions(installer, disks)
32
33 # run the autopart function to allocate and grow partitions
34 try:
35 doPartitioning(installer)
36
37 if installer.ds.storage.doAutoPart:
38 _scheduleLVs(installer, devs)
39
40 # grow LVs
41 growLVM(installer)
42 except PartitioningWarning as msg:
43 installer.intf.messageWindow(_("Warnings During Automatic Partitioning"),
44 _("Following warnings occurred during automatic "
45 "partitioning:\n\n%s") % (msg,),)
46 log.warning(msg)
47 except PartitioningError as msg:
48 # restore drives to original state
49 installer.ds.storage.reset()
50 #installer.dispatch.skipStep("partition", skip = 0)
51 installer.intf.messageWindow(_("Error Partitioning"),
52 _("Could not allocate requested partitions: \n\n"
53 "%s.%s") % (msg, extra))
54 return
55
56 # now do a full check of the requests
57 (errors, warnings) = installer.ds.storage.sanityCheck()
58 if warnings:
59 for warning in warnings:
60 installer.log.warning(warning)
61 if errors:
62 errortxt = "\n".join(errors)
63 installer.intf.messageWindow(_("Automatic Partitioning Errors"),
64 _("The following errors occurred with your "
65 "partitioning:\n\n%s\n\n"
66 "This can happen if there is not enough "
67 "space on your hard drive(s) for the "
68 "installation.\n\n"
69 "Press 'OK' to choose a different partitioning option.")
70 % (errortxt,),)
71
72 installer.ds.storage.reset()
73 #XXX return DISPATCH_BACK
74 return INSTALL_OK
75
76 def doPartitioning(installer):
77 """ Allocate and grow partitions.
78
79 When this function returns without error, all PartitionDevice
80 instances must have their parents set to the disk they are
81 allocated on, and their partedPartition attribute set to the
82 appropriate parted.Partition instance from their containing
83 disk. All req_xxxx attributes must be unchanged.
84
85 Arguments:
86
87 storage - Main anaconda Storage instance
88
89 Keyword arguments:
90
91 exclusiveDisks -- list of names of disks to use
92
93 """
94 storage = installer.ds.storage
95 disks = storage.disks
96
97 exclusiveDisks = storage.clearDisks
98 if exclusiveDisks:
99 disks = [d for d in disks if d.name in exclusiveDisks]
100
101 for disk in disks:
102 disk.setup()
103
104 partitions = storage.partitions
105 for part in partitions:
106 part.req_bootable = False
107 if not part.exists:
108 # start over with flexible-size requests
109 part.req_size = part.req_base_size
110
111 # FIXME: isn't there a better place for this to happen?
112 #try:
113 # bootDev = anaconda.platform.bootDevice()
114 #except DeviceError:
115 # bootDev = None
116
117 #if bootDev:
118 # bootDev.req_bootable = True
119
120 # FIXME: make sure non-existent partitions have empty parents list
121 allocatePartitions(installer, disks, partitions)
122 growPartitions(installer, disks, partitions)
123 # The number and thus the name of partitions may have changed now,
124 # allocatePartitions() takes care of this for new partitions, but not
125 # for pre-existing ones, so we update the name of all partitions here
126 for part in partitions:
127 part.updateName()
128
129 # XXX hack -- if we created any extended partitions we need to add
130 # them to the tree now
131 for disk in disks:
132 extended = disk.partedDisk.getExtendedPartition()
133 if not extended:
134 continue
135
136 extendedName = devicePathToName(extended.getDeviceNodeName())
137 device = storage.devicetree.getDeviceByName(extendedName)
138 if device:
139 if not device.exists:
140 # created by us, update partedPartition
141 device.partedPartition = extended
142 continue
143
144 # This is a little odd because normally instantiating a partition
145 # that does not exist means leaving self.parents empty and instead
146 # populating self.req_disks. In this case, we need to skip past
147 # that since this partition is already defined.
148 device = PartitionDevice(extendedName, parents=disk)
149 device.parents = [disk]
150 device.partedPartition = extended
151 storage.createDevice(device)
152
153 def clearPartitions(installer):
154 """ Clear partitions and dependent devices from disks.
155
156 Arguments:
157
158 storage -- a storage.Storage instance
159
160 Keyword arguments:
161
162 None
163
164 NOTES:
165
166 - Needs some error handling, especially for the parted bits.
167
168 """
169 storage = installer.ds.storage
170
171 # we are only interested in partitions that physically exist
172 partitions = [p for p in storage.partitions if p.exists]
173 disks = [] # a list of disks from which we've removed partitions
174 clearparts = [] # list of partitions we'll remove
175 for part in partitions:
176 # if we got a list of disks to clear, make sure this one's on it
177 if storage.clearDisks and part.disk.name not in storage.clearDisks:
178 continue
179
180 # don't clear partitions holding install media
181 #if part.name in storage.protectedPartitions:
182 # continue
183
184 # we don't want to fool with extended partitions, freespace
185 if part.partType not in (parted.PARTITION_NORMAL, parted.PARTITION_LOGICAL):
186 continue
187
188 # XXX is there any argument for not removing incomplete devices?
189 # -- maybe some RAID devices
190 devices = storage.deviceDeps(part)
191 while devices:
192 installer.log.debug("Devices to remove: %s" % ([d.name for d in devices],))
193 leaves = [d for d in devices if d.isleaf]
194 installer.log.debug("Leaves to remove: %s" % ([d.name for d in leaves],))
195 for leaf in leaves:
196 storage.destroyDevice(leaf)
197 devices.remove(leaf)
198
199 #installer.log.debug("Partitions left: %s" % [p.getDeviceNodeName() for p in part.partedPartition.disk.partitions])
200 disk_name = os.path.basename(part.partedPartition.disk.device.path)
201 if disk_name not in disks:
202 disks.append(disk_name)
203
204 clearparts.append(part)
205
206 for part in clearparts:
207 storage.destroyDevice(part)
208
209 # now remove any empty extended partitions
210 removeEmptyExtendedPartitions(installer)
211
212 def removeEmptyExtendedPartitions(installer):
213 storage = installer.ds.storage
214 for disk in storage.disks:
215 #installer.log.debug("Checking whether disk %s has an empty extended" % disk.name)
216 extended = disk.partedDisk.getExtendedPartition()
217 logical_parts = disk.partedDisk.getLogicalPartitions()
218 #installer.log.debug("Extended is %s ; logicals is %s" % (extended, [p.getDeviceNodeName() for p in logical_parts]))
219 if extended and not logical_parts:
220 installer.log.debug("Removing empty extended partition from %s" % disk.name)
221 extended_name = devicePathToName(extended.getDeviceNodeName())
222 extended = storage.devicetree.getDeviceByName(extended_name)
223 storage.destroyDevice(extended)
224 #disk.partedDisk.removePartition(extended.partedPartition)
225
226 def _createFreeSpacePartitions(installer):
227 # get a list of disks that have at least one free space region of at
228 # least 100MB
229 disks = []
230 for disk in installer.ds.storage.disks:
231 if disk.name not in installer.ds.storage.clearDisks:
232 continue
233
234 partedDisk = disk.partedDisk
235 part = disk.partedDisk.getFirstPartition()
236 while part:
237 if not part.type & parted.PARTITION_FREESPACE:
238 part = part.nextPartition()
239 continue
240
241 if part.getSize(unit="MB") > 100:
242 disks.append(disk)
243 break
244
245 part = part.nextPartition()
246
247 # create a separate pv partition for each disk with free space
248 devs = []
249 for disk in disks:
250 if installer.ds.storage.encryptedAutoPart:
251 fmt_type = "luks"
252 else:
253 fmt_type = "lvmpv"
254 part = installer.ds.storage.newPartition(fmt_type=fmt_type,
255 size=1, grow=True,
256 disks=[disk])
257 installer.ds.storage.createDevice(part)
258 devs.append(part)
259
260 return (disks, devs)
261
262 def _schedulePartitions(installer, disks):
263 #
264 # Convert storage.autoPartitionRequests into Device instances and
265 # schedule them for creation
266 #
267 # First pass is for partitions only. We'll do LVs later.
268 #
269 for request in installer.ds.storage.autoPartitionRequests:
270 if request.asVol:
271 continue
272
273 if request.fstype is None:
274 request.fstype = installer.ds.storage.defaultFSType
275
276 dev = installer.ds.storage.newPartition(fmt_type=request.fstype,
277 size=request.size,
278 grow=request.grow,
279 maxsize=request.maxSize,
280 mountpoint=request.mountpoint,
281 disks=disks,
282 weight=request.weight)
283
284 # schedule the device for creation
285 installer.ds.storage.createDevice(dev)
286
287 # make sure preexisting broken lvm/raid configs get out of the way
288 return
289
290 def allocatePartitions(installer, disks, partitions):
291 """ Allocate partitions based on requested features.
292
293 Non-existing partitions are sorted according to their requested
294 attributes, and then allocated.
295
296 The basic approach to sorting is that the more specifically-
297 defined a request is, the earlier it will be allocated. See
298 the function partitionCompare for details on the sorting
299 criteria.
300
301 The PartitionDevice instances will have their name and parents
302 attributes set once they have been allocated.
303 """
304 #installer.log.debug("disks=%s ; partitions=%s" % (disks, partitions))
305 new_partitions = [p for p in partitions if not p.exists]
306 new_partitions.sort(cmp=partitionCompare)
307
308 # XXX is this needed anymore?
309 partedDisks = {}
310 for disk in disks:
311 if disk.path not in partedDisks.keys():
312 partedDisks[disk.path] = disk.partedDisk #.duplicate()
313
314 # remove all newly added partitions from the disk
315 installer.log.debug("Removing all non-preexisting from disk(s)")
316 for _part in new_partitions:
317 if _part.partedPartition:
318 if _part.isExtended:
319 continue # these get removed last
320 #_part.disk.partedDisk.removePartition(_part.partedPartition)
321 partedDisk = partedDisks[_part.disk.partedDisk.device.path]
322 installer.log.debug("Removing part %s (%s) from disk %s (%s)" %
323 (_part.partedPartition.path,
324 [p.path for p in _part.partedPartition.disk.partitions],
325 partedDisk.device.path,
326 [p.path for p in partedDisk.partitions]))
327
328 partedDisk.removePartition(_part.partedPartition)
329 _part.partedPartition = None
330 _part.disk = None
331
332 # remove empty extended so it doesn't interfere
333 extended = partedDisk.getExtendedPartition()
334 if extended and not partedDisk.getLogicalPartitions():
335 installer.log.debug("Removing empty extended partition")
336 #partedDisk.minimizeExtendedPartition()
337 partedDisk.removePartition(extended)
338
339 for _part in new_partitions:
340 if _part.partedPartition and _part.isExtended:
341 # ignore new extendeds as they are implicit requests
342 continue
343
344 # obtain the set of candidate disks
345 req_disks = []
346 if _part.disk:
347 # we have a already selected a disk for this request
348 req_disks = [_part.disk]
349 elif _part.req_disks:
350 # use the requested disk set
351 req_disks = _part.req_disks
352 else:
353 # no disks specified means any disk will do
354 req_disks = disks
355
356 #installer.log.debug("allocating partition: %s ; disks: %s ; boot: %s ; "
357 # "primary: %s ; size: %dMB ; grow: %s ; max_size: %s" %
358 # (_part.name, req_disks, _part.req_bootable, _part.req_primary,
359 # _part.req_size, _part.req_grow, _part.req_max_size))
360 free = None
361 use_disk = None
362 part_type = None
363 # loop through disks
364 for _disk in req_disks:
365 disk = partedDisks[_disk.path]
366 #for p in disk.partitions:
367 # installer.log.debug("disk %s: part %s" % (disk.device.path, p.path))
368 sectorSize = disk.device.physicalSectorSize
369 best = None
370
371 #installer.log.debug("Checking freespace on %s" % _disk.name)
372
373 new_part_type = getNextPartitionType(disk)
374 if new_part_type is None:
375 # can't allocate any more partitions on this disk
376 installer.log.debug("No free partition slots on %s" % _disk.name)
377 continue
378
379 if _part.req_primary and new_part_type != parted.PARTITION_NORMAL:
380 # we need a primary slot and none are free on this disk
381 installer.log.debug("No primary slots available on %s" % _disk.name)
382 continue
383
384 best = getBestFreeSpaceRegion(installer, disk,
385 new_part_type,
386 _part.req_size,
387 best_free=free,
388 boot=_part.req_bootable)
389
390 if best == free and not _part.req_primary and \
391 new_part_type == parted.PARTITION_NORMAL:
392 # see if we can do better with a logical partition
393 installer.log.debug("Not enough free space for primary -- trying logical")
394 new_part_type = getNextPartitionType(disk, no_primary=True)
395 if new_part_type:
396 best = getBestFreeSpaceRegion(disk,
397 new_part_type,
398 _part.req_size,
399 best_free=free,
400 boot=_part.req_bootable)
401
402 if best and free != best:
403 # now we know we are choosing a new free space,
404 # so update the disk and part type
405 #installer.log.debug("Updating use_disk to %s (%s), type: %s"
406 # % (_disk, _disk.name, new_part_type))
407 part_type = new_part_type
408 use_disk = _disk
409 installer.log.debug("New free: %s (%d-%d / %dMB)" % (best.device.path,
410 best.start,
411 best.end,
412 best.getSize()))
413 free = best
414
415 if free and _part.req_bootable:
416 # if this is a bootable partition we want to
417 # use the first freespace region large enough
418 # to satisfy the request
419 installer.log.debug("Found free space for bootable request")
420 break
421
422 if free is None:
423 raise PartitioningError("Not enough free space on disks")
424
425 _disk = use_disk
426 disk = _disk.partedDisk
427
428 # create the extended partition if needed
429 # TODO: move to a function (disk, free)
430 if part_type == parted.PARTITION_EXTENDED:
431 installer.log.debug("Creating extended partition")
432 geometry = parted.Geometry(device=disk.device,
433 start=free.start,
434 length=free.length,
435 end=free.end)
436 extended = parted.Partition(disk=disk,
437 type=parted.PARTITION_EXTENDED,
438 geometry=geometry)
439 constraint = parted.Constraint(device=disk.device)
440 # FIXME: we should add this to the tree as well
441 disk.addPartition(extended, constraint)
442
443 # end proposed function
444
445 # now the extended partition exists, so set type to logical
446 part_type = parted.PARTITION_LOGICAL
447
448 # recalculate freespace
449 installer.log.debug("Recalculating free space")
450 free = getBestFreeSpaceRegion(disk,
451 part_type,
452 _part.req_size,
453 boot=_part.req_bootable)
454 if not free:
455 raise PartitioningError("Not enough free space after "
456 "creating extended partition")
457
458 # create minimum geometry for this request
459 # req_size is in MB
460 sectors_per_track = disk.device.biosGeometry[2]
461 length = (_part.req_size * (1024 * 1024)) / sectorSize
462 new_geom = parted.Geometry(device=disk.device,
463 start=max(sectors_per_track, free.start),
464 length=length)
465
466 # create maximum and minimum geometries for constraint
467 start = max(0 , free.start - 1)
468 max_geom = parted.Geometry(device=disk.device,
469 start=start,
470 length=min(length + 1, disk.device.length - start))
471 min_geom = parted.Geometry(device=disk.device,
472 start=free.start + 1,
473 length=length-1)
474
475
476 # create the partition and add it to the disk
477 partition = parted.Partition(disk=disk,
478 type=part_type,
479 geometry=new_geom)
480 constraint = parted.Constraint(maxGeom=max_geom, minGeom=min_geom)
481 disk.addPartition(partition=partition,
482 constraint=constraint)
483 installer.log.debug("Created partition %s of %dMB and added it to %s" %
484 (partition.getDeviceNodeName(), partition.getSize(), disk.device.path))
485
486 # this one sets the name
487 _part.partedPartition = partition
488 _part.disk = _disk
489
490 # parted modifies the partition in the process of adding it to
491 # the disk, so we need to grab the latest version...
492 _part.partedPartition = disk.getPartitionByPath(_part.path)
493
494 def growPartitions(installer, disks, partitions):
495 """ Grow all growable partition requests.
496
497 All requests should know what disk they will be on by the time
498 this function is called. This is reflected in the
499 PartitionDevice's disk attribute. Note that the req_disks
500 attribute remains unchanged.
501
502 The total available free space is summed up for each disk and
503 partition requests are allocated a maximum percentage of the
504 available free space on their disk based on their own base size.
505
506 Each attempted size means calling allocatePartitions again with
507 one request's size having changed.
508
509 After taking into account several factors that may limit the
510 maximum size of a requested partition, we arrive at a firm
511 maximum number of sectors by which a request can potentially grow.
512
513 An initial attempt is made to allocate the full maximum size. If
514 this fails, we begin a rough binary search with a maximum of three
515 iterations to settle on a new size.
516
517 Arguments:
518
519 disks -- a list of all usable disks (DiskDevice instances)
520 partitions -- a list of all partitions (PartitionDevice
521 instances)
522 """
523 #installer.log.debug("growPartitions: disks=%s, partitions=%s" %
524 # ([d.name for d in disks], [p.name for p in partitions]))
525 all_growable = [p for p in partitions if p.req_grow]
526 if not all_growable:
527 return
528
529 # sort requests by base size in decreasing order
530 all_growable.sort(key=lambda p: p.req_size, reverse=True)
531
532 installer.log.debug("Growable requests are %s" % [p.name for p in all_growable])
533
534 for disk in disks:
535 installer.log.debug("Growing requests on %s" % disk.name)
536 for p in disk.partedDisk.partitions:
537 installer.log.debug(" %s: %s (%dMB)" % (disk.name, p.getDeviceNodeName(),
538 p.getSize()))
539 sectorSize = disk.partedDisk.device.physicalSectorSize
540 # get a list of free space regions on the disk
541 free = disk.partedDisk.getFreeSpaceRegions()
542 if not free:
543 installer.log.debug("No free space on %s" % disk.name)
544 continue
545
546 # sort the free regions in decreasing order of size
547 free.sort(key=lambda r: r.length, reverse=True)
548 disk_free = reduce(lambda x,y: x + y, [f.length for f in free])
549 installer.log.debug("Total free: %d sectors ; largest: %d sectors (%dMB)"
550 % (disk_free, free[0].length, free[0].getSize()))
551
552 # make a list of partitions currently allocated on this disk
553 # -- they're already sorted
554 growable = []
555 disk_total = 0
556 for part in all_growable:
557 #log.debug("checking if part %s (%s) is on this disk" % (part.name,
558 # part.disk.name))
559 if part.disk == disk:
560 growable.append(part)
561 disk_total += part.partedPartition.geometry.length
562 installer.log.debug("Add %s (%dMB/%d sectors) to growable total"
563 % (part.name, part.partedPartition.getSize(),
564 part.partedPartition.geometry.length))
565 installer.log.debug("Growable total is now %d sectors" % disk_total)
566
567 # now we loop through the partitions...
568 # this first loop is to identify obvious chunks of free space that
569 # will be left over due to max size
570 leftover = 0
571 limited = {}
572 unlimited_total = 0
573 for part in growable:
574 # calculate max number of sectors this request can grow
575 req_sectors = part.partedPartition.geometry.length
576 share = float(req_sectors) / float(disk_total)
577 max_grow = (share * disk_free)
578 max_sectors = req_sectors + max_grow
579 limited[part.name] = False
580
581 if part.req_max_size:
582 req_max_sect = (part.req_max_size * (1024 * 1024)) / sectorSize
583 if req_max_sect < max_sectors:
584 mb = ((max_sectors - req_max_sect) * sectorSize) / (1024*1024)
585
586 installer.log.debug("Adding %dMB to leftovers from %s"
587 % (mb, part.name))
588 leftover += (max_sectors - req_max_sect)
589 limited[part.name] = True
590
591 if not limited[part.name]:
592 unlimited_total += req_sectors
593
594 # now we loop through the partitions...
595 for part in growable:
596 # calculate max number of sectors this request can grow
597 req_sectors = part.partedPartition.geometry.length
598 share = float(req_sectors) / float(disk_total)
599 max_grow = (share * disk_free)
600 if not limited[part.name]:
601 leftover_share = float(req_sectors) / float(unlimited_total)
602 max_grow += leftover_share * leftover
603 max_sectors = req_sectors + max_grow
604 max_mb = (max_sectors * sectorSize) / (1024 * 1024)
605
606 installer.log.debug("%s: base_size=%dMB, max_size=%sMB" %
607 (part.name, part.req_base_size, part.req_max_size))
608 installer.log.debug("%s: current_size=%dMB (%d sectors)" %
609 (part.name, part.partedPartition.getSize(),
610 part.partedPartition.geometry.length))
611 installer.log.debug("%s: %dMB (%d sectors, or %d%% of %d)" %
612 (part.name, max_mb, max_sectors, share * 100, disk_free))
613
614 installer.log.debug("Checking constraints on max size...")
615 # don't grow beyond the request's maximum size
616 if part.req_max_size:
617 installer.log.debug("max_size: %dMB" % part.req_max_size)
618 # FIXME: round down to nearest cylinder boundary
619 req_max_sect = (part.req_max_size * (1024 * 1024)) / sectorSize
620 if req_max_sect < max_sectors:
621 max_grow -= (max_sectors - req_max_sect)
622 max_sectors = req_sectors + max_grow
623
624 # don't grow beyond the resident filesystem's max size
625 if part.format.maxSize > 0:
626 installer.log.debug("Format maxsize: %dMB" % part.format.maxSize)
627 # FIXME: round down to nearest cylinder boundary
628 fs_max_sect = (part.format.maxSize * (1024 * 1024)) / sectorSize
629 if fs_max_sect < max_sectors:
630 max_grow -= (max_sectors - fs_max_sect)
631 max_sectors = req_sectors + max_grow
632
633 # we can only grow as much as the largest free region on the disk
634 if free[0].length < max_grow:
635 installer.log.debug("Largest free region: %d sectors (%dMB)" %
636 (free[0].length, free[0].getSize()))
637 # FIXME: round down to nearest cylinder boundary
638 max_grow = free[0].length
639 max_sectors = req_sectors + max_grow
640
641 # Now, we try to grow this partition as close to max_grow
642 # sectors as we can.
643 #
644 # We could call allocatePartitions after modifying this
645 # request and saving the original value of part.req_size,
646 # or we could try to use disk.maximizePartition().
647 max_size = (max_sectors * sectorSize) / (1024 * 1024)
648 orig_size = part.req_size
649 # try the max size to begin with
650 installer.log.debug("Attempting to allocate maximum size: %dMB" % max_size)
651 part.req_size = max_size
652 try:
653 allocatePartitions(installer, disks, partitions)
654 except PartitioningError, e:
655 installer.log.debug("Max size attempt failed: %s (%dMB)" % (part.name,
656 max_size))
657 part.req_size = orig_size
658 else:
659 continue
660
661 installer.log.debug("Starting binary search: size=%d max_size=%d" % (part.req_size, max_size))
662 count = 0
663 op_func = add
664 increment = max_grow
665 last_good_size = part.req_size
666 last_outcome = None
667 while count < 3:
668 last_size = part.req_size
669 increment /= 2
670 req_sectors = op_func(req_sectors, increment)
671 part.req_size = (req_sectors * sectorSize) / (1024 * 1024)
672 installer.log.debug("Attempting size=%dMB" % part.req_size)
673 count += 1
674 try:
675 allocatePartitions(disks, partitions)
676 except PartitioningError, e:
677 installer.log.debug("Attempt at %dMB failed" % part.req_size)
678 op_func = sub
679 last_outcome = False
680 else:
681 op_func = add
682 last_good_size = part.req_size
683 last_outcome = True
684
685 if not last_outcome:
686 part.req_size = last_good_size
687 installer.log.debug("Backing up to size=%dMB" % part.req_size)
688 try:
689 allocatePartitions(disks, partitions)
690 except PartitioningError, e:
691 raise PartitioningError("Failed to grow partitions")
692
693 # reset all requests to their original requested size
694 for part in partitions:
695 if part.exists:
696 continue
697 part.req_size = part.req_base_size
698
699 def growLVM(installer):
700 """ Grow LVs according to the sizes of the PVs. """
701 storage = installer.ds.storage
702 for vg in storage.vgs:
703 total_free = vg.freeSpace
704 if not total_free:
705 installer.log.debug("vg %s has no free space" % vg.name)
706 continue
707
708 installer.log.debug("vg %s: %dMB free ; lvs: %s" % (vg.name, vg.freeSpace,
709 [l.lvname for l in vg.lvs]))
710
711 # figure out how much to grow each LV
712 grow_amounts = {}
713 lv_total = vg.size - total_free
714 installer.log.debug("used: %dMB ; vg.size: %dMB" % (lv_total, vg.size))
715
716 # This first loop is to calculate percentage-based growth
717 # amounts. These are based on total free space.
718 lvs = vg.lvs
719 lvs.sort(cmp=lvCompare)
720 for lv in lvs:
721 if not lv.req_grow or not lv.req_percent:
722 continue
723
724 portion = (lv.req_percent * 0.01)
725 grow = portion * vg.vgFree
726 new_size = lv.req_size + grow
727 if lv.req_max_size and new_size > lv.req_max_size:
728 grow -= (new_size - lv.req_max_size)
729
730 if lv.format.maxSize and lv.format.maxSize < new_size:
731 grow -= (new_size - lv.format.maxSize)
732
733 # clamp growth amount to a multiple of vg extent size
734 grow_amounts[lv.name] = vg.align(grow)
735 total_free -= grow
736 lv_total += grow
737
738 # This second loop is to calculate non-percentage-based growth
739 # amounts. These are based on free space remaining after
740 # calculating percentage-based growth amounts.
741
742 # keep a tab on space not allocated due to format or requested
743 # maximums -- we'll dole it out to subsequent requests
744 leftover = 0
745 for lv in lvs:
746 installer.log.debug("Checking lv %s: req_grow: %s ; req_percent: %s"
747 % (lv.name, lv.req_grow, lv.req_percent))
748 if not lv.req_grow or lv.req_percent:
749 continue
750
751 portion = float(lv.req_size) / float(lv_total)
752 grow = portion * total_free
753 installer.log.debug("grow is %dMB" % grow)
754
755 todo = lvs[lvs.index(lv):]
756 unallocated = reduce(lambda x,y: x+y,
757 [l.req_size for l in todo
758 if l.req_grow and not l.req_percent])
759 extra_portion = float(lv.req_size) / float(unallocated)
760 extra = extra_portion * leftover
761 installer.log.debug("%s getting %dMB (%d%%) of %dMB leftover space"
762 % (lv.name, extra, extra_portion * 100, leftover))
763 leftover -= extra
764 grow += extra
765 installer.log.debug("grow is now %dMB" % grow)
766 max_size = lv.req_size + grow
767 if lv.req_max_size and max_size > lv.req_max_size:
768 max_size = lv.req_max_size
769
770 if lv.format.maxSize and max_size > lv.format.maxSize:
771 max_size = lv.format.maxSize
772
773 installer.log.debug("max size is %dMB" % max_size)
774 max_size = max_size
775 leftover += (lv.req_size + grow) - max_size
776 grow = max_size - lv.req_size
777 installer.log.debug("lv %s gets %dMB" % (lv.name, vg.align(grow)))
778 grow_amounts[lv.name] = vg.align(grow)
779
780 if not grow_amounts:
781 installer.log.debug("No growable lvs in vg %s" % vg.name)
782 continue
783
784 # now grow the lvs by the amounts we've calculated above
785 for lv in lvs:
786 if lv.name not in grow_amounts.keys():
787 continue
788 lv.size += grow_amounts[lv.name]
789
790 # now there shouldn't be any free space left, but if there is we
791 # should allocate it to one of the LVs
792 vg_free = vg.freeSpace
793 installer.log.debug("vg %s has %dMB free" % (vg.name, vg_free))
794 if vg_free:
795 for lv in lvs:
796 if not lv.req_grow:
797 continue
798
799 if lv.req_max_size and lv.size == lv.req_max_size:
800 continue
801
802 if lv.format.maxSize and lv.size == lv.format.maxSize:
803 continue
804
805 # first come, first served
806 projected = lv.size + vg.freeSpace
807 if lv.req_max_size and projected > lv.req_max_size:
808 projected = lv.req_max_size
809
810 if lv.format.maxSize and projected > lv.format.maxSize:
811 projected = lv.format.maxSize
812
813 installer.log.debug("Giving leftover %dMB to %s" % (projected - lv.size,
814 lv.name))
815 lv.size = projected
816
817 def partitionCompare(part1, part2):
818 """ More specifically defined partitions come first.
819
820 < 1 => x < y
821 0 => x == y
822 > 1 => x > y
823 """
824 ret = 0
825
826 if part1.req_base_weight:
827 ret -= part1.req_base_weight
828
829 if part2.req_base_weight:
830 ret += part2.req_base_weight
831
832 # bootable partitions to the front
833 ret -= cmp(part1.req_bootable, part2.req_bootable) * 1000
834
835 # more specific disk specs to the front of the list
836 ret += cmp(len(part1.parents), len(part2.parents)) * 500
837
838 # primary-only to the front of the list
839 ret -= cmp(part1.req_primary, part2.req_primary) * 200
840
841 # larger requests go to the front of the list
842 ret -= cmp(part1.size, part2.size) * 100
843
844 # fixed size requests to the front
845 ret += cmp(part1.req_grow, part2.req_grow) * 50
846
847 # potentially larger growable requests go to the front
848 if part1.req_grow and part2.req_grow:
849 if not part1.req_max_size and part2.req_max_size:
850 ret -= 25
851 elif part1.req_max_size and not part2.req_max_size:
852 ret += 25
853 else:
854 ret -= cmp(part1.req_max_size, part2.req_max_size) * 25
855
856 if ret > 0:
857 ret = 1
858 elif ret < 0:
859 ret = -1
860
861 return ret
862
863 def lvCompare(lv1, lv2):
864 """ More specifically defined lvs come first.
865
866 < 1 => x < y
867 0 => x == y
868 > 1 => x > y
869 """
870 ret = 0
871
872 # larger requests go to the front of the list
873 ret -= cmp(lv1.size, lv2.size) * 100
874
875 # fixed size requests to the front
876 ret += cmp(lv1.req_grow, lv2.req_grow) * 50
877
878 # potentially larger growable requests go to the front
879 if lv1.req_grow and lv2.req_grow:
880 if not lv1.req_max_size and lv2.req_max_size:
881 ret -= 25
882 elif lv1.req_max_size and not lv2.req_max_size:
883 ret += 25
884 else:
885 ret -= cmp(lv1.req_max_size, lv2.req_max_size) * 25
886
887 if ret > 0:
888 ret = 1
889 elif ret < 0:
890 ret = -1
891
892 return ret
893
894 def getNextPartitionType(disk, no_primary=None):
895 """ Find the type of partition to create next on a disk.
896
897 Return a parted partition type value representing the type of the
898 next partition we will create on this disk.
899
900 If there is only one free primary partition and we can create an
901 extended partition, we do that.
902
903 If there are free primary slots and an extended partition we will
904 recommend creating a primary partition. This can be overridden
905 with the keyword argument no_primary.
906
907 Arguments:
908
909 disk -- a parted.Disk instance representing the disk
910
911 Keyword arguments:
912
913 no_primary -- given a choice between primary and logical
914 partitions, prefer logical
915
916 """
917 part_type = None
918 extended = disk.getExtendedPartition()
919 supports_extended = disk.supportsFeature(parted.DISK_TYPE_EXTENDED)
920 logical_count = len(disk.getLogicalPartitions())
921 max_logicals = disk.getMaxLogicalPartitions()
922 primary_count = disk.primaryPartitionCount
923
924 if primary_count == disk.maxPrimaryPartitionCount and \
925 extended and logical_count < max_logicals:
926 part_type = parted.PARTITION_LOGICAL
927 elif primary_count == (disk.maxPrimaryPartitionCount - 1) and \
928 not extended and supports_extended:
929 # last chance to create an extended partition
930 part_type = parted.PARTITION_EXTENDED
931 elif no_primary and extended and logical_count < max_logicals:
932 # create a logical even though we could presumably create a
933 # primary instead
934 part_type = parted.PARTITION_LOGICAL
935 elif not no_primary:
936 # XXX there is a possiblity that the only remaining free space on
937 # the disk lies within the extended partition, but we will
938 # try to create a primary first
939 part_type = parted.PARTITION_NORMAL
940
941 return part_type
942
943 def getBestFreeSpaceRegion(installer, disk, part_type, req_size,
944 boot=None, best_free=None):
945 """ Return the "best" free region on the specified disk.
946
947 For non-boot partitions, we return the largest free region on the
948 disk. For boot partitions, we return the first region that is
949 large enough to hold the partition.
950
951 Partition type (parted's PARTITION_NORMAL, PARTITION_LOGICAL) is
952 taken into account when locating a suitable free region.
953
954 For locating the best region from among several disks, the keyword
955 argument best_free allows the specification of a current "best"
956 free region with which to compare the best from this disk. The
957 overall best region is returned.
958
959 Arguments:
960
961 disk -- the disk (a parted.Disk instance)
962 part_type -- the type of partition we want to allocate
963 (one of parted's partition type constants)
964 req_size -- the requested size of the partition (in MB)
965
966 Keyword arguments:
967
968 boot -- indicates whether this will be a bootable partition
969 (boolean)
970 best_free -- current best free region for this partition
971
972 """
973 #installer.log.debug("getBestFreeSpaceRegion: disk=%s part_type=%d req_size=%dMB boot=%s best=%s" %
974 # (disk.device.path, part_type, req_size, boot, best_free))
975 extended = disk.getExtendedPartition()
976 for _range in disk.getFreeSpaceRegions():
977 if extended:
978 # find out if there is any overlap between this region and the
979 # extended partition
980 installer.log.debug("Looking for intersection between extended (%d-%d) and free (%d-%d)" %
981 (extended.geometry.start, extended.geometry.end, _range.start, _range.end))
982
983 # parted.Geometry.overlapsWith can handle this
984 try:
985 free_geom = extended.geometry.intersect(_range)
986 except ArithmeticError, e:
987 # this freespace region does not lie within the extended
988 # partition's geometry
989 free_geom = None
990
991 if (free_geom and part_type == parted.PARTITION_NORMAL) or \
992 (not free_geom and part_type == parted.PARTITION_LOGICAL):
993 installer.log.debug("Free region not suitable for request")
994 continue
995
996 if part_type == parted.PARTITION_NORMAL:
997 # we're allocating a primary and the region is not within
998 # the extended, so we use the original region
999 free_geom = _range
1000 else:
1001 free_geom = _range
1002
1003 installer.log.debug("Current free range on %s is %d-%d (%dMB)" % (disk.device.path,
1004 free_geom.start,
1005 free_geom.end,
1006 free_geom.getSize()))
1007 free_size = free_geom.getSize()
1008
1009 if req_size <= free_size:
1010 if not best_free or free_geom.length > best_free.length:
1011 best_free = free_geom
1012
1013 if boot:
1014 # if this is a bootable partition we want to
1015 # use the first freespace region large enough
1016 # to satisfy the request
1017 break
1018
1019 return best_free
1020
1021 def _scheduleLVs(installer, devs):
1022 if installer.ds.storage.encryptedAutoPart:
1023 pvs = []
1024 for dev in devs:
1025 pv = LUKSDevice("luks-%s" % dev.name,
1026 format=getFormat("lvmpv", device=dev.path),
1027 size=dev.size,
1028 parents=dev)
1029 pvs.append(pv)
1030 installer.ds.storage.createDevice(pv)
1031 else:
1032 pvs = devs
1033
1034 # create a vg containing all of the autopart pvs
1035 vg = installer.ds.storage.newVG(pvs=pvs)
1036 installer.ds.storage.createDevice(vg)
1037
1038 #
1039 # Convert storage.autoPartitionRequests into Device instances and
1040 # schedule them for creation.
1041 #
1042 # Second pass, for LVs only.
1043 for request in installer.ds.storage.autoPartitionRequests:
1044 if not request.asVol:
1045 continue
1046
1047 if request.fstype is None:
1048 request.fstype = installer.ds.storage.defaultFSType
1049
1050 # FIXME: move this to a function and handle exceptions
1051 dev = installer.ds.storage.newLV(vg=vg,
1052 fmt_type=request.fstype,
1053 mountpoint=request.mountpoint,
1054 grow=request.grow,
1055 maxsize=request.maxSize,
1056 size=request.size)
1057
1058 # schedule the device for creation
1059 installer.ds.storage.createDevice(dev)