commit 132a991c8c441c86864a35c666dd41748213bdaf
Author: Ohad Levy <ohadlevy@gmail.com>
Date:   Tue Jul 17 16:58:10 2012 +0300

    basic openstack support

diff --git a/app/helpers/compute_resources_vms_helper.rb b/app/helpers/compute_resources_vms_helper.rb
index 20e68fa43..ed8315799 100644
--- a/app/helpers/compute_resources_vms_helper.rb
+++ b/app/helpers/compute_resources_vms_helper.rb
@@ -11,8 +11,10 @@ module ComputeResourcesVmsHelper
         case value
         when Array
           value.to_sentence
-        when Fog::Time
+        when Fog::Time, Time
           time_ago_in_words(value) + " ago"
+        when nil
+            "N/A"
         else
           value.to_s
         end
diff --git a/app/models/compute_resource.rb b/app/models/compute_resource.rb
index 87c2f467b..10728454c 100644
--- a/app/models/compute_resource.rb
+++ b/app/models/compute_resource.rb
@@ -1,6 +1,6 @@
 require 'fog_extensions'
 class ComputeResource < ActiveRecord::Base
-  PROVIDERS = %w[ Libvirt Ovirt EC2 Vmware ].delete_if{|p| p == "Libvirt" && !SETTINGS[:libvirt]}
+  PROVIDERS = %w[ Libvirt Ovirt EC2 Vmware Openstack].delete_if{|p| p == "Libvirt" && !SETTINGS[:libvirt]}
   audited :except => [:password, :attrs]
   serialize :attrs, Hash
 
@@ -73,7 +73,7 @@ class ComputeResource < ActiveRecord::Base
 
   def provider_friendly_name
     list = SETTINGS[:libvirt] ? ["Libvirt"] : []
-    list += %w[ oVirt EC2 VMWare ]
+    list += %w[ oVirt EC2 VMWare OpenStack ]
     list[PROVIDERS.index(provider)] rescue ""
   end
 
diff --git a/app/views/compute_resources/form/_openstack.html.erb b/app/views/compute_resources/form/_openstack.html.erb
new file mode 100644
index 000000000..d6398d2ef
--- /dev/null
+++ b/app/views/compute_resources/form/_openstack.html.erb
@@ -0,0 +1,9 @@
+<%= text_f f, :url, :label => "Authentication URL", :class => "input-xlarge", :help_block => "e.g. http://openstack:5000/v2.0/tokens" %>
+<%= text_f f, :user, :label => "Access Key" %>
+<%= password_f f, :password, :label => "Secret Key" %>
+
+<% tenants = f.object.tenants rescue [] %>
+<%= selectable_f(f, :tenant, tenants, {}, {:label => 'Tenant', :disabled => tenants.empty?,
+                 :help_inline => link_to_function(tenants.empty? ? "Load Tenants" : "Test Connection", "testConnection(this)",
+                 :class => "btn + #{tenants.empty? ? "" : "btn-success"}",
+                 :'data-url' => test_connection_compute_resources_path) + image_tag('spinner.gif', :id => 'test_connection_indicator', :class => 'hide').html_safe }) %>
diff --git a/app/views/compute_resources_vms/form/_openstack.html.erb b/app/views/compute_resources_vms/form/_openstack.html.erb
new file mode 100644
index 000000000..21862814e
--- /dev/null
+++ b/app/views/compute_resources_vms/form/_openstack.html.erb
@@ -0,0 +1,6 @@
+<%= select_f f, :flavor_ref, compute_resource.flavors, :id, :name, {}, :label => 'Flavor' %>
+<div id='image_selection'><%= select_f f, :image_ref, compute_resource.images, :id, :name, {:include_blank =>'Please Select an Image'}, :label => "Image" %></div>
+<%= select_f f, :tenant_id, compute_resource.tenants, :id, :name %>
+
+<%= select_f f, :security_groups, compute_resource.security_groups, :name, :name, {}, :label => "Security Group"  %>
+
diff --git a/app/views/compute_resources_vms/index/_openstack.html.erb b/app/views/compute_resources_vms/index/_openstack.html.erb
new file mode 100644
index 000000000..c47869baf
--- /dev/null
+++ b/app/views/compute_resources_vms/index/_openstack.html.erb
@@ -0,0 +1,23 @@
+<table class="table table-bordered">
+  <tr>
+    <thead>
+      <th>Name</th>
+      <th>Type</th>
+      <th>Tenant</th>
+      <th>State</th>
+      <th></th>
+    </thead>
+  </tr>
+  <% @compute_resource.vms.each do |vm| -%>
+    <tr>
+      <td><%= link_to_if_authorized vm.name, hash_for_compute_resource_vm_path(:compute_resource_id => @compute_resource, :id => vm.identity) %></td>
+      <td><%= vm.flavor_with_object %></td>
+      <td><%= vm.tenant %></td>
+      <td <%= vm_power_class(vm.ready?)%>> <%= vm_state(!vm.ready?) %> </td>
+      <td>
+        <%= action_buttons(vm_power_action(vm),
+            display_delete_if_authorized(hash_for_compute_resource_vm_path(:compute_resource_id => @compute_resource, :id => vm.id))) %>
+      </td>
+    </tr>
+  <% end -%>
+</table>
diff --git a/app/views/compute_resources_vms/show/_openstack.html.erb b/app/views/compute_resources_vms/show/_openstack.html.erb
new file mode 100644
index 000000000..dd4f5847e
--- /dev/null
+++ b/app/views/compute_resources_vms/show/_openstack.html.erb
@@ -0,0 +1,14 @@
+<% title @vm.name %>
+<div class='span12'>
+  <table class="table table-bordered table-striped">
+    <tr><th colspan="2">Properties</th></tr>
+
+    <%= prop :availability_zone %>
+    <%= prop :first_private_ip_address %>
+    <%= prop :state %>
+    <%= prop :created_at %>
+    <%= prop :tenant %>
+    <%= prop :flavor_with_object, "Flavor" %>
+    <%= prop :security_groups, "Security Groups" %>
+  </table>
+</div>
diff --git a/lib/fog_extensions.rb b/lib/fog_extensions.rb
index 3bdc4717f..35cc1d263 100644
--- a/lib/fog_extensions.rb
+++ b/lib/fog_extensions.rb
@@ -21,9 +21,16 @@ begin
   require 'fog/ovirt/models/compute/volume'
   Fog::Compute::Ovirt::Volume.send(:include, FogExtensions::Ovirt::Volume)
 
