-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(PUP-4619) sort fstab entries #3953
Changes from all commits
ff5ea56
20d8adb
eb24165
92f6b75
9e08db2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -195,6 +195,10 @@ def pre_gen(record) | |
end | ||
end | ||
|
||
# Class variable to store the catalog's choice about whether | ||
# fstab entries should be sorted. | ||
@sort_output = false | ||
|
||
# Every entry in fstab is :unmounted until we can prove different | ||
def self.prefetch_hook(target_records) | ||
target_records.collect do |record| | ||
|
@@ -240,6 +244,15 @@ def self.prefetch(resources = nil) | |
end | ||
end | ||
end | ||
# Examine the catalog to determine whether the fstab should be ordered. | ||
# Default is no sorting | ||
@sort_output = false | ||
return if !resources || resources.empty? | ||
meta = resources.values[0].catalog.resource('resources', 'mount') | ||
return unless meta | ||
if meta[:sort_output] | ||
@sort_output = true | ||
end | ||
end | ||
|
||
def self.mountinstances | ||
|
@@ -272,6 +285,53 @@ def self.mountinstances | |
instances | ||
end | ||
|
||
# Sort fstab entries so that | ||
# a mount point is located inside another mount point appears later and | ||
# a device that is located inside a mount point appears later. | ||
# | ||
# "Stable opportunistic insertion sort" | ||
# Copy unsorted list A to sorted B by repeatedly | ||
# * removing the first element from A | ||
# * compare with each element in B, from first to last | ||
# * insert before current element in B if smaller | ||
# * insert at the end of B otherwise | ||
# * finish if A is empty | ||
# * repeat | ||
# | ||
# Stabilizes itself by inserting as late as possible. | ||
# | ||
# A record is "smaller" than another if | ||
# * the mount point of A is a leading substring of B's mount point | ||
# * the mount point of A is a leading substring of B's device (B is a bind mount) | ||
# | ||
# @note Does not really care about directory names and does substring | ||
# comparison only. So /media will be put before /mediastuff. This is | ||
# not necessary but will be rare and usually cause no harm, either. | ||
# | ||
# @api private | ||
def self.order(records) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than an O(n^2) sort with a nested catch/throw (I would imagine that the Ruby VM as it needs to setup an exception handler in the frame, which is an expensive operation on some platforms), would this work? def order(records)
return records if records.empty?
# Create an array of indexes into the given records array
indexes = [*0..(records.size - 1)]
indexes.sort! do |a, b|
record_a = records[a]
record_b = records[b]
name_a = record_a[:name]
device_a = record_a[:device]
name_b = record_b[:name]
device_b = record_b[:device]
# If b depends on a, a should go first
next -1 if name_b.start_with?(name_a) || device_b.start_with?(name_a)
# Otherwise, if a depends on b, b should go first
next 1 if name_a.start_with?(name_b) || device_a.start_with?(name_b)
# Sort independent entries by index for stable sort
a <=> b
end.map! do |index|
# Replace the index with the original entry
records[index]
end
end There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great point about complexity, but at first glance, this looks to me as though it's basically a clever trick to stabilize Ruby's own sort. That's helpful, but we're still running the risk of critical comparisons never taking place. Yes, this is a total order, but only because of the arbitrary "keep original order" ruling. It still does not address the issue that for the primary comparator, most elements will be equal. In other words, if the quicksort happens to choose convenient pivot elements, this will work, but not in the average case. Unless I'm wrong :-) I'll pass it by the existing tests in any case. Thanks for looking into this @peterhuene ! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, the above still isn't quite right. At any rate, I'm not terribly concerned about the O(N^2) runtime as I suspect the majority of fstab files aren't filled with thousands upon thousands of entries. Could we at least do away with the catch/throw for an inserted flag? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I feared: Passes some tests, but does break one of them. Agree that fstabs are usually not large. Even if, this is an "at most once" kind of deal. The agent will do this only after all I wasn't aware of the catch/throw penalty. If elegance costs performance, I will absolutely change to a flag as you suggested. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ...pushed that as an unsquashed commit for posterity. Will tidy up before triage :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, should have thought ahead and figured that quicksort won't work here as the dependency check breaks the total ordering requirement. The insertion sort works as a poor man's topological sort, so I'm 👍. |
||
return records if records.empty? | ||
return records unless @sort_output; | ||
# insert first record first | ||
result = [ ] | ||
# iterate over the rest | ||
records.each do |a| | ||
inserted = false | ||
result.each_index do |i| | ||
b = result[i] | ||
# is a a leading substring of b? | ||
if b[:name].index(a[:name]) == 0 || b[:device].index(a[:name]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on the comment above, shouldn't it be |
||
result.insert(i, a) | ||
inserted = true | ||
break | ||
end | ||
end | ||
next if inserted | ||
result.push(a) | ||
end | ||
result | ||
end | ||
|
||
def flush | ||
needs_mount = @property_hash.delete(:needs_mount) | ||
super | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -391,6 +391,15 @@ def self.to_file(records) | |
header + text | ||
end | ||
|
||
# Hook for ordering records right before writing them back to disk. | ||
# | ||
# @abstract The default behavior is to keep the original order, which | ||
# is generally preferable. Inheriting providers can override this method | ||
# to satisfy special ordering requirements in the managed files. | ||
def self.order(records) | ||
return records | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we comment why the |
||
end | ||
|
||
def create | ||
@resource.class.validproperties.each do |property| | ||
if value = @resource.should(property) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
UUID=a5d1054c-caa0-4b80-b12f-bf9449086f8e / ext4 defaults,relatime,errors=remount-ro 0 1 | ||
/dev/vg0/puppetlabs /opt/data/research/projects/puppetlabs ext4 defaults 0 2 | ||
/opt/temp/tables /var/local/mysql-temp none bind,rw 0 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
UUID=a5d1054c-caa0-4b80-b12f-bf9449086f8e / ext4 defaults,relatime,errors=remount-ro 0 1 | ||
/dev/vg0/puppetlabs /opt/data/research/projects/puppetlabs ext4 defaults 0 2 | ||
/dev/vg0/temp /opt/temp ext4 defaults 0 0 | ||
/opt/temp/tables /var/local/mysql-temp none bind,rw 0 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
UUID=a5d1054c-caa0-4b80-b12f-bf9449086f8e / ext4 defaults,relatime,errors=remount-ro 0 1 | ||
/dev/vg0/data /opt/data ext4 defaults 0 0 | ||
/dev/vg0/puppetlabs /opt/data/research/projects/puppetlabs ext4 defaults 0 2 | ||
/opt/temp/tables /var/local/mysql-temp none bind,rw 0 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
UUID=a5d1054c-caa0-4b80-b12f-bf9449086f8e / ext4 defaults,relatime,errors=remount-ro 0 1 | ||
/dev/vg0/puppetlabs /opt/data/research/projects/puppetlabs ext4 defaults 0 2 | ||
/opt/temp/tables /var/local/mysql-temp none bind,rw 0 0 | ||
/dev/vg0/log_archive /opt/data/log-archive ext4 defaults 0 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
UUID=a5d1054c-caa0-4b80-b12f-bf9449086f8e / ext4 defaults,relatime,errors=remount-ro 0 1 | ||
/dev/vg0/data /opt/data ext4 defaults 0 2 | ||
/dev/vg0/opt /opt none bind,rw 0 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
UUID=a5d1054c-caa0-4b80-b12f-bf9449086f8e / ext4 defaults,relatime,errors=remount-ro 0 1 | ||
/dev/vg0/puppetlabs /opt/data/research/projects/puppetlabs ext4 defaults 0 2 | ||
/opt/temp/tables /var/local/mysql-temp none bind,rw 0 0 | ||
/dev/vg0/temp /opt/temp ext4 defaults 0 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
UUID=a5d1054c-caa0-4b80-b12f-bf9449086f8e / ext4 defaults,relatime,errors=remount-ro 0 1 | ||
/dev/vg0/opt /opt none bind,rw 0 0 | ||
/dev/vg0/data /opt/data ext4 defaults 0 2 | ||
/dev/vg0/log_archive /opt/data/log-archive ext4 defaults 0 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
UUID=a5d1054c-caa0-4b80-b12f-bf9449086f8e / ext4 defaults,relatime,errors=remount-ro 0 1 | ||
/dev/vg0/puppetlabs /opt/data/research/projects/puppetlabs ext4 defaults 0 2 | ||
/opt/temp/tables /var/local/mysql-temp none bind,rw 0 0 | ||
/dev/vg0/data /opt/data ext4 defaults 0 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
UUID=a5d1054c-caa0-4b80-b12f-bf9449086f8e / ext4 defaults,relatime,errors=remount-ro 0 1 | ||
/dev/vg0/data /opt/data ext4 defaults 0 2 | ||
/dev/vg0/opt /opt none bind,rw 0 0 | ||
/dev/vg0/log_archive /opt/data/log-archive ext4 defaults 0 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems odd, but I don't see any consistent patterns about using the resources metatype. @joshcooper @peterhuene have any further comments on this approach? The only real alternative would be a configuration option.
I'm not sure the usage is intuitive, it requires specifying a
resources
resource with the namemount
. As inresources { 'mount': sort_output => true }
. It's a little magical, but I guess that's nothing new.