-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvirsh-nat-bridge
executable file
·331 lines (297 loc) · 8.93 KB
/
virsh-nat-bridge
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#!/usr/bin/env ruby
#
# Copyright 2013 Victor Penso
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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, see <http://www.gnu.org/licenses/>.
#
require 'ostruct'
require 'getoptlong'
require 'erb'
require 'tempfile'
require 'logger'
exec_name = File.split(__FILE__)[-1]
HELP = <<EOF
Usage:
#{exec_name} [-DhOv] [-b bridge] [-d domain]
[-m mac-addresses] [-N network]
[-n node[,node,..]] command
Configures and deploys a network configuration for LibVirt
called 'nat_bridge', Lookup of IP- and MAC-addresses for a
given hostname.
Commands:
li,list
List all available FQDNs and IP-addresses (dnsmasq
host list format).
lo,lookup hostname|ip|mac
Show the configuration for a given MAC-, IP-address
and hostname.
st,status
Show the state of the network.
start
Add the configuration to LibVirt and start the network.
stop
Stop the network and remove the configuration from
LibVirt.
Options:
-b,--bridge name
Network bridge used as NAT gateway (default nbr0).
-d,--debug
Show stacktraces in case of errors.
-D,--domain name
Domain name used for the hosts in the defined
network (default 'devops.test').
-h,--help
Show this help information.
-m,--mac-addresses prefix
Mac-address prefix used for the virtual machines
(default 02:FF:0A:0A:06)
-N,--network prefix
IP-address prefix used for the virtual machines
(default 10.1.1)
-O,--no-default-nodes
Don't add the list of default host names to the
DNS configuration. Use option -n instead
-n,--nodes name[,name,...]
List of host names to added to the DNS.
--version
Print program version.
EOF
class System
def self.exec(command)
command + ' 2>&1'
unless $options.command_prefix.nil?
command = %Q[#{$options.command_prefix} #{command}]
end
$logger.debug "Exec [#{command}]"
# Execute command as subprocess and return the exit code
pipe = IO.popen(command)
# Get the process ID from the child
pid = pipe.pid
# Read the output from the stream
output = pipe.read
# Wait for successful return and pass back the code to the caller
Process.wait(pid)
state=$?
$logger.debug "Returned with #{state}"
if state == 0
return output.gsub(/^$\n/, '')
else
$logger.warn "Failed to execute [#{command}]"
return nil
end
end
end
module Virsh
class Network
def self.config
# First used IP address in network
ip = 5
# hostname lookup
hosts = Array.new
$options.nodes.each do |node|
hosts << %Q[<host ip="#{$options.network}.#{ip}"><hostname>#{node}</hostname></host>]
ip += 1
next if ip > 100
end
# resolve IP
ip = 5
ips = Array.new
$options.nodes.each do |node|
bytes = "%02X" % ip
mac = "#{$options.mac}:#{bytes}"
ips << %Q[<host mac="#{mac}" name="#{node}.#{$options.domain}" ip="#{$options.network}.#{ip}"/>]
ip += 1
next if ip > 100
end
config = ERB.new(<<-EOF
<network>
<name><%= $options.name %></name>
<bridge name="<%= $options.bridge %>" />
<forward mode="nat"/>
<domain name="<%= $options.domain %>"/>
<dns>
<%= hosts.join("\n ") %>
</dns>
<ip address="<%= $options.network %>.1" netmask="<%= $options.netmask %>">
<dhcp>
<range start="<%= $options.network %>.100" end="<%= $options.network %>.254" />
<%= ips.join("\n ")%>
</dhcp>
</ip>
</network>
EOF
).result(binding)
return config.gsub(/^ /,'')
end
def self.list
if self.active?
config = System::exec("virsh net-dumpxml #{$options.name} | grep 'host mac'")
config.split("\n").each do |line|
mac,name,ip = line.gsub(/^ *<host mac='/,'').gsub(/' name='/,' ').gsub(/' ip='/,' ').gsub(/' ?\/>/,'').split
$stdout.puts "#{mac},#{name},#{ip}"
end
else
$stdout.puts "Network [#{$options.name}] not configured."
end
end
def self.lookup(node)
config = `virsh net-dumpxml #{$options.name} | grep -o '^ *<host mac=.*#{node}.*/>$'`
if config.empty?
$stderr.puts "#{node} not defined!"
exit 1
end
mac,name,ip = config.gsub(/^ *<host mac='/,'').gsub(/' name='/,' ').gsub(/' ip='/,' ').gsub(/' ?\/>/,'').split
$stdout.puts "#{name} #{ip} #{mac}"
end
def self.status
if self.active?
$stdout.puts System::exec("virsh net-info #{$options.name}")
else
$stdout.puts "Network [#{$options.name}] not configured."
end
end
def self.start
path = "/tmp/libvirt_network.xml.#{Time.now.to_i}"
File.open(path,'w') do |file|
file.puts self.config
end
System::exec("virsh net-define #{path}")
System::exec("virsh net-start #{$options.name}")
System::exec("virsh net-autostart #{$options.name}")
end
def self.stop
System::exec("virsh net-destroy #{$options.name}")
System::exec("virsh net-undefine #{$options.name}")
end
def self.active?
if System::exec("virsh net-list | grep #{$options.name}").nil?
false
else
true
end
end
end
end
begin
stdin = $stdin.tty? ? String.new : $stdin.read
$options = OpenStruct.new
$options.debug = false
$options.name = 'nat_bridge'
$options.bridge = 'nbr0'
$options.domain = 'devops.test'
$options.network = '10.1.1'
$options.netmask = '255.255.255.0'
$options.mac = '02:FF:0A:0A:06'
$options.nodes = %w(
lxdns01 lxdns02
lxcm01 lxcm02
lxcc01 lxcc02 lxcc03 lxcc04
lxrm01 lxrm02
lxb001 lxb002 lxb003 lxb004 lxb005 lxb006 lxb007 lxb008 lxb009 lxb010
lxmon01 lxmon02 lxmon03 lxmon04 lxmon05
lxdev01 lxdev02 lxdev03 lxdev04
lxfs01 lxfs02 lxfs03 lxfs04 lxfs05
lxhvs01 lxhvs02 lxhvs03 lxhvs04
lxdb01 lxdb02 lxdb03
lxrepo01 lxrepo02 lxrepo03
)
$logger = Logger.new($stderr)
# Adjust the time format used for the logger
$logger.datetime_format = "%Y-%m-%d %H:%M:%S "
$logger.formatter = proc do |severity, datetime, progname, message|
"[#{datetime.strftime($logger.datetime_format)}] #{severity} -- #{message}\n"
end
$logger.level = Logger::FATAL
nodes = String.new
GetoptLong.new(
['--bridge','-b',GetoptLong::REQUIRED_ARGUMENT],
['--domain','-D',GetoptLong::REQUIRED_ARGUMENT],
['--debug','-d',GetoptLong::NO_ARGUMENT],
['--help','-h',GetoptLong::NO_ARGUMENT],
['--log-level','-L',GetoptLong::REQUIRED_ARGUMENT],
['--mac-addresses','-m',GetoptLong::REQUIRED_ARGUMENT],
['--network','-N',GetoptLong::REQUIRED_ARGUMENT],
['--no-default-nodes','-O',GetoptLong::NO_ARGUMENT],
['--nodes','-n',GetoptLong::REQUIRED_ARGUMENT],
['--version',GetoptLong::NO_ARGUMENT]
).each do |opt,arg|
case opt
when '--bridge'
$options.bridge = arg
when '--debug'
$options.debug = true
$logger.level = Logger::DEBUG
when '--domain'
$options.domain = arg
when '--help'
$stdout.puts HELP
exit 0
when '--log-level'
$logger.level = case arg
when 'warn'
Logger::WARN
when 'debug'
Logger::DEBUG
when 'fatal'
Logger::FATAL
else
Logger::INFO
end
when '--mac-addresses'
$options.mac = arg
when '--network'
$options.network = arg
when '--no-default-nodes'
$options.nodes = Array.new
when '--nodes'
nodes = arg
when '--version'
$stdout.puts 0.2
exit 0
end
end
# Add the nodes defined by optional parameters
nodes.split(',').each do |node|
$options.nodes << node
end
command = ARGV.shift || raise('Expecting command argument!')
case command
when 'config'
$stdout.puts Virsh::Network.config
when 'status','st'
Virsh::Network.status
when 'start'
Virsh::Network.start
when 'stop'
Virsh::Network.stop
when 'list','li'
Virsh::Network.list
when 'lookup','lo'
node = ARGV.shift || raise('Expecting name argument!')
Virsh::Network.lookup node
else
raise("Command #{command} not supported!")
end
rescue => exc
$stderr.puts "ERROR: #{exc.message}"
$stderr.puts " use -h for detailed instructions"
if $options.debug
$stderr.puts '-- Stack Trace --'
$stderr.puts exc.backtrace
else
$stderr.puts 'You may want run this in debug mode with \'-d\''
end
exit 1
end
exit 0