diff --git a/lib/puppet/provider/network/libvirt.rb b/lib/puppet/provider/network/libvirt.rb new file mode 100644 index 0000000..7dcf721 --- /dev/null +++ b/lib/puppet/provider/network/libvirt.rb @@ -0,0 +1,177 @@ +require 'libvirt' +require 'erb' +require 'nokogiri' + +Puppet::Type.type(:network).provide(:libvirt) do + desc "Create domains with libvirt" + + mk_resource_methods + + $conn = Libvirt::open('qemu:///system') + + def self.parse_network(network) + doc = Nokogiri::XML(network.xml_desc) + definition = {} + definition[:name] = doc.at_xpath('//name').content + definition[:uuid] = doc.at_xpath('//uuid').content + if doc.at_xpath('//bridge') and doc.at_xpath('//bridge').attribute('name') + definition[:bridge] = doc.at_xpath('//bridge').attribute('name').content + end + if doc.at_xpath('//forward') + forward = doc.at_xpath('//forward') + if forward.attribute('mode') + definition[:forward_mode] = forward.attribute('mode').content + end + if forward.attribute('dev') + definition[:forward_dev] = forward.attribute('dev').content + end + if forward.xpath('//interface') + definition[:forward_interfaces] = [] + for int in forward.xpath('//interface') + definition[:forward_interfaces].push(int.attribute('dev').content) + end + end + end + if doc.xpath('//ip') + definition[:ip] = [] + definition[:ipv6] = [] + for ip in doc.xpath('//ip') + data = {} + for setting in ['address', 'netmask', 'prefix'] + if ip.attribute(setting) + data[setting] = ip.attribute(setting).content + end + end + if ip.at_xpath('//dhcp') + data[:dhcp] = {} + for setting in ['start', 'end'] + data[:dhcp][setting] = ip.at_xpath('//dhcp/range').attribute(setting).content + end + if ip.at_xpath('//dhcp/bootp') + data[:dhcp][:bootp_file] = ip.at_xpath('//dhcp/bootp').attribute('file').content + end + end + if ip.attribute('family') and ip.attribute('family').content == 'ipv6' + definition[:ipv6].push(data) + else + definition[:ip].push(data) + end + end + end + if doc.at_xpath('//mac') + definition[:mac] = doc.at_xpath('//mac').attribute('address').content + end + return definition + end + + def self.instances + networks = [] + for net in $conn.list_all_networks() do + hash = parse_network(net) + hash[:autostart] = net.autostart? + networks << new(hash) + end + return networks + end + + def self.prefetch(resources) + instances.each do |prov| + if resource = resources[prov.name] + resource.provider = prov + end + end + end + + def create + # @property_hash = @resource + end + + + def flush + debug("flushing '" + @resource[:name] + "' with: " + @property_hash.to_s ) +net_xml = < + <%= @resource[:name] %> + <% if @property_hash[:uuid] %><%= @property_hash[:uuid] %><% end %> + <% if @property_hash[:mac] %> + + <% end %> + <% if @resource[:forward_mode] %> + dev='<%= @resource[:forward_dev] %>'<%end%> mode='<%= @resource[:forward_mode] %>'<% if !@resource[:forward_interfaces] %>/<%end%>> + <% if @resource[:forward_interfaces] %> + <% @resource[:forward_interfaces].each do |dev| %> + + <% end %> + + <% end %> + <% end %> + <% if @resource[:bridge] %> + stp='on' delay='0'<%end%>/> + <% end %> + <%if @resource[:ip] %> + <% @resource[:ip].each do |ip| %> + address='<%=ip['address']%>'<%end%><% if ip['netmask']%> netmask='<%=ip['netmask']%>'<%end%><% if ip['prefix']%> prefix='<%=ip['prefix']%>'<%end%><% unless ip['dhcp'] %>/<% end %>> + <% if ip['dhcp'] %> + <% dhcp = ip['dhcp'] %> + + <% if dhcp['start'] and dhcp['end']%> + ' end='<%=dhcp['end']%>'/> + <%end%> + <% if dhcp['bootp_file']%> + '<% if dhcp['bootp_server']%> server='<%=dhcp['bootp_server']%>'<%end%>/> + <%end%> + + + <% end%> + <% end%> + <%end%> + <% if @resource[:ipv6] %> + <% @resource[:ipv6].each do |ip| %> + address='<%=ip['address']%>'<%end%><% if ip['netmask']%> netmask='<%=ip['netmask']%>'<%end%><% if ip['prefix']%> prefix='<%=ip['prefix']%>'<%end%><% unless ip['dhcp'] %>/<% end %>> + <% if ip['dhcp'] %> + <% dhcp = ip['dhcp'] %> + + <% if dhcp['start'] and dhcp['end']%> + ' end='<%=dhcp['end']%>'/> + <%end%> + + + <% end%> + <% end%> + <%end%> + +EOF + new_net_xml = ERB.new(net_xml).result(binding) + debug("generated: " + new_net_xml ) + begin + net = $conn.define_network_xml(new_net_xml) + rescue Libvirt::Error => e +puts "error", e + end + end + + def exists? + begin + net = $conn.lookup_network_by_name(name) + true + rescue Libvirt::RetrieveError => e + false + end + end + + def destroy + net = $conn.lookup_network_by_name(name) + net.destroy + net.undefine + end + + def autostart + @property_hash[:autostart] == true + end + + def autostart=(value) + net = $conn.lookup_network_by_name(name) + net.autostart = value + end + +end diff --git a/lib/puppet/type/network.rb b/lib/puppet/type/network.rb new file mode 100644 index 0000000..6ee0375 --- /dev/null +++ b/lib/puppet/type/network.rb @@ -0,0 +1,59 @@ +Puppet::Type.newtype(:network) do + @doc = "manages domains with libvirt" + + ensurable + + newparam(:name) do + desc "The name of the domain." + end + + newproperty(:autostart) do + desc "Whether to start this network at boot time" + end + + newproperty(:bridge) do + desc "Name of the bridge this network will be attached to" + end + + newproperty(:forward_mode) do + desc "One of nat, route, bridge, vepa, passthrough, private, hostdev" + #TODO nat must have an ip address + #TODO A network with forward mode='bridge' can specify a bridge name or a forward dev, but not both + end + + newproperty(:forward_dev) do + desc "The interface to forward, useful in bridge and route mode" + end + + newproperty(:forward_interfaces, :array_matching => :all) do + desc "An array of interfaces to forwad" + end + + newproperty(:ip, :array_matching => :all ) do + desc "a hash with + address + netmask (or alterntively prefix) + dhcp This is another hash that consists of + start - start of the range + end - end of the range + host - an array of hosts" + end + + newproperty(:ipv6, :array_matching => :all) do + desc "a hash with + address + netmask (or alterntively prefix) + dhcp This is another hash that consists of + start - start of the range + end - end of the range + host - an array of hosts + Note: The following options are not supported on IPv6 networks + bootp_file - A file to serve for servers booting from PXE + bootp_server - Which server that file is served from" + end + + newproperty(:mac) do + desc "mac address for the bridge" + end + +end diff --git a/manifests/network.pp b/manifests/network.pp deleted file mode 100644 index 929189a..0000000 --- a/manifests/network.pp +++ /dev/null @@ -1,151 +0,0 @@ -# Define: libvirt::network -# -# define, configure, enable and autostart a network for libvirt guests -# -# Parameters: -# $ensure -# Ensure this network is defined (present), or enabled (running), or undefined (absent) -# $autostart -# Whether to start this network at boot time -# $bridge -# Name of the bridge this network will be attached to -# $forward_mode -# One of nat, route, bridge, vepa, passthrough, private, hostdev -# $forward_dev -# The interface to forward, useful in bridge and route mode -# $forward_interfaces -# An array of interfaces to forwad -# $ip and/or $ipv6 array hashes with -# address -# netmask (or alterntively prefix) -# dhcp This is another hash that consists of -# start - start of the range -# end - end of the range -# host - an array of hosts -# Note: The following options are not supported on IPv6 networks -# bootp_file - A file to serve for servers booting from PXE -# bootp_server - Which server that file is served from -# $mac - A MAC for this network, if none is defined, libvirt will chose one for you -# -# Sample Usage : -# -# $dhcp = { -# start => '192.168.122.2', -# end => '192.168.122.254', -# bootp_file => 'pxelinux.0', -# } -# $pxe_ip = { -# 'address' => '192.168.122.2' -# 'prefix' => '24' -# 'dhcp' => $dhcp, -# } -# libvirt::network { 'pxe': -# ensure => 'enabled', -# autostart => true, -# forward_mode => 'nat', -# ip => [ $pxe_ip ], -# } -# -# libvirt::network { 'direct-net' -# ensure => 'enabled', -# autostart => true, -# forward_mode => 'bridge', -# forward_dev => 'br0', -# forward_interfaces => [ 'eth0', ], -# } -# -# $ipv6 = { -# address => '2001:db8:ca2:2::1', -# prefix => '64', -# } -# -# libvirt::network { 'dual-stack' -# ensure => 'enabled', -# autostart => true, -# forward_mode => 'nat', -# ip => [ $pxe_ip ], -# ipv6 => [ $ipv6 ], -# } -# -define libvirt::network ( - $ensure = 'present', - $autostart = false, - $bridge = undef, - $forward_mode = undef, - $forward_dev = undef, - $forward_interfaces = [], - $ip = undef, - $ipv6 = undef, - $mac = undef, -) { - validate_bool ($autostart) - validate_re ($ensure, '^(present|defined|enabled|running|undefined|absent)$', - 'Ensure must be one of defined (present), enabled (running), or undefined (absent).') - - include ::libvirt::params - - Exec { - cwd => '/', - path => '/bin:/usr/bin', - user => 'root', - provider => 'posix', - require => Service[$::libvirt::params::libvirt_service], - environment => ['LC_ALL=en_US.utf8', ], - } - - $ensure_file = $ensure? { - /(present|defined|enabled|running)/ => 'present', - /(undefined|absent)/ => 'absent', - } - - $network_file = "/etc/libvirt/qemu/networks/${title}.xml" - $autostart_file = "/etc/libvirt/qemu/networks/autostart/${title}.xml" - - case $ensure_file { - 'present': { - $content = template('libvirt/network.xml.erb') - exec { "create-${network_file}": - command => "cat > ${network_file} < $network_file, - unless => "test -f ${network_file}", - } - exec { "virsh-net-define-${title}": - command => "virsh net-define ${network_file}", - unless => "virsh -q net-list --all | grep -Eq '^\s*${title}'", - require => Exec["create-${network_file}"], - } - if $autostart { - exec { "virsh-net-autostart-${title}": - command => "virsh net-autostart ${title}", - require => Exec["virsh-net-define-${title}"], - creates => $autostart_file, - } - } - if $ensure in [ 'enabled', 'running' ] { - exec { "virsh-net-start-${title}": - command => "virsh net-start ${title}", - require => Exec["virsh-net-define-${title}"], - unless => "virsh -q net-list --all | grep -Eq '^\s*${title}\\s+active'", - } - } - } - 'absent': { - exec { "virsh-net-destroy-${title}": - command => "virsh net-destroy ${title}", - onlyif => "virsh -q net-list --all | grep -Eq '^\s*${title}\\s+active'", - } - exec { "virsh-net-undefine-${title}": - command => "virsh net-undefine ${title}", - onlyif => "virsh -q net-list --all | grep -Eq '^\s*${title}\\s+inactive'", - require => Exec["virsh-net-destroy-${title}"], - } - file { [ $network_file, $autostart_file ]: - ensure => absent, - require => Exec["virsh-net-undefine-${title}"], - } - } - default : { - fail ("${module_name} This default case should never be reached in Libvirt::Network{'${title}':} on node ${::fqdn}.") - } - } -} diff --git a/templates/network.xml.erb b/templates/network.xml.erb deleted file mode 100644 index 98be69f..0000000 --- a/templates/network.xml.erb +++ /dev/null @@ -1,49 +0,0 @@ - - <%= @name -%> - <%- if @mac -%> - - <%- end -%> - <%- if @forward_mode -%> - dev='<%= @forward_dev -%>'<%-end-%> mode='<%= @forward_mode -%>'<%-if @forward_interfaces.empty? -%>/<%-end-%>> - <%- if !@forward_interfaces.empty? -%> - <%- @forward_interfaces.each do |dev| -%> - - <%- end -%> - - <%- end -%> - <%-end -%> - <%- if @bridge -%> - stp='on' delay='0'<%-end-%>/> - <%- end -%> - <%-if @ip -%> - <%- @ip.each do |ip| -%> - address='<%=ip['address']-%>'<%-end-%><%-if ip['netmask']-%> netmask='<%=ip['netmask']-%>'<%-end-%><%-if ip['prefix']-%> prefix='<%=ip['prefix']-%>'<%-end-%><%- unless ip['dhcp'] %>/<%- end -%>> - <%- if ip['dhcp'] -%> - <%- dhcp = ip['dhcp'] -%> - - <%-if dhcp['start'] and dhcp['end']-%> - ' end='<%=dhcp['end']-%>'/> - <%-end-%> - <%-if dhcp['bootp_file']-%> - '<%-if dhcp['bootp_server']-%> server='<%=dhcp['bootp_server']-%>'<%-end-%>/> - <%-end-%> - - - <%- end-%> - <%- end-%> - <%-end-%> - <%-if @ipv6 -%> - <%- @ipv6.each do |ip| -%> - address='<%=ip['address']-%>'<%-end-%><%-if ip['netmask']-%> netmask='<%=ip['netmask']-%>'<%-end-%><%-if ip['prefix']-%> prefix='<%=ip['prefix']-%>'<%-end-%><%- unless ip['dhcp'] %>/<%- end -%>> - <%- if ip['dhcp'] -%> - <%- dhcp = ip['dhcp'] -%> - - <%-if dhcp['start'] and dhcp['end']-%> - ' end='<%=dhcp['end']-%>'/> - <%-end-%> - - - <%- end-%> - <%- end-%> - <%-end-%> - diff --git a/tests/network.pp b/tests/network.pp new file mode 100644 index 0000000..b0cae43 --- /dev/null +++ b/tests/network.pp @@ -0,0 +1,56 @@ +network { 'direct-net': + autostart => true, + forward_mode => 'bridge', + forward_dev => 'eth0', + forward_interfaces => [ 'eth0'] +} + +network { 'bridge': + bridge => 'vmbr0', + forward_mode => 'bridge' +} + +$dhcp1 = { + 'start' => '192.168.122.2', + 'end' => '192.168.122.254', + 'bootp_file' => 'pxelinux.0', +} +$ip1 = { + 'address' => '192.168.122.1', + 'netmask' => '255.255.255.0', + 'dhcp' => $dhcp1, +} +network { 'pxe': + autostart => true, + forward_mode => 'nat', + forward_dev => 'virbr0', + ip => [ $ip1] +} + + +$dhcp2 = { + 'start' => '192.168.222.2', + 'end' => '192.168.222.254', +} +$ip2 = { + 'address' => '192.168.222.1', + 'netmask' => '255.255.255.0', +} +$ipv6 = { + address => '2001:db8:ca2:2::1', + prefix => '64', +} +network { 'dual-stack': + autostart => true, + forward_mode => 'nat', + forward_dev => 'virbr2', + bridge => 'virbr2', + ip => [ $ip2], + ipv6 => [ $ipv6 ], +} + +network { 'bridge_mode': + bridge => 'vmbr4', + forward_mode => 'bridge' +} +