Skip to content

Commit 21d1b71

Browse files
committed
Retrieve VM IP address using libvirt
Requires ruby-libvirt 0.6.0 plus one additional commit: http://libvirt.org/git/?p=ruby-libvirt.git;a=commit;h=c2d4192ebf28b8030b753b715a72f0cdf725d313
1 parent 31a6b1d commit 21d1b71

File tree

9 files changed

+87
-90
lines changed

9 files changed

+87
-90
lines changed

lib/fog/libvirt/compute.rb

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Libvirt < Fog::Service
4040
request :clone_volume
4141
request :list_networks
4242
request :destroy_network
43+
request :dhcp_leases
4344
request :list_interfaces
4445
request :destroy_interface
4546
request :get_node_info

lib/fog/libvirt/models/compute/network.rb

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ def initialize(attributes = {})
1616
super
1717
end
1818

19+
def dhcp_leases(mac, flags = 0)
20+
service.dhcp_leases(uuid, mac, flags)
21+
end
22+
1923
def save
2024
raise Fog::Errors::Error.new('Creating a new network is not yet implemented. Contributions welcome!')
2125
end

lib/fog/libvirt/models/compute/server.rb

+10-78
Original file line numberDiff line numberDiff line change
@@ -263,88 +263,20 @@ def cloud_init_volume_name
263263
def addresses(service_arg=service, options={})
264264
mac=self.mac
265265

266-
# Aug 24 17:34:41 juno arpwatch: new station 10.247.4.137 52:54:00:88:5a:0a eth0.4
267-
# Aug 24 17:37:19 juno arpwatch: changed ethernet address 10.247.4.137 52:54:00:27:33:00 (52:54:00:88:5a:0a) eth0.4
268-
# Check if another ip_command string was provided
269-
ip_command_global=service_arg.ip_command.nil? ? 'grep $mac /var/log/arpwatch.log|sed -e "s/new station//"|sed -e "s/changed ethernet address//g" |sed -e "s/reused old ethernet //" |tail -1 |cut -d ":" -f 4-| cut -d " " -f 3' : service_arg.ip_command
270-
ip_command_local=options[:ip_command].nil? ? ip_command_global : options[:ip_command]
271-
272-
ip_command="mac=#{mac}; server_name=#{name}; "+ip_command_local
273-
274-
ip_address=nil
275-
276-
if service_arg.uri.ssh_enabled?
277-
278-
# Retrieve the parts we need from the service to setup our ssh options
279-
user=service_arg.uri.user #could be nil
280-
host=service_arg.uri.host
281-
keyfile=service_arg.uri.keyfile
282-
port=service_arg.uri.port
283-
284-
# Setup the options
285-
ssh_options={}
286-
ssh_options[:keys]=[ keyfile ] unless keyfile.nil?
287-
ssh_options[:port]=port unless keyfile.nil?
288-
ssh_options[:paranoid]=true if service_arg.uri.no_verify?
289-
290-
begin
291-
result=Fog::SSH.new(host, user, ssh_options).run(ip_command)
292-
rescue Errno::ECONNREFUSED
293-
raise Fog::Errors::Error.new("Connection was refused to host #{host} to retrieve the ip_address for #{mac}")
294-
rescue Net::SSH::AuthenticationFailed
295-
raise Fog::Errors::Error.new("Error authenticating over ssh to host #{host} and user #{user}")
296-
end
297-
298-
# Check for a clean exit code
299-
if result.first.status == 0
300-
ip_address=result.first.stdout.strip
301-
else
302-
# We got a failure executing the command
303-
raise Fog::Errors::Error.new("The command #{ip_command} failed to execute with a clean exit code")
304-
end
305-
306-
else
307-
# It's not ssh enabled, so we assume it is
308-
if service_arg.uri.transport=="tls"
309-
raise Fog::Errors::Error.new("TlS remote transport is not currently supported, only ssh")
310-
end
311-
312-
# Execute the ip_command locally
313-
# Initialize empty ip_address string
314-
ip_address=""
315-
316-
IO.popen("#{ip_command}") do |p|
317-
p.each_line do |l|
318-
ip_address+=l
319-
end
320-
status=Process.waitpid2(p.pid)[1].exitstatus
321-
if status!=0
322-
raise Fog::Errors::Error.new("The command #{ip_command} failed to execute with a clean exit code")
266+
ip_address = nil
267+
nic = self.nics.find {|nic| nic.mac==mac}
268+
if !nic.nil?
269+
Fog::Compute[:libvirt].networks.all.each do |net|
270+
if net.name == nic.network
271+
leases = net.dhcp_leases(mac, 0)
272+
# Assume the lease expiring last is the current IP address
273+
ip_address = leases.sort_by { |lse| lse["expirytime"] }.last["ipaddr"] if !leases.empty?
274+
break
323275
end
324276
end
325-
326-
#Strip any new lines from the string
327-
ip_address=ip_address.chomp
328-
end
329-
330-
# The Ip-address command has been run either local or remote now
331-
332-
if ip_address==""
333-
#The grep didn't find an ip address result"
334-
ip_address=nil
335-
else
336-
# To be sure that the command didn't return another random string
337-
# We check if the result is an actual ip-address
338-
# otherwise we return nil
339-
unless ip_address=~/^(\d{1,3}\.){3}\d{1,3}$/
340-
raise Fog::Errors::Error.new(
341-
"The result of #{ip_command} does not have valid ip-address format\n"+
342-
"Result was: #{ip_address}\n"
343-
)
344-
end
345277
end
346278

