-
Notifications
You must be signed in to change notification settings - Fork 34
/
config.rb
305 lines (275 loc) · 10.7 KB
/
config.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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# Copyright (c) [2019] 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 "y2network/config_writer"
require "y2network/config_reader"
require "y2network/routing"
require "y2network/dns"
require "y2network/hostname"
require "y2network/interfaces_collection"
require "y2network/s390_group_devices_collection"
require "y2network/connection_configs_collection"
require "y2network/physical_interface"
require "y2network/can_be_copied"
require "y2network/backend"
module Y2Network
# This class represents the current network configuration including interfaces,
# routes, etc.
#
# @example Reading from wicked
# config = Y2Network::Config.from(:sysconfig)
# config.interfaces.map(&:name) #=> ["lo", eth0", "wlan0"]
#
# @example Adding a default route to the first routing table
# config = Y2Network::Config.from(:sysconfig)
# route = Y2Network::Route.new(to: :default)
# config.routing.tables.first << route
# config.write
class Config
include CanBeCopied
include Yast::Logger
# @return [Backend, Symbol, nil]
attr_reader :backend
# @return [InterfacesCollection]
attr_accessor :interfaces
# @return [ConnectionConfigsCollection]
attr_accessor :connections
# @return [S390GroupDevicesCollection]
attr_accessor :s390_devices
# @return [Routing] Routing configuration
attr_accessor :routing
# @return [DNS] DNS configuration
attr_accessor :dns
# @return [Hostname] Hostname configuration
attr_accessor :hostname
# @return [Array<Driver>] Available drivers
attr_accessor :drivers
# @return [Symbol] Information source (see {Y2Network::Reader} and {Y2Network::Writer})
attr_accessor :source
class << self
# @param source [Symbol] Source to read the configuration from
# @param opts [Array<Object>] Reader options. Check readers documentation to find out
# supported options.
def from(source, *opts)
reader = ConfigReader.for(source, *opts)
reader.config
end
# Adds the configuration to the register
#
# @param id [Symbol] Configuration ID
# @param config [Y2Network::Config] Network configuration
def add(id, config)
configs[id] = config
end
# Finds the configuration in the register
#
# @param id [Symbol] Configuration ID
# @return [Config,nil] Configuration with the given ID or nil if not found
def find(id)
configs[id]
end
# Resets the configuration register
def reset
configs.clear
end
private
# Configuration register
def configs
@configs ||= {}
end
end
# Constructor
#
# @param source [Symbol] Configuration source
# @param opts [Hash] configuration options
# @option opts [InterfacesCollection] :interfaces List of interfaces
# @option opts [ConnectionConfigsCollection] :connections List of connection configurations
# @option opts [S390GroupDevicesCollection] :s390_devices
# @option opts [Routing] :routing Object with routing configuration
# @option opts [DNS] :dns Object with DNS configuration
# @option opts [Hostname] :hostname Object with Hostname configuration
# @option opts [Array<Driver>] :drivers List of available drivers
def initialize(source:, **opts)
@backend = opts.fetch(:backend, nil)
@interfaces = opts.fetch(:interfaces, InterfacesCollection.new)
@connections = opts.fetch(:connections, ConnectionConfigsCollection.new)
@s390_devices = opts.fetch(:s390_devices, S390GroupDevicesCollection.new)
@drivers = opts.fetch(:drivers, [])
@routing = opts.fetch(:routing, Routing.new)
@dns = opts.fetch(:dns, DNS.new)
@hostname = opts.fetch(:hostname, Hostname.new)
@source = source
end
# Writes the configuration into the YaST modules
#
# Writes only changes against original configuration if the original configuration
# is provided
#
# @param original [Y2Network::Config] configuration used for detecting changes
# @param target [Symbol] Target to write the configuration to (:sysconfig)
# @param only [Array<symbol>, nil] explicit sections to be written, by default if no
# parameter is given then all changes will be written.
#
# @see Y2Network::ConfigWriter
def write(original: nil, target: nil, only: nil)
target = target || backend&.id || source
Y2Network::ConfigWriter.for(target).write(self, original, only: only)
end
# Determines whether two configurations are equal
#
# @return [Boolean] true if both configurations are equal; false otherwise
def ==(other)
source == other.source &&
backend == other.backend &&
interfaces == other.interfaces &&
routing == other.routing &&
dns == other.dns &&
hostname == other.hostname &&
connections == other.connections &&
s390_devices == other.s390_devices
end
# Renames a given interface and the associated connections
#
# @param old_name [String] Old interface's name
# @param new_name [String] New interface's name
# @param mechanism [Symbol] Property to base the rename on (:mac or :bus_id)
def rename_interface(old_name, new_name, mechanism)
log.info "Renaming #{old_name.inspect} to #{new_name.inspect} using #{mechanism.inspect}"
interface = interfaces.by_name(old_name || new_name)
interface.rename(new_name, mechanism)
return unless old_name # do not modify configurations if it is just renaming mechanism
connections.by_interface(old_name).each do |connection|
connection.interface = new_name
rename_dependencies(old_name, new_name, connection)
end
hostname.dhcp_hostname = new_name if hostname.dhcp_hostname == old_name
end
# deletes interface and all its config. If interface is physical,
# it is not removed as we cannot remove physical interface.
#
# @param name [String] Interface's name
def delete_interface(name)
delete_dependents(name)
connections.reject! { |c| c.interface == name }
# do not use no longer existing device name
hostname.dhcp_hostname = :none if hostname.dhcp_hostname == name
interface = interfaces.by_name(name)
return if interface.is_a?(PhysicalInterface) && interface.present?
interfaces.reject! { |i| i.name == name }
end
# Adds or update a connection config
#
# If the interface which is associated to does not exist (because it is a virtual one or it is
# not present), it gets added.
def add_or_update_connection_config(connection_config)
log.info "add_update connection config #{connection_config.inspect}"
connections.add_or_update(connection_config)
interface = interfaces.by_name(connection_config.interface)
return if interface
log.info "Creating new interface"
interfaces << Interface.from_connection(connection_config)
end
# Returns the candidate drivers for a given interface
#
# @return [Array<Driver>]
def drivers_for_interface(name)
interface = interfaces.by_name(name)
names = interface.drivers.map(&:name)
if interface.custom_driver && !names.include?(interface.custom_driver)
names << interface.custom_driver
end
drivers.select { |d| names.include?(d.name) }
end
# Adds or update a driver
#
# @param new_driver [Driver] Driver to add or update
def add_or_update_driver(new_driver)
idx = drivers.find_index { |d| d.name == new_driver.name }
if idx
drivers[idx] = new_driver
else
drivers << new_driver
end
end
# Determines whether a given interface is configured or not
#
# An interface is considered as configured when it has an associated collection.
#
# @param iface_name [String] Interface's name
# @return [Boolean]
def configured_interface?(iface_name)
return false if iface_name.nil? || iface_name.empty?
!connections.by_interface(iface_name).empty?
end
# @note does not work recursively. So for delete it needs to be called for all modified vlans.
# @return [ConnectionConfigsCollection] returns collection of interfaces that needs
# to be modified or deleted if `connection_config` is deleted or renamed
def connections_to_modify(connection_config)
result = []
bond_bridge = connection_config.find_master(connections)
result << bond_bridge if bond_bridge
vlans = connections.to_a.select do |c|
c.type.vlan? && c.parent_device == connection_config.name
end
result.concat(vlans)
ConnectionConfigsCollection.new(result)
end
# Return whether the config backend is the one given or not
#
# @param name [Symbol]
def backend?(name)
backend&.id == name
end
def backend=(id)
@backend = Y2Network::Backend.all.find { |b| b.id == id }
end
alias_method :eql?, :==
private
def delete_dependents(name)
connection = connections.by_name(name)
to_modify = connections_to_modify(connection)
to_modify.each do |dependency|
case dependency.type
when InterfaceType::BRIDGE
dependency.ports.delete(name)
when InterfaceType::BONDING
dependency.slaves.delete(name)
when InterfaceType::VLAN
delete_interface(dependency.interface)
else
raise "Unexpected type of interface to modify #{dependency.inspect}"
end
end
end
def rename_dependencies(old_name, new_name, connection)
to_modify = connections_to_modify(connection)
to_modify.each do |dependency|
case dependency.type
when InterfaceType::BRIDGE
dependency.ports.map! { |e| (e == old_name) ? new_name : e }
when InterfaceType::BONDING
dependency.slaves.map! { |e| (e == old_name) ? new_name : e }
when InterfaceType::VLAN
dependency.parent_device = new_name
else
raise "Unexpected type of interface to modify #{dependency.inspect}"
end
end
end
end
end