Skip to content

Commit

Permalink
Merge pull request #454 from yast/optimize_hosts_read
Browse files Browse the repository at this point in the history
Optimize hosts read
  • Loading branch information
jreidinger committed Oct 14, 2016
2 parents 430be2c + 63ed52c commit 5f6cb6d
Show file tree
Hide file tree
Showing 11 changed files with 489 additions and 162 deletions.
7 changes: 6 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ compiler:
before_install:
# disable rvm, use system Ruby
- rvm reset
# install newer augeasget repo with newer augeas, otherwise ruby-augeas fails (see https://github.com/yast/yast-network/pull/454#issuecomment-253795507)
- sudo add-apt-repository -y ppa:raphink/augeas
- sudo apt-get update
- sudo apt-get install libaugeas-dev libxml2-dev
# end of augeas install
- wget https://raw.githubusercontent.com/yast/yast-devtools/master/travis-tools/travis_setup.sh
- sh ./travis_setup.sh -p "rake yast2-devtools yast2-testsuite yast2 yast2-storage yast2-proxy yast2-country yast2-packager" -g "rspec:3.3.0 yast-rake gettext rubocop:0.41.2 simplecov:0.10.0 coveralls"
- sh ./travis_setup.sh -p "ruby2.1-dev augeas-lenses libaugeas0 rake yast2-devtools yast2-testsuite yast2 yast2-storage yast2-proxy yast2-country yast2-packager" -g "rspec:3.3.0 yast-rake gettext rubocop:0.41.2 simplecov:0.10.0 coveralls cfa cheetah"
script:
- rubocop
- rake check:syntax
Expand Down
7 changes: 7 additions & 0 deletions package/yast2-network.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
-------------------------------------------------------------------
Fri Oct 14 14:08:31 UTC 2016 - jreidinger@suse.com

