aws-eni 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +2 -0
- data/aws-eni.gemspec +26 -0
- data/bin/aws-eni +442 -0
- data/lib/aws-eni.rb +278 -0
- data/lib/aws-eni/errors.rb +13 -0
- data/lib/aws-eni/ifconfig.rb +309 -0
- data/lib/aws-eni/meta.rb +76 -0
- data/lib/aws-eni/version.rb +5 -0
- metadata +114 -0
data/lib/aws-eni.rb
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'aws-sdk'
|
3
|
+
require 'aws-eni/version'
|
4
|
+
require 'aws-eni/errors'
|
5
|
+
require 'aws-eni/meta'
|
6
|
+
require 'aws-eni/ifconfig'
|
7
|
+
|
8
|
+
module Aws
|
9
|
+
module ENI
|
10
|
+
extend self
|
11
|
+
|
12
|
+
def environment
|
13
|
+
@environment ||= {}.tap do |e|
|
14
|
+
hwaddr = IFconfig['eth0'].hwaddr
|
15
|
+
Meta.open_connection do |conn|
|
16
|
+
e[:instance_id] = Meta.http_get(conn, 'instance-id')
|
17
|
+
e[:availability_zone] = Meta.http_get(conn, 'placement/availability-zone')
|
18
|
+
e[:region] = e[:availability_zone].sub(/(.*)[a-z]/,'\1')
|
19
|
+
e[:vpc_id] = Meta.http_get(conn, "network/interfaces/macs/#{hwaddr}/vpc-id")
|
20
|
+
e[:vpc_cidr] = Meta.http_get(conn, "network/interfaces/macs/#{hwaddr}/vpc-ipv4-cidr-block")
|
21
|
+
end
|
22
|
+
unless e[:vpc_id]
|
23
|
+
raise EnvironmentError, "Unable to detect VPC settings, library incompatible with EC2-Classic"
|
24
|
+
end
|
25
|
+
end.freeze
|
26
|
+
rescue Meta::ConnectionFailed
|
27
|
+
raise EnvironmentError, "Unable to load EC2 meta-data"
|
28
|
+
end
|
29
|
+
|
30
|
+
def owner_tag(new_owner = nil)
|
31
|
+
@owner_tag = new_owner.to_s if new_owner
|
32
|
+
@owner_tag || 'aws-eni script'
|
33
|
+
end
|
34
|
+
|
35
|
+
def client
|
36
|
+
@client ||= Aws::EC2::Client.new(region: environment[:region])
|
37
|
+
end
|
38
|
+
|
39
|
+
# return our internal model of this instance's network configuration on AWS
|
40
|
+
def list(filter = nil)
|
41
|
+
IFconfig.filter(filter).map(&:to_h) if environment
|
42
|
+
end
|
43
|
+
|
44
|
+
# sync local machine's network interface config with the EC2 meta-data
|
45
|
+
# pass dry_run option to check whether configuration is out of sync without
|
46
|
+
# modifying it
|
47
|
+
def configure(filter = nil, options = {})
|
48
|
+
IFconfig.configure(filter, options) if environment
|
49
|
+
end
|
50
|
+
|
51
|
+
# clear local machine's network interface config
|
52
|
+
def deconfigure(filter = nil)
|
53
|
+
IFconfig.deconfigure(filter) if environment
|
54
|
+
end
|
55
|
+
|
56
|
+
# create network interface
|
57
|
+
def create_interface(options = {})
|
58
|
+
timestamp = Time.now.xmlschema
|
59
|
+
params = {}
|
60
|
+
params[:subnet_id] = options[:subnet_id] || IFconfig.first.subnet_id
|
61
|
+
params[:private_ip_address] = options[:primary_ip] if options[:primary_ip]
|
62
|
+
params[:groups] = [*options[:security_groups]] if options[:security_groups]
|
63
|
+
params[:description] = "generated by #{owner_tag} from #{environment[:instance_id]} on #{timestamp}"
|
64
|
+
|
65
|
+
response = client.create_network_interface(params)
|
66
|
+
client.create_tags(resources: [response[:network_interface][:network_interface_id]], tags: [
|
67
|
+
{ key: 'created by', value: owner_tag },
|
68
|
+
{ key: 'created on', value: timestamp },
|
69
|
+
{ key: 'created from', value: environment[:instance_id] }
|
70
|
+
])
|
71
|
+
{
|
72
|
+
id: response[:network_interface][:network_interface_id],
|
73
|
+
subnet_id: response[:network_interface][:subnet_id],
|
74
|
+
api_response: response[:network_interface]
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
# attach network interface
|
79
|
+
def attach_interface(id, options = {})
|
80
|
+
interface = IFconfig[options[:device_number] || options[:name]]
|
81
|
+
raise InvalidParameterError, "Interface #{interface.name} is already in use" if interface.exists?
|
82
|
+
|
83
|
+
params = {}
|
84
|
+
params[:network_interface_id] = id
|
85
|
+
params[:instance_id] = environment[:instance_id]
|
86
|
+
params[:device_index] = interface.device_number
|
87
|
+
|
88
|
+
response = client.attach_network_interface(params)
|
89
|
+
attached = wait_for(10) { interface.exists? }
|
90
|
+
raise TimeoutError, "Timed out waiting for the interface to attach" unless attached
|
91
|
+
interface.configure if options[:configure]
|
92
|
+
interface.enable if options[:enable]
|
93
|
+
{
|
94
|
+
id: interface.interface_id,
|
95
|
+
name: interface.name,
|
96
|
+
configured: options[:configure],
|
97
|
+
api_response: response
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
# detach network interface
|
102
|
+
def detach_interface(id, options = {})
|
103
|
+
interface = IFconfig.filter(id).first
|
104
|
+
raise InvalidParameterError, "Interface #{interface.name} does not exist" unless interface && interface.exists?
|
105
|
+
if options[:name] && interface.name != options[:name]
|
106
|
+
raise InvalidParameterError, "Interface #{interface.interface_id} not found on #{options[:name]}"
|
107
|
+
end
|
108
|
+
if options[:device_number] && interface.device_number != options[:device_number].to_i
|
109
|
+
raise InvalidParameterError, "Interface #{interface.interface_id} not found at index #{options[:device_number]}"
|
110
|
+
end
|
111
|
+
|
112
|
+
description = client.describe_network_interfaces(filters: [{
|
113
|
+
name: 'attachment.instance-id',
|
114
|
+
values: [environment[:instance_id]]
|
115
|
+
},{
|
116
|
+
name: 'network-interface-id',
|
117
|
+
values: [interface.interface_id]
|
118
|
+
}])
|
119
|
+
description = description[:network_interfaces].first
|
120
|
+
raise UnknownInterfaceError, "Interface attachment could not be located" unless description
|
121
|
+
|
122
|
+
interface.disable
|
123
|
+
interface.deconfigure
|
124
|
+
client.detach_network_interface(
|
125
|
+
attachment_id: description[:attachment][:attachment_id],
|
126
|
+
force: true
|
127
|
+
)
|
128
|
+
deleted = false
|
129
|
+
created_by_us = description.tag_set.any? { |tag| tag.key == 'created by' && tag.value == owner_tag }
|
130
|
+
unless options[:delete] == false || options[:delete].nil? && !created_by_us
|
131
|
+
detached = wait_for(10, 0.3) do
|
132
|
+
!interface.exists? && interface_status(description[:network_interface_id]) == 'available'
|
133
|
+
end
|
134
|
+
raise TimeoutError, "Timed out waiting for the interface to detach" unless detached
|
135
|
+
client.delete_network_interface(network_interface_id: description[:network_interface_id])
|
136
|
+
deleted = true
|
137
|
+
end
|
138
|
+
{
|
139
|
+
id: description[:network_interface_id],
|
140
|
+
name: "eth#{description[:attachment][:device_index]}",
|
141
|
+
device_number: description[:attachment][:device_index],
|
142
|
+
created_by_us: created_by_us,
|
143
|
+
deleted: deleted,
|
144
|
+
api_response: description
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
# delete unattached network interfaces
|
149
|
+
def clean_interfaces(filter = nil, options = {})
|
150
|
+
safe_mode = true unless options[:safe_mode] == false
|
151
|
+
|
152
|
+
filters = [
|
153
|
+
{ name: 'vpc-id', values: [environment[:vpc_id]] },
|
154
|
+
{ name: 'status', values: ['available'] }
|
155
|
+
]
|
156
|
+
if filter
|
157
|
+
case filter
|
158
|
+
when /^eni-/
|
159
|
+
filters << { name: 'network-interface-id', values: [filter] }
|
160
|
+
when /^subnet-/
|
161
|
+
filters << { name: 'subnet-id', values: [filter] }
|
162
|
+
when /^#{environment[:region]}[a-z]$/
|
163
|
+
filters << { name: 'availability-zone', values: [filter] }
|
164
|
+
else
|
165
|
+
raise InvalidParameterError, "Unknown resource filter: #{filter}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
if safe_mode
|
169
|
+
filters << { name: 'tag:created by', values: [owner_tag] }
|
170
|
+
end
|
171
|
+
|
172
|
+
descriptions = client.describe_network_interfaces(filters: filters)
|
173
|
+
interfaces = descriptions[:network_interfaces].select do |interface|
|
174
|
+
skip = safe_mode && interface.tag_set.any? do |tag|
|
175
|
+
begin
|
176
|
+
tag.key == 'created on' && Time.now - Time.parse(tag.value) < 60
|
177
|
+
rescue ArgumentError
|
178
|
+
false
|
179
|
+
end
|
180
|
+
end
|
181
|
+
unless skip
|
182
|
+
client.delete_network_interface(network_interface_id: interface[:network_interface_id])
|
183
|
+
true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
{
|
187
|
+
count: interfaces.count,
|
188
|
+
deleted: interfaces.map { |eni| eni[:network_interface_id] },
|
189
|
+
api_response: interfaces
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
# add new private ip using the AWS api and add it to our local ip config
|
194
|
+
def assign_secondary_ip(interface, options = {})
|
195
|
+
raise NoMethodError, "assign_secondary_ip not yet implemented"
|
196
|
+
{
|
197
|
+
private_ip: '0.0.0.0',
|
198
|
+
device_name: 'eth0',
|
199
|
+
interface_id: 'eni-1a2b3c4d'
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
# remove a private ip using the AWS api and remove it from local config
|
204
|
+
def unassign_secondary_ip(private_ip, options = {})
|
205
|
+
raise NoMethodError, "unassign_secondary_ip not yet implemented"
|
206
|
+
{
|
207
|
+
private_ip: '0.0.0.0',
|
208
|
+
device_name: 'eth0',
|
209
|
+
interface_id: 'eni-1a2b3c4d',
|
210
|
+
public_ip: '0.0.0.0',
|
211
|
+
allocation_id: 'eipalloc-1a2b3c4d',
|
212
|
+
association_id: 'eipassoc-1a2b3c4d',
|
213
|
+
released: true
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
# associate a private ip with an elastic ip through the AWS api
|
218
|
+
def associate_elastic_ip(private_ip, options = {})
|
219
|
+
raise NoMethodError, "associate_elastic_ip not yet implemented"
|
220
|
+
{
|
221
|
+
private_ip: '0.0.0.0',
|
222
|
+
device_name: 'eth0',
|
223
|
+
interface_id: 'eni-1a2b3c4d',
|
224
|
+
public_ip: '0.0.0.0',
|
225
|
+
allocation_id: 'eipalloc-1a2b3c4d',
|
226
|
+
association_id: 'eipassoc-1a2b3c4d'
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
# dissociate a public ip from a private ip through the AWS api and
|
231
|
+
# optionally release the public ip
|
232
|
+
def dissociate_elastic_ip(ip, options = {})
|
233
|
+
raise NoMethodError, "dissociate_elastic_ip not yet implemented"
|
234
|
+
{
|
235
|
+
private_ip: '0.0.0.0',
|
236
|
+
device_name: 'eth0',
|
237
|
+
interface_id: 'eni-1a2b3c4d',
|
238
|
+
public_ip: '0.0.0.0',
|
239
|
+
allocation_id: 'eipalloc-1a2b3c4d',
|
240
|
+
association_id: 'eipassoc-1a2b3c4d',
|
241
|
+
released: true
|
242
|
+
}
|
243
|
+
end
|
244
|
+
|
245
|
+
# allocate a new elastic ip address
|
246
|
+
def allocate_elastic_ip
|
247
|
+
raise NoMethodError, "allocate_elastic_ip not yet implemented"
|
248
|
+
{
|
249
|
+
public_ip: '0.0.0.0',
|
250
|
+
allocation_id: 'eipalloc-1a2b3c4d'
|
251
|
+
}
|
252
|
+
end
|
253
|
+
|
254
|
+
# release the specified elastic ip address
|
255
|
+
def release_elastic_ip(ip, options = {})
|
256
|
+
raise NoMethodError, "release_elastic_ip not yet implemented"
|
257
|
+
{
|
258
|
+
public_ip: '0.0.0.0',
|
259
|
+
allocation_id: 'eipalloc-1a2b3c4d'
|
260
|
+
}
|
261
|
+
end
|
262
|
+
|
263
|
+
private
|
264
|
+
|
265
|
+
def interface_status(id)
|
266
|
+
resp = client.describe_network_interfaces(network_interface_ids: [id])
|
267
|
+
resp[:network_interfaces].first[:status] unless resp[:network_interfaces].empty?
|
268
|
+
end
|
269
|
+
|
270
|
+
def wait_for(timer = 5, interval = 0.1, &block)
|
271
|
+
until timer < 0 or block.call
|
272
|
+
timer -= interval
|
273
|
+
sleep interval
|
274
|
+
end
|
275
|
+
timer > 0
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
module Aws
|
3
|
+
module ENI
|
4
|
+
class Error < RuntimeError; end
|
5
|
+
class TimeoutError < Error; end
|
6
|
+
class MissingParameterError < Error; end
|
7
|
+
class InvalidParameterError < Error; end
|
8
|
+
class UnknownInterfaceError < Error; end
|
9
|
+
class EnvironmentError < Error; end
|
10
|
+
class CommandError < Error; end
|
11
|
+
class PermissionError < CommandError; end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
require 'open3'
|
3
|
+
require 'aws-eni/errors'
|
4
|
+
|
5
|
+
module Aws
|
6
|
+
module ENI
|
7
|
+
class IFconfig
|
8
|
+
|
9
|
+
class << self
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
attr_accessor :verbose
|
13
|
+
|
14
|
+
# Array-like accessor to automatically instantiate our class
|
15
|
+
def [](index)
|
16
|
+
index = $1.to_i if index.to_s =~ /^(?:eth)?([0-9]+)$/
|
17
|
+
index ||= next_available_index
|
18
|
+
@instance_cache ||= []
|
19
|
+
@instance_cache[index] ||= new("eth#{index}", false)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Purge and deconfigure non-existent interfaces from the cache
|
23
|
+
def clean
|
24
|
+
# exists? will automatically call deconfigure if necessary
|
25
|
+
@instance_cache.map!{ |dev| dev if dev.exists? }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return array of available ethernet interfaces
|
29
|
+
def existing
|
30
|
+
Dir.entries("/sys/class/net/").grep(/^eth[0-9]+$/){ |name| self[name] }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return the next unused device index
|
34
|
+
def next_available_index
|
35
|
+
for index in 0..32 do
|
36
|
+
break index unless self[index].exists?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Iterate over available ethernet interfaces (required for Enumerable)
|
41
|
+
def each(&block)
|
42
|
+
existing.each(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return array of enabled interfaces
|
46
|
+
def enabled
|
47
|
+
select(&:enabled?)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Configure all available interfaces identified by an optional selector
|
51
|
+
def configure(selector = nil, options = {})
|
52
|
+
filter(selector).reduce(0) do |count, dev|
|
53
|
+
count + dev.configure(options[:dry_run])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Remove configuration on available interfaces identified by an optional
|
58
|
+
# selector
|
59
|
+
def deconfigure(selector = nil)
|
60
|
+
filter(selector).each(&:deconfigure)
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
# Return an array of available interfaces identified by name, id,
|
65
|
+
# hwaddr, or subnet id.
|
66
|
+
def filter(match = nil)
|
67
|
+
return existing unless match
|
68
|
+
select{ |dev| dev.is?(match) }.tap do |result|
|
69
|
+
raise UnknownInterfaceError, "No interface found matching \"#{match}\"" if result.empty?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Execute a command
|
74
|
+
def exec(command, options = {})
|
75
|
+
output = nil
|
76
|
+
verbose = self.verbose || options[:verbose]
|
77
|
+
raise_errors = options[:raise_errors]
|
78
|
+
puts "ip #{command}" if verbose
|
79
|
+
|
80
|
+
Open3.popen3("/sbin/ip #{command}") do |i,o,e,t|
|
81
|
+
unless t.value.success?
|
82
|
+
error_msg = e.read
|
83
|
+
if error_msg =~ /operation not permitted/i
|
84
|
+
raise PermissionError, "Operation not permitted"
|
85
|
+
end
|
86
|
+
warn "Warning: #{error_msg}" if verbose
|
87
|
+
raise CommandError, error_msg if raise_errors
|
88
|
+
end
|
89
|
+
output = o.read
|
90
|
+
end
|
91
|
+
output
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
attr_reader :name, :device_number, :route_table
|
96
|
+
|
97
|
+
def initialize(name, auto_config = true)
|
98
|
+
unless name =~ /^eth([0-9]+)$/
|
99
|
+
raise UnknownInterfaceError, "Invalid interface: #{name}"
|
100
|
+
end
|
101
|
+
@name = name
|
102
|
+
@device_number = $1.to_i
|
103
|
+
@route_table = @device_number + 10000
|
104
|
+
configure if auto_config
|
105
|
+
end
|
106
|
+
|
107
|
+
# Get our interface's MAC address
|
108
|
+
def hwaddr
|
109
|
+
begin
|
110
|
+
exists? && IO.read("/sys/class/net/#{name}/address").strip
|
111
|
+
rescue Errno::ENOENT
|
112
|
+
end.tap do |address|
|
113
|
+
raise UnknownInterfaceError, "Unknown interface: #{name}" unless address
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Verify device exists on our system
|
118
|
+
def exists?
|
119
|
+
File.directory?("/sys/class/net/#{name}").tap do |exists|
|
120
|
+
deconfigure unless exists || @clean
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Validate and return basic interface metadata
|
125
|
+
def info
|
126
|
+
hwaddr = self.hwaddr
|
127
|
+
unless @meta_cache && hwaddr == @meta_cache[:hwaddr]
|
128
|
+
dev_path = "network/interfaces/macs/#{hwaddr}"
|
129
|
+
Meta.open_connection do |conn|
|
130
|
+
raise Meta::BadResponse unless Meta.http_get(conn, "#{dev_path}/")
|
131
|
+
@meta_cache = {
|
132
|
+
hwaddr: hwaddr,
|
133
|
+
interface_id: Meta.http_get(conn, "#{dev_path}/interface-id"),
|
134
|
+
subnet_id: Meta.http_get(conn, "#{dev_path}/subnet-id"),
|
135
|
+
subnet_cidr: Meta.http_get(conn, "#{dev_path}/subnet-ipv4-cidr-block")
|
136
|
+
}.freeze
|
137
|
+
end
|
138
|
+
end
|
139
|
+
@meta_cache
|
140
|
+
end
|
141
|
+
|
142
|
+
def interface_id
|
143
|
+
info[:interface_id]
|
144
|
+
end
|
145
|
+
|
146
|
+
def subnet_id
|
147
|
+
info[:subnet_id]
|
148
|
+
end
|
149
|
+
|
150
|
+
def subnet_cidr
|
151
|
+
info[:subnet_cidr]
|
152
|
+
end
|
153
|
+
|
154
|
+
# Return an array of configured ip addresses (primary + secondary)
|
155
|
+
def local_ips
|
156
|
+
list = exec("addr show dev #{name} primary") +
|
157
|
+
exec("addr show dev #{name} secondary")
|
158
|
+
list.lines.grep(/inet ([0-9\.]+)\/.* #{name}/i){ $1 }
|
159
|
+
end
|
160
|
+
|
161
|
+
def public_ips
|
162
|
+
ip_assoc = {}
|
163
|
+
dev_path = "network/interfaces/macs/#{hwaddr}"
|
164
|
+
Meta.open_connection do |conn|
|
165
|
+
# return an array of configured ip addresses (primary + secondary)
|
166
|
+
Meta.http_get(conn, "#{dev_path}/ipv4-associations/").to_s.each_line do |public_ip|
|
167
|
+
public_ip.strip!
|
168
|
+
local_ip = Meta.http_get(conn, "#{dev_path}/ipv4-associations/#{public_ip}")
|
169
|
+
ip_assoc[local_ip] = public_ip
|
170
|
+
end
|
171
|
+
end
|
172
|
+
ip_assoc
|
173
|
+
end
|
174
|
+
|
175
|
+
# Enable our interface
|
176
|
+
def enable
|
177
|
+
exec("link set dev #{name} up")
|
178
|
+
end
|
179
|
+
|
180
|
+
# Disable our interface
|
181
|
+
def disable
|
182
|
+
exec("link set dev #{name} down")
|
183
|
+
end
|
184
|
+
|
185
|
+
# Check whether our interface is enabled
|
186
|
+
def enabled?
|
187
|
+
exists? && exec("link show up").include?(name)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Initialize a new interface config
|
191
|
+
def configure(dry_run = false)
|
192
|
+
changes = 0
|
193
|
+
info = self.info
|
194
|
+
prefix = info[:subnet_cidr].split('/').last.to_i
|
195
|
+
gateway = IPAddr.new(info[:subnet_cidr]).succ.to_s
|
196
|
+
|
197
|
+
meta_ips = Meta.get("network/interfaces/macs/#{info[:hwaddr]}/local-ipv4s").lines.map(&:strip)
|
198
|
+
local_primary, *local_aliases = local_ips
|
199
|
+
meta_primary, *meta_aliases = meta_ips
|
200
|
+
|
201
|
+
# ensure primary ip address is correct
|
202
|
+
if name != 'eth0' && local_primary != meta_primary
|
203
|
+
unless dry_run
|
204
|
+
deconfigure
|
205
|
+
exec("addr add #{meta_primary}/#{prefix} brd + dev #{name}")
|
206
|
+
exec("route add default via #{gateway} dev #{name} table #{route_table}")
|
207
|
+
exec("route flush cache")
|
208
|
+
end
|
209
|
+
changes += 1
|
210
|
+
end
|
211
|
+
|
212
|
+
# add missing secondary ips
|
213
|
+
(meta_aliases - local_aliases).each do |ip|
|
214
|
+
exec("addr add #{ip}/#{prefix} brd + dev #{name}") unless dry_run
|
215
|
+
changes += 1
|
216
|
+
end
|
217
|
+
|
218
|
+
# remove extra secondary ips
|
219
|
+
(local_aliases - meta_aliases).each do |ip|
|
220
|
+
exec("addr del #{ip}/#{prefix} dev #{name}") unless dry_run
|
221
|
+
changes += 1
|
222
|
+
end
|
223
|
+
|
224
|
+
unless name == 'eth0'
|
225
|
+
rules_to_add = meta_ips || []
|
226
|
+
exec("rule list").lines.grep(/^([0-9]+):.*\s([0-9\.]+)\s+lookup #{route_table}/) do
|
227
|
+
unless rules_to_add.delete($2)
|
228
|
+
exec("rule delete pref #{$1}") unless dry_run
|
229
|
+
changes += 1
|
230
|
+
end
|
231
|
+
end
|
232
|
+
rules_to_add.each do |ip|
|
233
|
+
exec("rule add from #{ip} lookup #{route_table}") unless dry_run
|
234
|
+
changes += 1
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
@clean = nil
|
239
|
+
changes
|
240
|
+
end
|
241
|
+
|
242
|
+
# Remove configuration for an interface
|
243
|
+
def deconfigure
|
244
|
+
# assume eth0 primary ip is managed by dhcp
|
245
|
+
if name == 'eth0'
|
246
|
+
exec("addr flush dev eth0 secondary")
|
247
|
+
else
|
248
|
+
exec("rule list").lines.grep(/^([0-9]+):.*lookup #{route_table}/) do
|
249
|
+
exec("rule delete pref #{$1}")
|
250
|
+
end
|
251
|
+
exec("addr flush dev #{name}")
|
252
|
+
exec("route flush table #{route_table}")
|
253
|
+
exec("route flush cache")
|
254
|
+
end
|
255
|
+
@clean = true
|
256
|
+
end
|
257
|
+
|
258
|
+
# Add a secondary ip to this interface
|
259
|
+
def add_alias(ip)
|
260
|
+
prefix = info[:subnet_cidr].split('/').last.to_i
|
261
|
+
exec("addr add #{ip}/#{prefix} brd + dev #{name}")
|
262
|
+
|
263
|
+
unless name == 'eth0' || exec("rule list") =~ /from #{ip} lookup #{route_table}/
|
264
|
+
exec("rule add from #{ip} lookup #{route_table}")
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Remove a secondary ip from this interface
|
269
|
+
def remove_alias
|
270
|
+
prefix = info[:subnet_cidr].split('/').last.to_i
|
271
|
+
exec("addr del #{ip}/#{prefix} dev #{name}")
|
272
|
+
|
273
|
+
if name != 'eth0' && exec("rule list") =~ /([0-9]+):\s+from #{ip} lookup #{route_table}/
|
274
|
+
exec("rule delete pref #{$1}")
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Identify this interface by one of its attributes
|
279
|
+
def is?(match)
|
280
|
+
if match == name
|
281
|
+
true
|
282
|
+
else
|
283
|
+
info = self.info
|
284
|
+
match == info[:interface_id] || match == info[:hwaddr] || match == info[:subnet_id]
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Return an array representation of our interface config, including public
|
289
|
+
# ip associations and enabled status
|
290
|
+
def to_h
|
291
|
+
info.merge({
|
292
|
+
name: name,
|
293
|
+
device_number: device_number,
|
294
|
+
route_table: route_table,
|
295
|
+
local_ips: local_ips,
|
296
|
+
public_ips: public_ips,
|
297
|
+
enabled: enabled?
|
298
|
+
})
|
299
|
+
end
|
300
|
+
|
301
|
+
private
|
302
|
+
|
303
|
+
# Alias for static method
|
304
|
+
def exec(command, options = {})
|
305
|
+
self.class.exec(command, options)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|