+  require 'fog/openstack'
+  require 'fog/openstack/models/compute/server'
+  Fog::Compute::OpenStack::Server.send(:include, FogExtensions::Openstack::Server)
+
+  require 'fog/openstack/models/compute/flavor'
+  Fog::Compute::OpenStack::Flavor.send(:include, FogExtensions::Openstack::Flavor)
+
 rescue LoadError
   Rails.logger.info "Fog is not installed - unable to manage compute resources"
 rescue => exception
   Rails.logger.warn "Fog initialization failed - #{exception}"
-  Rails.logger.debug exception.join("\n")
+  Rails.logger.debug exception.backtrace.join("\n")
 end
diff --git a/lib/fog_extensions/openstack/flavor.rb b/lib/fog_extensions/openstack/flavor.rb
new file mode 100644
index 000000000..b91b2179b
--- /dev/null
+++ b/lib/fog_extensions/openstack/flavor.rb
@@ -0,0 +1,13 @@
+module FogExtensions
+  module Openstack
+    module Flavor
+      def to_label
+        "#{id} - #{name}"
+      end
+
+      def to_s
+        name
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/fog_extensions/openstack/server.rb b/lib/fog_extensions/openstack/server.rb
new file mode 100644
index 000000000..727aeb772
--- /dev/null
+++ b/lib/fog_extensions/openstack/server.rb
@@ -0,0 +1,31 @@
+module FogExtensions
+  module Openstack
+    module Server
+
+      #TODO: get as much of this merged into fog 1.5
+
+      def tenant
+        connection.tenants.detect{|t| t.id == tenant_id }
+      end
+
+  #    alias_method_chain :flavor, :object
+
+      def flavor_with_object
+        connection.flavors.get attributes[:flavor]['id']
+      end
+
+      def first_private_ip_address
+        private_ip_address["addr"]
+      end
+
+      def created_at
+        Time.parse attributes['created']
+      end
+
+      def security_groups
+        connection.security_groups.all
+      end
+
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/foreman/model/openstack.rb b/lib/foreman/model/openstack.rb
new file mode 100644
index 000000000..0d572083c
--- /dev/null
+++ b/lib/foreman/model/openstack.rb
@@ -0,0 +1,66 @@
+module Foreman::Model
+  class Openstack < ComputeResource
+    attr_accessor :tenant
+    has_one :key_pair, :foreign_key => :compute_resource_id
+    after_create :setup_key_pair
+    after_destroy :destroy_key_pair
+    delegate :flavors, :to => :client
+    delegate :tenants, :to => :client
+    delegate :security_groups, :to => :client
+    delegate :images, :to => :client
+
+    validates_presence_of :user, :password
+
+    def provided_attributes
+      super.merge({ :ip => :public_ip_address })
+    end
+
+    def self.model_name
+      ComputeResource.model_name
+    end
+
+    def capabilities
+      [:image]
+    end
+
+    def test_connection
+      super
+      errors[:user].empty? and errors[:password] and tenants
+    rescue => e
+      errors[:base] << e.message
+    end
+
+    private
+
+    def client
+      @client ||= ::Fog::Compute.new(:provider => :openstack, :openstack_api_key => password, :openstack_username => user, :openstack_auth_url => url)
+    end
+
+    def setup_key_pair
+      key = client.key_pairs.create :name => "foreman-#{id}#{Foreman.uuid}"
+      KeyPair.create! :name => key.name, :compute_resource_id => self.id, :secret => key.private_key
+    rescue => e
+      logger.warn "failed to generate key pair"
+      destroy_key_pair
+      raise
+    end
+
+    def destroy_key_pair
+      return unless key_pair
+      logger.info "removing OpenStack key #{key_pair.name}"
+      key = client.key_pairs.get(key_pair.name)
+      key.destroy if key
+      key_pair.destroy
+      true
+    rescue => e
+      logger.warn "failed to delete key pair from OpenStack, you might need to cleanup manually : #{e}"
+    end
+
+    def vm_instance_defaults
+      {
+        :name      => "foreman-#{Foreman.uuid}",
+        :key_name  => key_pair.name,
+      }
+    end
+  end
+end