- optimize loading /etc/hosts if there is a lot of entries
(bsc#877047)
- 3.2.5

-------------------------------------------------------------------
Mon Oct 10 09:11:29 UTC 2016 - mfilka@suse.com

Expand Down
10 changes: 9 additions & 1 deletion package/yast2-network.spec
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@


Name: yast2-network
Version: 3.2.4
Version: 3.2.5
Release: 0

BuildRoot: %{_tmppath}/%{name}-%{version}-build
Expand Down Expand Up @@ -50,6 +50,12 @@ BuildRequires: yast2-storage >= 2.21.11
Requires: yast2-storage >= 2.21.11
# Packages::vnc_packages
Requires: yast2-packager >= 3.1.47
# cfa for parsing hosts
BuildRequires: rubygem(%rb_default_ruby_abi:cfa)
Requires: rubygem(%rb_default_ruby_abi:cfa)
# lenses are needed to use cfa
BuildRequires: augeas-lenses
Requires: augeas-lenses

# testsuite
BuildRequires: rubygem(rspec)
Expand Down Expand Up @@ -93,6 +99,8 @@ rake install DESTDIR="%{buildroot}"
%{yast_schemadir}/autoyast/rnc/networking.rnc
%{yast_schemadir}/autoyast/rnc/host.rnc
%{yast_libdir}/network
%dir %{yast_libdir}/cfa/
%{yast_libdir}/cfa/hosts.rb
%{yast_ydatadir}/network

%dir %{yast_docdir}
Expand Down
10 changes: 2 additions & 8 deletions src/clients/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,9 @@ def HostGUI
def ListHandler(_options)
# Command line output Headline
# configuration of hosts
summary = Ops.add(
Ops.add(
"\n" + _("Host Configuration Summary:") + "\n\n",
RichText.Rich2Plain(Host.Summary)
),
"\n"
)
summary = "\n" + _("Host Configuration Summary:") + "\n\n" +
RichText.Rich2Plain(Host.Summary) + "\n"

Builtins.y2debug("%1", summary)
CommandLine.Print(summary)
true
end
Expand Down
6 changes: 3 additions & 3 deletions src/include/network/lan/address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1530,7 +1530,7 @@ def AddressDialog
ip_changed = LanItems.ipaddr !=
Ops.get_string(@settings, "IPADDR", "")
if ip_changed
Host.set_names(LanItems.ipaddr, [])
Host.remove_ip(LanItems.ipaddr)
Builtins.y2milestone("IP has changed")
end

Expand All @@ -1541,12 +1541,12 @@ def AddressDialog

if @hostname_initial != Ops.get_string(@settings, "HOSTNAME", "") || ip_changed
if Ops.get_string(@settings, "HOSTNAME", "") == ""
Host.set_names(LanItems.ipaddr, [])
Host.remove_ip(LanItems.ipaddr)
else
Host.Update(
@hostname_initial,
Ops.get_string(@settings, "HOSTNAME", ""),
[Ops.get_string(@settings, "IPADDR", "")]
Ops.get_string(@settings, "IPADDR", "")
)
end
end
Expand Down
4 changes: 0 additions & 4 deletions src/include/network/services/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,6 @@ def HostsMainDialog(standalone)
key = Ops.get_string(row, 1, "")
Host.add_name(key, value)
end
# deleted entries need to be set to [],
# so that ini-agent does not keep them in
# config file (#455862)
Builtins.foreach(deleted_items) { |d| Host.set_names(d, []) }
end
break
else
Expand Down
200 changes: 200 additions & 0 deletions src/lib/cfa/hosts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
require "yast"
require "yast2/target_file"

require "cfa/base_model"
require "cfa/matcher"
require "cfa/augeas_parser"

module CFA
# class representings /etc/hosts file model. It provides helper to manipulate
# with file. It uses CFA framework and Augeas parser.
# @see http://www.rubydoc.info/github/config-files-api/config_files_api/CFA/BaseModel
# @see http://www.rubydoc.info/github/config-files-api/config_files_api/CFA/AugeasParser
class Hosts < BaseModel
PARSER = AugeasParser.new("hosts.lns")
PATH = "/etc/hosts".freeze
include Yast::Logger

def initialize(file_handler: nil)
super(PARSER, PATH, file_handler: file_handler)
end

# The old format used by {Yast::HostClass}.
# @return [Hash{String => Array<String>}] keys are IPs,
# values are lists of lines in /etc/hosts (not names!)
# with whitespace separated hostnames, where the first one is canonical
# and the rest are aliases
#
# For example, the file contents
#
# 1.2.3.4 www.example.org www
# 1.2.3.7 log.example.org log
# 1.2.3.7 sql.example.org sql
#
# is returned as
#
# {
# "1.2.3.4" => "www.example.org www"
# "1.2.3.7" => [
# "log.example.org log",
# "sql.example.org sql"
# ]
# }
def hosts
matcher = Matcher.new { |k, _v| k =~ /^\d*$/ }
data.select(matcher).each_with_object({}) do |host, result|
entry = host[:value]
result[entry["ipaddr"]] ||= []
result[entry["ipaddr"]] << single_host_entry(entry)
end
end

# Returns single entry from hosts for given ip or empty array if not found
# @see #hosts
# @return [Array<String>]
def host(ip)
hosts = data.select(ip_matcher(ip))

hosts.map do |host|
single_host_entry(host[:value])
end
end

# deletes all occurences of given ip in host table
# @return [void]
def delete_by_ip(ip)
entries = data.select(ip_matcher(ip))
if entries.empty?
log.info "no entry to delete for ip #{ip}"
return
end

if entries.size > 1
log.info "delete host with ip '#{ip}' removes more then one entry"
end

entries.each do |e|
log.info "deleting record #{e.inspect}"
data.delete(e[:key])
end
end

# Replaces or adds a new host entry.
# If more than one entry with the given ip exists
# then it replaces the last instance.
# @param [String] ip
# @param [String] canonical
# @param [Array<String>] aliases
# @return [void]
def set_entry(ip, canonical, aliases = [])
entries = data.select(ip_matcher(ip))
if entries.empty?
add_entry(ip, canonical, aliases)
return
end

if entries.size > 1
log.info "more then one entry with ip '#{ip}'. Replacing last one."
end

entry = entries.last[:value]
entry["ipaddr"] = ip
entry["canonical"] = canonical
# clear previous aliases
entry.delete("alias")
entry.delete("alias[]")
aliases_col = entry.collection("alias")
aliases.each do |a|
aliases_col.add(a)
end
end

# Adds new entry, even if it exists
# @param [String] ip
# @param [String] canonical
# @param [Array<String>] aliases
# @return [void]
def add_entry(ip, canonical, aliases = [])
log.info "adding new entry for ip #{ip}"
entry_line = AugeasTree.new
entry_line["ipaddr"] = ip
entry_line["canonical"] = canonical
aliases_col = entry_line.collection("alias")
aliases.each do |a|
aliases_col.add(a)
end
data.add(unique_id, entry_line)
end

# Removes hostname from all entries in hosts table.
# If it is the only hostname for a given ip, the ip is removed
# If it is canonical name, then the first alias becomes the canonical hostname
# @param [String] hostname
# @return [void]
def delete_hostname(hostname)
entries = data.select(hostname_matcher(hostname))
entries.each do |pair|
entry = pair[:value]
if entry["canonical"] == hostname
aliases = aliases_for(entry)
if aliases.empty?
delete_host(entry["ipaddr"])
else
entry["canonical"] = aliases.first
entry.delete("alias")
entry.delete("alias[]")
aliases_col = entry.collection("alias")
aliases[1..-1].each do |a|
aliases_col.add(a)
end
end
else
reduced_aliases = aliases_for(entry)
reduced_aliases.delete(hostname)
entry.delete("alias")
entry.delete("alias[]")
aliases_col = entry.collection("alias")
aliases[1..-1].each do |a|
aliases_col.add(a)
end
end
end
end

private

# returns matcher for cfa to find entries with given ip
def ip_matcher(ip)
Matcher.new { |_k, v| v["ipaddr"] == ip }
end

# returns matcher for cfa to find entries with given hostname
def hostname_matcher(hostname)
Matcher.new do |_k, v|
v["canonical"] == hostname || aliases_for(v).include?(hostname)
end
end

# returns aliases as array even if there is only one
def aliases_for(entry)
entry["alias[]"] ? entry.collection("alias").map { |a| a } : [entry["alias"]].compact
end

# generate old format string with first canonical and then aliases
# all separated by space
def single_host_entry(entry)
result = [entry["canonical"]]
result.concat(aliases_for(entry))
result.join(" ")
end

# helper to generate unique id for cfa entry
def unique_id
id = 1
loop do
return id.to_s unless data[id.to_s]
id += 1
end
end
end
end
Loading

0 comments on commit 5f6cb6d

Please sign in to comment.