-
Notifications
You must be signed in to change notification settings - Fork 19
/
autoinst_devices_creator.rb
240 lines (216 loc) · 10.8 KB
/
autoinst_devices_creator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#!/usr/bin/env ruby
#
# encoding: utf-8
# Copyright (c) [2017] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.
require "y2storage/proposal/partitions_distribution_calculator"
require "y2storage/proposal/partition_creator"
require "y2storage/proposal/md_creator"
require "y2storage/proposal/autoinst_creator_result"
require "y2storage/exceptions"
module Y2Storage
module Proposal
# Class to create and reuse devices during the AutoYaST proposal, based
# on the information contained in the profile.
#
# ## Comparison with the guided proposal
#
# This class receives a devicegraph in which the previous devices have
# already been deleted or resized according to the AutoYaST profile. This
# is different from the guided setup equivalent step, in which the minimal
# amount of existing devices are deleted/resized on demand while trying to
# allocate the planned devices.
#
# ## Reducing planned devices when there is not enough space
#
# Another key difference with the guided proposal is that, when there is
# not enough space (for partitions or logical volumes), it will do a second
# attempt reducing all planned devices proportionally. In order to do so,
# it will remove the min_size limit (setting it to just 1 byte) and,
# additionally, it will set a proportional weight for every partition (see
# {#flexible_devices}).
#
# Although this approach may not produce the optimal results, it is less
# intrusive and easier to maintain than other alternatives. Bear in mind
# that AutoYaST does not expect complex scenarios (like multiple disks with
# several gaps), so the result should be good enough.
#
# If we were aiming for the optimal devices distribution, we should look at
# {Y2Storage::Planned::PartitionsDistribution#assigned_space} and follow
# the same approach (reducing min_size and setting a proportional weight)
# when it is not possible to place the devices in the given free space. But
# we would also need to do further changes, like skipping some checks when
# running in this flexible mode.
class AutoinstDevicesCreator
include Yast::Logger
# Constructor
#
# @param original_graph [Devicegraph] Devicegraph to be used as starting point
def initialize(original_graph)
@original_graph = original_graph
end
# Devicegraph including all the specified planned devices
#
# @param planned_devices [Array<Planned::Device>] Devices to create/reuse
# @param disk_names [Array<String>] Disks to consider
# @return [Devicegraph] New devicegraph in which all the planned devices have been allocated
def populated_devicegraph(planned_devices, disk_names)
# Process planned partitions
planned_partitions = planned_devices.select { |d| d.is_a?(Planned::Partition) }
parts_to_reuse, parts_to_create = planned_partitions.partition(&:reuse?)
creator_result = create_partitions(parts_to_create, disk_names)
reuse_devices(parts_to_reuse, creator_result.devicegraph)
# Process planned stray block devices (Xen virtual partitions)
process_stray_devs(planned_devices, creator_result.devicegraph)
# Process planned MD arrays
planned_mds = planned_devices.select { |d| d.is_a?(Planned::Md) }
mds_to_reuse, mds_to_create = planned_mds.partition(&:reuse?)
creator_result.merge!(create_mds(mds_to_create, creator_result, parts_to_reuse))
mds_to_reuse.each { |i| i.reuse!(creator_result.devicegraph) }
# Process planned volume groups
planned_vgs = planned_devices.select { |d| d.is_a?(Planned::LvmVg) }
creator_result.merge!(set_up_lvm(planned_vgs, creator_result, parts_to_reuse))
vgs_to_reuse = planned_vgs.select(&:reuse?)
reuse_vgs(vgs_to_reuse, creator_result.devicegraph)
Y2Storage::Proposal::AutoinstCreatorResult.new(
creator_result, parts_to_create + mds_to_create + planned_vgs
)
end
protected
# @return [Devicegraph] Original devicegraph
attr_reader :original_graph
# Finds the best distribution for the given planned partitions
#
# @param planned_partitions [Array<Planned::Partition>] Partitions to add
# @param disk_names [Array<String>] Names of disks to consider
#
# @see Proposal::PartitionsDistributionCalculator#best_distribution
def best_distribution(planned_partitions, disk_names)
disks = original_graph.disk_devices.select { |d| disk_names.include?(d.name) }
spaces = disks.map(&:free_spaces).flatten
calculator = Proposal::PartitionsDistributionCalculator.new
dist = calculator.best_distribution(planned_partitions, spaces)
return dist if dist
# Second try with more flexible planned partitions
calculator.best_distribution(flexible_devices(planned_partitions), spaces)
end
private
# Creates planned partitions in the given devicegraph
#
# @param new_partitions [Array<Planned::Partition>] Devices to create
# @param disk_names [Array<String>] Disks to consider
# @return [PartitionCreatorResult]
def create_partitions(new_partitions, disk_names)
log.info "Partitions to create: #{new_partitions}"
primary, non_primary = new_partitions.partition(&:primary)
parts_to_create = primary + non_primary
dist = best_distribution(parts_to_create, disk_names)
raise NoDiskSpaceError, "Could not find a valid partitioning distribution" if dist.nil?
part_creator = Proposal::PartitionCreator.new(original_graph)
part_creator.create_partitions(dist)
end
# Creates volume groups in the given devicegraph
#
# @param vgs [Array<Planned::LvmVg>] List of planned volume groups to add
# @param previous_result [Proposal::CreatorResult] Starting point
# @param parts_to_reuse [Array<Planned::Partition>] List of partitions to reuse
# @return [Proposal::CreatorResult] Result containing the specified volume groups
def set_up_lvm(vgs, previous_result, parts_to_reuse)
log.info "BEGIN: set_up_lvm: vgs=#{vgs.inspect} previous_result=#{previous_result.inspect}"
vgs.reduce(previous_result) do |result, vg|
pvs = previous_result.created_names { |d| d.pv_for?(vg.volume_group_name) }
pvs += parts_to_reuse.select { |d| d.pv_for?(vg.volume_group_name) }.map(&:reuse_name)
result.merge(create_logical_volumes(result.devicegraph, vg, pvs))
end
end
# Create volume group in the given devicegraph
#
# @param devicegraph [Devicegraph] Starting devicegraph
# @param vg [Planned::LvmVg] Volume group
# @param pvs [Planned::Partition,Planned::Md] List of physical volumes
# @return [Proposal::CreatorResult] Result containing the specified volume group
def create_logical_volumes(devicegraph, vg, pvs)
lvm_creator = Proposal::LvmCreator.new(devicegraph)
lvm_creator.create_volumes(vg, pvs)
rescue RuntimeError
lvm_creator = Proposal::LvmCreator.new(devicegraph)
new_vg = vg.clone
new_vg.lvs = flexible_devices(vg.lvs)
lvm_creator.create_volumes(new_vg, pvs)
end
# Reuses partitions or logical volumes for the given devicegraph
#
# Shrinking partitions/logical volumes should be processed first in order to free
# some space for growing ones.
#
# @param reused_devices [Array<Planned::Partition,Planned::LvmLv>] Logical volumes to reuse
# @param devicegraph [Devicegraph] Devicegraph to reuse partitions
def reuse_devices(reused_devices, devicegraph)
shrinking, not_shrinking = reused_devices.partition { |d| d.shrink?(devicegraph) }
(shrinking + not_shrinking).each { |d| d.reuse!(devicegraph) }
end
# Reuses volume groups for the given devicegraph
#
# @param reused_vgs [Array<Planned::LvmVg>] Volume groups to reuse
# @param devicegraph [Devicegraph] Devicegraph to reuse partitions
def reuse_vgs(reused_vgs, devicegraph)
reused_vgs.each do |vg|
vg.reuse!(devicegraph)
reuse_devices(vg.all_lvs.select(&:reuse?), devicegraph)
end
end
# Creates MD RAID devices in the given devicegraph
#
# @param mds [Array<Planned::Md>] List of planned MD arrays to create
# @param previous_result [Proposal::CreatorResult] Starting point
# @param parts_to_reuse [Array<Planned::Partition>] List of partitions to reuse
# @return [Proposal::CreatorResult] Result containing the specified MD RAIDs
def create_mds(mds, previous_result, parts_to_reuse)
mds.reduce(previous_result) do |result, md|
md_creator = Proposal::MdCreator.new(result.devicegraph)
devices = previous_result.created_names { |d| d.raid_name == md.name }
devices += parts_to_reuse.select { |d| d.raid_name == md.name }.map(&:reuse_name)
result.merge(md_creator.create_md(md, devices))
end
end
# Return a new planned devices with flexible limits
#
# The min_size is removed and a proportional weight is set for every device.
#
# @return [Hash<Planned::Partition => Planned::Partition>]
def flexible_devices(devices)
devices.map do |device|
new_device = device.clone
new_device.weight = device.min_size.to_i
new_device.min_size = DiskSize.B(1)
new_device
end
end
# Formats and/or mounts the stray block devices (Xen virtual partitions)
#
# @param planned_devices [Array<Planned::Device>] all planned devices
# @param devicegraph [Devicegraph] devicegraph containing the Xen
# partitions to be processed. It will be modified.
def process_stray_devs(planned_devices, devicegraph)
planned_stray_devs = planned_devices.select { |d| d.is_a?(Planned::StrayBlkDevice) }
planned_stray_devs.each { |d| d.reuse!(devicegraph) }
end
end
end
end