347-
return { :public => [ip_address], :private => [ip_address]}
279+
return { :public => [ip_address], :private => [ip_address] }
348280
end
349281

350282
def ip_address(key)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
require 'socket'
2+
3+
module Fog
4+
module Compute
5+
class Libvirt
6+
class Real
7+
def dhcp_leases(uuid, mac, flags = 0)
8+
client.lookup_network_by_uuid(uuid).dhcp_leases(mac, flags)
9+
end
10+
end
11+
12+
class Mock
13+
def dhcp_leases(uuid, mac, flags = 0)
14+
leases1 = {
15+
'aa:bb:cc:dd:ee:ff' => [
16+
{ 'type' => Socket::AF_INET, 'ipaddr' => '1.2.3.4', 'prefix' => 24, 'expirytime' => 5000 },
17+
{ 'type' => Socket::AF_INET, 'ipaddr' => '1.2.5.6', 'prefix' => 24, 'expirytime' => 5005 }
18+
]
19+
}
20+
leases2 = {
21+
'99:88:77:66:55:44' => [
22+
{ 'type' => Socket::AF_INET, 'ipaddr' => '10.1.1.5', 'prefix' => 24, 'expirytime' => 50 }
23+
]
24+
}
25+
networks = {
26+
# should match mock net uuid from list_networks.rb
27+
'a29146ea-39b2-412d-8f53-239eef117a32' => leases1,
28+
'fbd4ac68-cbea-4f95-86ed-22953fd92384' => leases2
29+
}
30+
if !networks[uuid].nil?
31+
return networks[uuid][mac]
32+
end
33+
end
34+
end
35+
end
36+
end
37+
end

lib/fog/libvirt/requests/compute/list_networks.rb

+11-11
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,17 @@ def network_to_attributes(net)
3737

3838
class Mock
3939
def list_networks(filters={ })
40-
net1 = mock_network 'net1'
41-
net2 = mock_network 'net2'
42-
[net1, net2]
43-
end
44-
45-
def mock_network name
46-
{
47-
:uuid => 'net.uuid',
48-
:name => name,
49-
:bridge_name => 'net.bridge_name'
50-
}
40+
[ {
41+
:uuid => 'a29146ea-39b2-412d-8f53-239eef117a32',
42+
:name => 'net1',
43+
:bridge_name => 'virbr0'
44+
},
45+
{
46+
:uuid => 'fbd4ac68-cbea-4f95-86ed-22953fd92384',
47+
:name => 'net2',
48+
:bridge_name => 'virbr1'
49+
}
50+
]
5151
end
5252
end
5353
end

tests/libvirt/compute_tests.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
tests("Compute requests") do
1212
%w{ create_domain create_volume define_domain define_pool destroy_interface destroy_network get_node_info list_domains
13-
list_interfaces list_networks list_pools list_pool_volumes list_volumes pool_action vm_action volume_action }.each do |request|
13+
list_interfaces list_networks list_pools list_pool_volumes list_volumes pool_action vm_action volume_action
14+
dhcp_leases }.each do |request|
1415
test("it should respond to #{request}") { compute.respond_to? request }
1516
end
1617
end

tests/libvirt/models/compute/network_tests.rb

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
tests('The network model should') do
77
tests('have the action') do
88
test('reload') { network.respond_to? 'reload' }
9+
test('dhcp_leases') { network.respond_to? 'dhcp_leases' }
10+
end
11+
tests('have a dhcp_leases action that') do
12+
test('returns an array') { network.dhcp_leases('99:88:77:66:55:44', 0).kind_of? Array }
913
end
1014
tests('have attributes') do
1115
model_attribute_hash = network.attributes

tests/libvirt/models/compute/server_tests.rb

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
}
2121
end
2222
end
23+
tests('have an ip_address action that') do
24+
test('returns the latest IP address lease') { server.public_ip_address() == '1.2.5.6' }
25+
end
2326
tests('have attributes') do
2427
model_attribute_hash = server.attributes
2528
attributes = [ :id,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Shindo.tests("Fog::Compute[:libvirt] | dhcp_leases request", 'libvirt') do
2+
3+
compute = Fog::Compute[:libvirt]
4+
5+
tests("DHCP leases response") do
6+
response = compute.dhcp_leases("fbd4ac68-cbea-4f95-86ed-22953fd92384", "99:88:77:66:55:44", 0)
7+
test("should be an array") { response.kind_of? Array }
8+
test("should have one element") { response.length == 1 }
9+
test("should have dict elements") { response[0].kind_of? Hash }
10+
["ipaddr", "prefix", "expirytime", "type"].each {
11+
|k| test("should have dict elements with required key #{k}") { !response[0][k].nil? }
12+
}
13+
end
14+
15+
end

0 commit comments

Comments
 (0)