diff --git a/lib/fog/bin.rb b/lib/fog/bin.rb index 7e17f451e..fe2a088e1 100644 --- a/lib/fog/bin.rb +++ b/lib/fog/bin.rb @@ -61,6 +61,7 @@ require 'fog/bin/dnsmadeeasy' require 'fog/bin/ecloud' require 'fog/bin/go_grid' require 'fog/bin/google' +require 'fog/bin/libvirt' require 'fog/bin/linode' require 'fog/bin/local' require 'fog/bin/new_servers' diff --git a/lib/fog/bin/libvirt.rb b/lib/fog/bin/libvirt.rb new file mode 100644 index 000000000..b42ae67d9 --- /dev/null +++ b/lib/fog/bin/libvirt.rb @@ -0,0 +1,53 @@ +module Libvirt # deviates from other bin stuff to accomodate gem + class << self + + def class_for(key) + case key + when :compute + Fog::Compute::Libvirt + else + raise ArgumentError, "Unrecognized service: #{key}" + end + end + + def [](service) + @@connections ||= Hash.new do |hash, key| + hash[key] = case key + when :compute + Formatador.display_line("[yellow][WARN] Libvirt[:compute] is deprecated, use Compute[:libvirt] instead[/]") + Fog::Compute.new(:provider => 'Libvirt') + else + raise ArgumentError, "Unrecognized service: #{key.inspect}" + end + end + @@connections[service] + end + + def available? + availability = !Gem.source_index.find_name('ruby-libvirt').empty? + if availability + for service in services + for collection in self.class_for(service).collections + unless self.respond_to?(collection) + self.class_eval <<-EOS, __FILE__, __LINE__ + def self.#{collection} + self[:#{service}].#{collection} + end + EOS + end + end + end + end + availability + end + + def collections + services.map {|service| self[service].collections}.flatten.sort_by {|service| service.to_s} + end + + def services + Fog::Libvirt.services + end + + end +end diff --git a/lib/fog/compute.rb b/lib/fog/compute.rb index e5ca41d4f..79d534373 100644 --- a/lib/fog/compute.rb +++ b/lib/fog/compute.rb @@ -47,6 +47,9 @@ module Fog when 'VirtualBox' require 'fog/compute/virtual_box' Fog::Compute::VirtualBox.new(attributes) + when :libvirt + require 'fog/compute/libvirt' + Fog::Compute::Libvirt.new(attributes) when :voxel require 'fog/compute/voxel' Fog::Compute::Voxel.new(attributes) @@ -57,7 +60,7 @@ module Fog def self.servers servers = [] - for provider in [:aws, :bluebox, :brightbox, :ecloud, :gogrid, :linode, :newservers, :ninefold, :rackspace, :slicehost, :stormondemand, :virtualbox, :voxel] + for provider in [:aws, :bluebox, :brightbox, :ecloud, :gogrid, :libvirt, :linode, :newservers, :ninefold, :rackspace, :slicehost, :stormondemand, :virtualbox, :voxel] begin servers.concat(self[provider].servers) rescue # ignore any missing credentials/etc diff --git a/lib/fog/compute/libvirt.rb b/lib/fog/compute/libvirt.rb new file mode 100644 index 000000000..3aca18877 --- /dev/null +++ b/lib/fog/compute/libvirt.rb @@ -0,0 +1,49 @@ +module Fog + module Compute + class Libvirt < Fog::Service + + requires :libvirt_uri + + model_path 'fog/compute/models/libvirt' + model :server + collection :servers + model :network + collection :networks + model :interface + collection :interfaces + model :volume + collection :volumes + model :pool + collection :pools + + class Mock + + def initialize(options={}) + Fog::Mock.not_implemented + end + + end + + class Real + + def initialize(options={}) + @uri = options[:libvirt_uri] + + #libvirt is part of the gem => ruby-libvirt + require 'libvirt' + @connection = ::Libvirt::open(@uri) + end + + # hack to provide 'requests' + def method_missing(method_sym, *arguments, &block) + if @connection.respond_to?(method_sym) + @connection.send(method_sym, *arguments) + else + super + end + end + + end + end + end +end diff --git a/lib/fog/compute/models/libvirt/interface.rb b/lib/fog/compute/models/libvirt/interface.rb new file mode 100644 index 000000000..741a46276 --- /dev/null +++ b/lib/fog/compute/models/libvirt/interface.rb @@ -0,0 +1,46 @@ +require 'fog/core/model' + +module Fog + module Compute + class Libvirt + + class Interface < Fog::Model + + identity :id + + attribute :mac + attribute :name + attribute :xml_desc + + def destroy + requires :raw + raw.delete + true + end + + private + + def raw + @raw + end + + def raw=(new_raw) + @raw = new_raw + + raw_attributes = { + :id => new_raw.name, + :name => new_raw.name, + :mac => new_raw.mac, + :xml_desc => new_raw.xml_desc, + } + + merge_attributes(raw_attributes) + + end + + end + + end + end + +end diff --git a/lib/fog/compute/models/libvirt/interfaces.rb b/lib/fog/compute/models/libvirt/interfaces.rb new file mode 100644 index 000000000..b567e31bb --- /dev/null +++ b/lib/fog/compute/models/libvirt/interfaces.rb @@ -0,0 +1,31 @@ +require 'fog/core/collection' +require 'fog/compute/models/libvirt/interface' + +module Fog + module Compute + class Libvirt + + class Interfaces < Fog::Collection + + model Fog::Compute::Libvirt::Interface + + def all + data=[] + connection.list_interfaces.each do |ifname| + interface=connection.lookup_interface_by_name(ifname) + data << { :raw => interface } + end + load(data) + end + + # Retrieve the interface by name + def get(name) + interface=connection.lookup_interface_by_name(name) + new(:raw => interface) + end + + end #class + + end #Class + end #module +end #module diff --git a/lib/fog/compute/models/libvirt/network.rb b/lib/fog/compute/models/libvirt/network.rb new file mode 100644 index 000000000..524e5d33f --- /dev/null +++ b/lib/fog/compute/models/libvirt/network.rb @@ -0,0 +1,70 @@ +require 'fog/core/model' +require 'fog/compute/models/libvirt/util' + +module Fog + module Compute + class Libvirt + + class Network < Fog::Model + + include Fog::Compute::LibvirtUtil + + identity :id + attribute :uuid + attribute :name + attribute :bridge_name + + attribute :xml_desc + + attr_reader :template_path,:network_mode,:bridge_name + + ##https://www.redhat.com/archives/libvirt-users/2011-May/msg00091.html + # Bridged VLAN + + # https://www.redhat.com/archives/libvirt-users/2011-April/msg00006.html + # Routed network without IP + + # http://wiki.libvirt.org/page/Networking + #http://wiki.libvirt.org/page/VirtualNetworking#Virtual_network_switches + def initialize(attributes = {}) + + @template_path = attributes[:template_path] || "network.xml.erb" + @network_mode = attributes[:network_mode] || "nat" + @bridge_name = attributes[:bridge_name] || "virbr0" + template_xml + end + + def destroy() + requires :raw + raw.destroy +# raw.undefine + true + end + + private + + def raw + @raw + end + + def raw=(new_raw) + @raw = new_raw + + raw_attributes = { + :id => new_raw.uuid, + :uuid => new_raw.uuid, + :name => new_raw.name, + :bridge_name => new_raw.bridge_name, + :xml_desc => new_raw.xml_desc, + } + + merge_attributes(raw_attributes) + + end + + end + + end + end + +end diff --git a/lib/fog/compute/models/libvirt/networks.rb b/lib/fog/compute/models/libvirt/networks.rb new file mode 100644 index 000000000..359ef8727 --- /dev/null +++ b/lib/fog/compute/models/libvirt/networks.rb @@ -0,0 +1,31 @@ +require 'fog/core/collection' +require 'fog/compute/models/libvirt/network' + +module Fog + module Compute + class Libvirt + + class Networks < Fog::Collection + + model Fog::Compute::Libvirt::Network + + def all + data=[] + connection.list_networks.each do |networkname| + network=connection.lookup_network_by_name(networkname) + data << { :raw => network } + end + load(data) + end + + # Retrieve the network by uuid + def get(uuid) + network=connection.lookup_network_by_uuid(uuid) + new(:raw => network) + end + + end #class + + end #Class + end #module +end #module diff --git a/lib/fog/compute/models/libvirt/pool.rb b/lib/fog/compute/models/libvirt/pool.rb new file mode 100644 index 000000000..b3cb528df --- /dev/null +++ b/lib/fog/compute/models/libvirt/pool.rb @@ -0,0 +1,96 @@ +require 'fog/core/model' + +module Fog + module Compute + class Libvirt + + class Pool < Fog::Model + + identity :id + + attribute :name + attribute :uuid + attribute :xml + + attr_reader :allocation + attr_reader :available + attr_reader :xml_desc + attr_reader :num_of_volumes + attr_reader :state + + # There are two options to initialize a new Pool + # 1. provide :xml as an argument + # 2. provide :name, :path + def initialize(attributes={}) +# unless attributes.has_key?(:xml) +# name = attributes[:name] || raise("Must provide a pool name") +# path = attributes[:path] || "/var/lib/libvirt/images" +# else +# @xml = attributes[:xml] +# end + super + + end + + def save + unless @xml.nil? + #connection. + end + end + + def destroy + requires :raw + + # Shutdown pool if active + if raw.active? + raw.destroy + end + # Delete corresponding data in this pool + raw.delete + + true + end + + def shutdown + requires :raw + raw.destroy + true + end + + private + + def raw + @raw + end + + def raw=(new_raw) + @raw = new_raw + + raw_attributes = { + :id => new_raw.uuid, + :uuid => new_raw.uuid, + :name => new_raw.name, + :xml_desc => new_raw.xml_desc, + :num_of_volumes => new_raw.num_of_volumes, + :allocation => new_raw.info.allocation, + :available => new_raw.info.available, + :capacity => new_raw.info.capacity, + :state => new_raw.info.state, + } + + # State + #INACTIVE = INT2NUM(VIR_STORAGE_POOL_INACTIVE) virStoragePoolState + #BUILDING = INT2NUM(VIR_STORAGE_POOL_BUILDING) + #RUNNING = INT2NUM(VIR_STORAGE_POOL_RUNNING) + #DEGRADED = INT2NUM(VIR_STORAGE_POOL_DEGRADED) + #INACCESSIBLE = INT2NUM(VIR_STORAGE_POOL_INACCESSIBLE) + + merge_attributes(raw_attributes) + end + + end + + end + end + +end diff --git a/lib/fog/compute/models/libvirt/pools.rb b/lib/fog/compute/models/libvirt/pools.rb new file mode 100644 index 000000000..56daa14f0 --- /dev/null +++ b/lib/fog/compute/models/libvirt/pools.rb @@ -0,0 +1,31 @@ +require 'fog/core/collection' +require 'fog/compute/models/libvirt/pool' + +module Fog + module Compute + class Libvirt + + class Pools < Fog::Collection + + model Fog::Compute::Libvirt::Pool + + def all + data=[] + connection.list_storage_pools.each do |poolname| + pool=connection.lookup_storage_pool_by_name(poolname) + data << { :raw => pool } + end + load(data) + end + + # Retrieve the pool by uuid + def get(uuid) + pool=connection.lookup_storage_pool_by_uuid(uuid) + new(:raw => pool) + end + + end #class + + end #Class + end #module +end #module diff --git a/lib/fog/compute/models/libvirt/server.rb b/lib/fog/compute/models/libvirt/server.rb new file mode 100644 index 000000000..285adcf10 --- /dev/null +++ b/lib/fog/compute/models/libvirt/server.rb @@ -0,0 +1,237 @@ +require 'fog/core/model' +require 'fog/compute/models/libvirt/util' + +module Fog + module Compute + class Libvirt + + class Server < Fog::Model + + include Fog::Compute::LibvirtUtil + + identity :id , :aliases => 'uuid' + + attribute :memory_size + attribute :name + attribute :os, :aliases => :os_type_id + attribute :xml_desc + attribute :cpus + attribute :arch + attribute :bridge_name + + attr_writer :private_key, :private_key_path, :public_key, :public_key_path, :username + attr_reader :bridge_name,:arch, :cpus,:bridge_name, :name,:template_path,:memory_size,:os,:volume_path + + def initialize(attributes = {}) + super + end + + def create(attributes = {}) + @name = attributes[:name] || raise("we need a name") + @bridge_name = attributes[:bridge_name] || "br0" + @cpus = attributes[:cpus] || 1 + @memory_size = attributes[:memory_size] || 256 + @username = attributes[:username] || "mccloud" + @os = attributes[:os] || "hvm" + @arch = attributes[:arch] || "x86_64" + @template_path = attributes[:template_path] || "guest.xml.erb" + # super + volume=connection.volumes.get("ubuntu-10_10_amd64.qcow2").clone("#{name}.qcow2") + @volume_path=volume.path + connection.define_domain_xml(template_xml) + + end + + def start + requires :raw + + unless @raw.active? + begin + @raw.create + rescue + print "An error occured :",$!,"\n" + end + end + end + + + def destroy(options={ :destroy_volumes => false}) + + #connection.volumes(name).destroy + requires :raw + if @raw.active? + @raw.destroy + end + @raw.undefine + end + + + def ready? + + status == :running + end + + def reboot + requires :raw + + @raw.reboot + end + + + def halt + requires :raw + + @raw.shutdown + end + + def resume + requires :raw + + @raw.resume + end + + def suspend + requires :raw + + @raw.suspend + end + + def status + state=case @raw.info.state + when 0 then :nostate + when 1 then :running + when 2 then :paused + when 3 then :shuttingdown + when 4 then :shutoff + when 5 then :crashed + end + return state + end + + def save() + + raise Fog::Errors::Error.new('Updating an existing server is not yet implemented. Contributions welcome!') + end + + def scp(local_path, remote_path, upload_options = {}) + raise 'Not Implemented' + requires :addresses, :username + + scp_options = {} + scp_options[:key_data] = [private_key] if private_key + Fog::SCP.new(addresses['public'].first, username, options).upload(local_path, remote_path, scp_options) + end + + def setup(credentials = {}) + requires :addresses, :identity, :public_key, :username + Fog::SSH.new(addresses['public'].first, username, credentials).run([ + %{mkdir .ssh}, + %{echo "#{public_key}" >> ~/.ssh/authorized_keys}, + %{echo "#{attributes.to_json}" >> ~/attributes.json}, + %{echo "#{metadata.to_json}" >> ~/metadata.json} + ]) + rescue Errno::ECONNREFUSED + sleep(1) + retry + end + + + ##Note this requires arpwatch to be running + ##and chmod o+x /var/lib/arpwatch + + def addresses + mac=self.mac + options={} + ipaddress=nil + if connected_by_ssh? + #command="arp -an|grep #{mac}|cut -d ' ' -f 2| cut -d '(' -f 2| cut -d ')' -f 1" + #command="grep #{mac} /var/log/daemon.log |sed -e 's/^.*address //'|cut -d ' ' -f 1" + command="grep #{mac} /var/lib/arpwatch/arp.dat|cut -f 2" + result=Fog::SSH.new(connection.hostname, "patrick.debois", options).run(command) + if result.first.status == 0 + ipaddress=result.first.stdout.strip + #TODO check for valid IP + #TODO check time validity + else + #cat /var/log/daemon.log|grep "52:54:00:52:f6:22"| + end + else + #local execute arp -an to get the ip + end + return { 'public' => [ipaddress], 'private' => [ipaddress]} + end + + + def ssh(commands) + requires :addresses, :identity, :username + + options = {} + #options[:key_data] = [private_key] if private_key + + require 'net/ssh/proxy/command' + options={ :password => "mccloud"} + if connected_by_ssh? + relay=connection.hostname + proxy = Net::SSH::Proxy::Command.new('ssh -l patrick.debois '+relay+' nc %h %p') + options[:proxy]= proxy + end + #Fog::SSH.new("192.168.122.48", "vagrant", options).run(commands) + Fog::SSH.new(addresses['public'].first, "mccloud", options).run(commands) + + end + + def stop + requires :raw + + @raw.shutdown + end + + def username + @username ||= 'root' + end + + def mac + require "rexml/document" + require 'erb' + + mac = document("domain/devices/interface/mac", "address") + return mac + end + + private + + def raw + @raw + end + + def raw=(new_raw) + @raw = new_raw + + raw_attributes = { + :id => new_raw.uuid, + :memory_size => new_raw.info.max_mem , + :name => new_raw.name, + :cpus => new_raw.info.nr_virt_cpu, + :os_type_id => new_raw.os_type, + :xml_desc => new_raw.xml_desc, + + } + + merge_attributes(raw_attributes) + end + + # finds a value from xml + def document path, attribute=nil + xml = REXML::Document.new(xml_desc) + attribute.nil? ? xml.elements[path].text : xml.elements[path].attributes[attribute] + end + + def connected_by_ssh? + return connection.uri.include?("+ssh") + end + end + + end + end + +end diff --git a/lib/fog/compute/models/libvirt/servers.rb b/lib/fog/compute/models/libvirt/servers.rb new file mode 100644 index 000000000..1eadb4531 --- /dev/null +++ b/lib/fog/compute/models/libvirt/servers.rb @@ -0,0 +1,52 @@ +require 'fog/core/collection' +require 'fog/compute/models/libvirt/server' + +module Fog + module Compute + class Libvirt + + class Servers < Fog::Collection + + model Fog::Compute::Libvirt::Server + + def all + + data = connection.list_defined_domains.map do |machine| + { + :raw => connection.lookup_domain_by_name(machine) + } + end + + connection.list_domains.each do |machine| + data << { + :raw => connection.lookup_domain_by_id(machine) + } + end + load(data) + end + + def bootstrap(new_attributes = {}) + raise 'Not Implemented' + # server = create(new_attributes) + # server.start + # server.wait_for { ready? } + # server.setup(:password => server.password) + # server + end + + # Retrieve the server by uuid + def get(server_id) + machine=connection.lookup_domain_by_uuid(server_id) + new(:raw => machine) + end + + # Retrieve the server by name + def get_by_name(name) + machine=connection.lookup_domain_by_name(name) + new(:raw => machine) + end + + end #class + end #Class + end #module +end #Module diff --git a/lib/fog/compute/models/libvirt/templates/guest.xml.erb b/lib/fog/compute/models/libvirt/templates/guest.xml.erb new file mode 100644 index 000000000..fdb90a0f8 --- /dev/null +++ b/lib/fog/compute/models/libvirt/templates/guest.xml.erb @@ -0,0 +1,37 @@ + + <%= name %> + <%= memory_size %> + <%= cpus %> + + <%= os %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/fog/compute/models/libvirt/templates/network.xml.erb b/lib/fog/compute/models/libvirt/templates/network.xml.erb new file mode 100644 index 000000000..977557f5d --- /dev/null +++ b/lib/fog/compute/models/libvirt/templates/network.xml.erb @@ -0,0 +1,6 @@ + + <%= name %> + + + + \ No newline at end of file diff --git a/lib/fog/compute/models/libvirt/templates/pool.xml.erb b/lib/fog/compute/models/libvirt/templates/pool.xml.erb new file mode 100644 index 000000000..e70137e11 --- /dev/null +++ b/lib/fog/compute/models/libvirt/templates/pool.xml.erb @@ -0,0 +1,6 @@ + + <%= name %> + + <%= path %> + + \ No newline at end of file diff --git a/lib/fog/compute/models/libvirt/templates/volume.xml b/lib/fog/compute/models/libvirt/templates/volume.xml new file mode 100644 index 000000000..ca94c34c5 --- /dev/null +++ b/lib/fog/compute/models/libvirt/templates/volume.xml @@ -0,0 +1,14 @@ + + ubuntu.qcow2 + 10 + 10 + + + + 0 + 0 + 0744 + + + + \ No newline at end of file diff --git a/lib/fog/compute/models/libvirt/util.rb b/lib/fog/compute/models/libvirt/util.rb new file mode 100644 index 000000000..b3c19289a --- /dev/null +++ b/lib/fog/compute/models/libvirt/util.rb @@ -0,0 +1,28 @@ +require "rexml/document" +require 'erb' + +module Fog + module Compute + module LibvirtUtil + # return templated xml to be used by libvirt + def template_xml + ERB.new(template, nil, '-').result(binding) + end + + private + # template file that contain our xml template + def template + File.read("#{File.dirname(__FILE__)}/templates/#{template_path}") + rescue => e + warn "failed to read template #{template_path}: #{e}" + end + + # finds a value from xml + def document path, attribute=nil + return nil if new? + xml = REXML::Document.new(@xml_desc) + attribute.nil? ? xml.elements[path].text : xml.elements[path].attributes[attribute] + end + end + end +end \ No newline at end of file diff --git a/lib/fog/compute/models/libvirt/volume.rb b/lib/fog/compute/models/libvirt/volume.rb new file mode 100644 index 000000000..bd2a241f9 --- /dev/null +++ b/lib/fog/compute/models/libvirt/volume.rb @@ -0,0 +1,70 @@ +require 'fog/core/model' + +module Fog + module Compute + class Libvirt + + class Volume < Fog::Model + + identity :id + + attribute :key + attribute :path + attribute :name + attribute :type + attribute :allocation + attribute :capacity + attribute :xml_desc + + def destroy + requires :raw + raw.delete + true + end + + def wipe + requires :raw + raw.wipe + true + end + + def clone(name) + pool=@raw.pool + xml = REXML::Document.new(xml_desc) + xml.root.elements['/volume/name'].text=name + xml.root.elements['/volume/key'].text=name + xml.delete_element('/volume/target/path') + puts "cloning this might take a while" + pool.create_volume_xml_from(xml.to_s,@raw) + return connection.volumes.get(name) + end + + private + + def raw + @raw + end + + def raw=(new_raw) + @raw = new_raw + + raw_attributes = { + :id => new_raw.key, + :key => new_raw.key, + :path => new_raw.path , + :name => new_raw.name, + :xml_desc => new_raw.xml_desc, + :allocation => new_raw.info.allocation, + :capacity => new_raw.info.capacity, + :type => new_raw.info.type + } + + merge_attributes(raw_attributes) + end + + end + + end + end + +end diff --git a/lib/fog/compute/models/libvirt/volumes.rb b/lib/fog/compute/models/libvirt/volumes.rb new file mode 100644 index 000000000..53fc18519 --- /dev/null +++ b/lib/fog/compute/models/libvirt/volumes.rb @@ -0,0 +1,40 @@ +require 'fog/core/collection' +require 'fog/compute/models/libvirt/volume' + +module Fog + module Compute + class Libvirt + + class Volumes < Fog::Collection + + model Fog::Compute::Libvirt::Volume + + def all + data=[] + connection.list_storage_pools.each do |poolname| + pool=connection.lookup_storage_pool_by_name(poolname) + pool.list_volumes.each do |volumename| + data << { :raw => pool.lookup_volume_by_name(volumename) } + end + end + load(data) + end + + # Retrieve the volume by uuid + def get(key) + connection.list_storage_pools.each do |poolname| + pool=connection.lookup_storage_pool_by_name(poolname) + volume=pool.lookup_volume_by_name(key) + unless volume.nil? + return new(:raw => volume) + end + end + + return nil + end + + end + + end + end +end diff --git a/lib/fog/providers.rb b/lib/fog/providers.rb index 9d42d9ada..e48f83138 100644 --- a/lib/fog/providers.rb +++ b/lib/fog/providers.rb @@ -14,6 +14,7 @@ require 'fog/providers/dnsmadeeasy' require 'fog/providers/ecloud' require 'fog/providers/go_grid' require 'fog/providers/google' +require 'fog/providers/libvirt' require 'fog/providers/linode' require 'fog/providers/local' require 'fog/providers/new_servers' diff --git a/lib/fog/providers/libvirt.rb b/lib/fog/providers/libvirt.rb new file mode 100644 index 000000000..eb567a1b6 --- /dev/null +++ b/lib/fog/providers/libvirt.rb @@ -0,0 +1,11 @@ +require 'fog/core' + +module Fog + module Libvirt + + extend Fog::Provider + + service(:compute, 'compute/libvirt') + + end +end