From: Jan Provaznik jprovazn@redhat.com
Replaces InstanceEvent model by more general polymorphic model. For now only instances are associated with events. --- src/app/models/event.rb | 26 ++++++++++++++++++++++++++ src/app/models/instance.rb | 1 + src/app/models/instance_event.rb | 26 -------------------------- src/db/migrate/20110201132528_events.rb | 26 ++++++++++++++++++++++++++ src/dbomatic/dbomatic | 6 ++---- 5 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 src/app/models/event.rb delete mode 100644 src/app/models/instance_event.rb create mode 100644 src/db/migrate/20110201132528_events.rb
diff --git a/src/app/models/event.rb b/src/app/models/event.rb new file mode 100644 index 0000000..91c64a2 --- /dev/null +++ b/src/app/models/event.rb @@ -0,0 +1,26 @@ +# Copyright (C) 2010 Red Hat, Inc. +# +# 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; version 2 of the License. +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class Event < ActiveRecord::Base + belongs_to :source, :polymorphic => true + + validates_presence_of :source_id + validates_presence_of :source_type +end diff --git a/src/app/models/instance.rb b/src/app/models/instance.rb index b84a596..3ae921d 100644 --- a/src/app/models/instance.rb +++ b/src/app/models/instance.rb @@ -48,6 +48,7 @@ class Instance < ActiveRecord::Base has_many :permissions, :as => :permission_object, :dependent => :destroy, :include => [:role], :order => "permissions.id ASC" + has_many :events, :as => :source, :dependent => :destroy
validates_presence_of :pool_id validates_presence_of :hardware_profile_id diff --git a/src/app/models/instance_event.rb b/src/app/models/instance_event.rb deleted file mode 100644 index 5076f8a..0000000 --- a/src/app/models/instance_event.rb +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (C) 2010 Red Hat, Inc. -# -# 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; version 2 of the License. -# -# 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, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. A copy of the GNU General Public License is -# also available at http://www.gnu.org/copyleft/gpl.html. - -# Filters added to this controller apply to all controllers in the application. -# Likewise, all the methods added will be available for all controllers. - -class InstanceEvent < ActiveRecord::Base - belongs_to :instance - - validates_presence_of :instance_id - validates_presence_of :event_type -end diff --git a/src/db/migrate/20110201132528_events.rb b/src/db/migrate/20110201132528_events.rb new file mode 100644 index 0000000..8af7137 --- /dev/null +++ b/src/db/migrate/20110201132528_events.rb @@ -0,0 +1,26 @@ +class Events < ActiveRecord::Migration + def self.up + drop_table :instance_events + create_table :events do |t| + t.integer :source_id, :null => false + t.string :source_type, :null => false + t.datetime :event_time + t.string :status_code + t.string :summary + t.string :description + t.timestamps + end + end + + def self.down + drop_table :events + create_table :instance_events do |t| + t.integer :instance_id, :null => false + t.string :event_type, :null => false + t.datetime :event_time + t.string :status + t.string :message + t.timestamps + end + end +end diff --git a/src/dbomatic/dbomatic b/src/dbomatic/dbomatic index 8a3e968..bf3a987 100755 --- a/src/dbomatic/dbomatic +++ b/src/dbomatic/dbomatic @@ -163,10 +163,8 @@ class CondorEventLog < Nokogiri::XML::SAX::Document
begin inst.save! - - InstanceEvent.create! :instance => inst, - :event_type => inst.state, - :event_time => @event_time + inst.events.create!(:status_code => inst.state, + :event_time => @event_time) rescue => e @logger.error "#{e.backtrace.shift}: #{e.message}" e.backtrace.each do |step|
From: Jan Provaznik jprovazn@redhat.com
When instance is started unique ssh key is generated and uploaded to instance. New instance key is not generated with deltacloud api because api returns only private part of key but with openssl lib.
This patch requires net_scp and delayed_job gems and delayedjob worker should be started with: 'rake jobs:work' command --- src/Rakefile | 7 ++++ src/app/models/cloud_account_observer.rb | 2 +- src/app/models/instance.rb | 17 ++++++++++ src/app/models/instance_key.rb | 34 ++++++++++++++++++++ src/app/models/instance_observer.rb | 11 +++--- src/config/environment.rb | 2 + src/config/initializers/delayed_job.rb | 2 + .../migrate/20110124103216_create_delayed_jobs.rb | 21 ++++++++++++ src/dbomatic/dbomatic | 5 ++- src/spec/models/instance_observer_spec.rb | 10 +++--- 10 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 src/config/initializers/delayed_job.rb create mode 100644 src/db/migrate/20110124103216_create_delayed_jobs.rb
diff --git a/src/Rakefile b/src/Rakefile index f2d8d21..ad7f788 100644 --- a/src/Rakefile +++ b/src/Rakefile @@ -12,4 +12,11 @@ begin rescue LoadError end
+begin + #gem 'delayed_job', :version => '~>2.0.4' + require 'delayed/tasks' +rescue LoadError + STDERR.puts "Run `rake gems:install` to install delayed_job" +end + require 'tasks/rails' diff --git a/src/app/models/cloud_account_observer.rb b/src/app/models/cloud_account_observer.rb index 299b9db..551c74d 100644 --- a/src/app/models/cloud_account_observer.rb +++ b/src/app/models/cloud_account_observer.rb @@ -7,7 +7,7 @@ class CloudAccountObserver < ActiveRecord::Observer create_bucket(account) end if key = account.generate_auth_key - account.update_attribute(:instance_key, InstanceKey.create!(:pem => key.pem, :name => key.id, :instance_key_owner => account)) + account.update_attribute(:instance_key, InstanceKey.create!(:pem => key.pem.first, :name => key.id, :instance_key_owner => account)) end end
diff --git a/src/app/models/instance.rb b/src/app/models/instance.rb index 3ae921d..1172998 100644 --- a/src/app/models/instance.rb +++ b/src/app/models/instance.rb @@ -170,6 +170,23 @@ class Instance < ActiveRecord::Base end end
+ def create_unique_key + client = self.cloud_account.connect + # TODO: what if dcloud driver is not running + return unless client && client.feature?(:instances, :authentication_key) + # deltacloud/ec2 api's create_key method returns only private part of + # key -> we don't know public part, so we generate new ssh key + # and replace whole authorized_keys file + begin + self.instance_key.replace_key(self.public_addresses) + self.events.create!(:summary => "successfully updated ssh key", :event_time => Time.now) + rescue + msg = "failed to upload ssh key: #{$!}" + self.last_error = msg + self.events.create!(:summary => msg, :event_time => Time.now) + end + end + def self.get_user_instances_stats(user) stats = { :running_instances => 0, diff --git a/src/app/models/instance_key.rb b/src/app/models/instance_key.rb index 9c941fe..3ec3e51 100644 --- a/src/app/models/instance_key.rb +++ b/src/app/models/instance_key.rb @@ -19,8 +19,42 @@ # Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers. # + +require 'openssl' +require 'base64' + class InstanceKey < ActiveRecord::Base
belongs_to :instance_key_owner, :polymorphic => true
+ REMOTE_USER = 'ec2-user' + REMOTE_HOME = '/home/ec2-user' + + def replace_key(addr) + transaction do + key = generate_ssh_key + replace_on_server(addr, key[:public]) + self.pem = key[:private] + save! + end + end + + private + + def replace_on_server(addr, new_pub) + Net::SCP::start(addr, REMOTE_USER, :key_data => [self.pem], :keys => []) do |scp| + scp.upload! StringIO.new(new_pub), File.join(REMOTE_HOME, '/.ssh/authorized_keys') + end + end + + def generate_ssh_key + key = OpenSSL::PKey::RSA.generate(1024) + writer = Net::SSH::Buffer.new + writer.write_key key + ssh_key = Base64.encode64( writer.to_s ).strip.gsub( /[\n\r\t ]/, "" ) + { + :private => key.export, + :public => "#{key.ssh_type} #{ssh_key} #{ENV['USER']}@#{ENV['HOSTNAME']}" + } + end end diff --git a/src/app/models/instance_observer.rb b/src/app/models/instance_observer.rb index 80b39c5..a0c5cb1 100644 --- a/src/app/models/instance_observer.rb +++ b/src/app/models/instance_observer.rb @@ -61,12 +61,13 @@ class InstanceObserver < ActiveRecord::Observer end end
- def before_update(instance) + def after_update(instance) # we try to generate key only when instance is running - # and instance_key is not generated yet - return if instance.state != Instance::STATE_RUNNING or instance.instance_key - if key = instance.cloud_account.generate_auth_key - instance.instance_key = InstanceKey.create!(:pem => key.pem, :name => key.id, :instance_key_owner => instance) + # and instance_key is same is cloud account key (same names - in dbomatic we + # copy cloud account key to instance key) + if instance.state_changed? and instance.state == Instance::STATE_RUNNING and + instance.instance_key and instance.instance_key.name == instance.cloud_account.instance_key.name + instance.delay.create_unique_key end end
diff --git a/src/config/environment.rb b/src/config/environment.rb index a897feb..1f49f25 100644 --- a/src/config/environment.rb +++ b/src/config/environment.rb @@ -54,6 +54,8 @@ Rails::Initializer.run do |config| config.gem "rb-inotify" config.gem 'rack-restful_submit', :version => '1.1.2' config.gem 'sunspot_rails', :lib => 'sunspot/rails' + config.gem 'delayed_job', :version => '~>2.0.4' + config.gem 'net-scp', :lib => 'net/scp'
config.middleware.swap Rack::MethodOverride, 'Rack::RestfulSubmit'
diff --git a/src/config/initializers/delayed_job.rb b/src/config/initializers/delayed_job.rb new file mode 100644 index 0000000..a8684b4 --- /dev/null +++ b/src/config/initializers/delayed_job.rb @@ -0,0 +1,2 @@ +Delayed::Worker.backend = :active_record +Delayed::Worker.max_attempts = 1 diff --git a/src/db/migrate/20110124103216_create_delayed_jobs.rb b/src/db/migrate/20110124103216_create_delayed_jobs.rb new file mode 100644 index 0000000..943ff9b --- /dev/null +++ b/src/db/migrate/20110124103216_create_delayed_jobs.rb @@ -0,0 +1,21 @@ +class CreateDelayedJobs < ActiveRecord::Migration + def self.up + create_table :delayed_jobs, :force => true do |table| + table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue + table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually. + table.text :handler # YAML-encoded string of the object that will do work + table.text :last_error # reason for last failure (See Note below) + table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. + table.datetime :locked_at # Set when a client is working on this object + table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) + table.string :locked_by # Who is working on this object (if locked) + table.timestamps + end + + add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority' + end + + def self.down + drop_table :delayed_jobs + end +end diff --git a/src/dbomatic/dbomatic b/src/dbomatic/dbomatic index bf3a987..e021ff6 100755 --- a/src/dbomatic/dbomatic +++ b/src/dbomatic/dbomatic @@ -212,7 +212,10 @@ class CondorEventLog < Nokogiri::XML::SAX::Document # FIXME: we are updating the instance_key_id here, but this is really not # the right way or place to do this. This will have to be revisited when # we come up with a real key management architecture - inst.instance_key_id = cloud_account.instance_key.id + new_key = InstanceKey.new(cloud_account.instance_key.attributes) + new_key.instance_key_owner = inst + new_key.save! + inst.instance_key = new_key
inst.save! end diff --git a/src/spec/models/instance_observer_spec.rb b/src/spec/models/instance_observer_spec.rb index be2665e..d237786 100644 --- a/src/spec/models/instance_observer_spec.rb +++ b/src/spec/models/instance_observer_spec.rb @@ -156,16 +156,16 @@ describe InstanceObserver do
it "should generate instance key when instance is running" do client = mock('DeltaCloud', :null_object => true) - key = mock('Key', :null_object => true) - key.stub!(:pem).and_return("PEM") - key.stub!(:id).and_return("1_user") client.stub!(:"feature?").and_return(true) - client.stub!(:"create_key").and_return(key) @cloud_account.stub!(:connect).and_return(client) @instance.stub!(:cloud_account).and_return(@cloud_account)
+ @instance.instance_key = Factory(:instance_key, :name => 'key1', :instance_key_owner => @instance) + @instance.instance_key.stub!(:replace_on_server).and_return(true) + @cloud_account.instance_key = Factory(:instance_key, :name => 'key1', :instance_key_owner => @cloud_account) + @instance.state = Instance::STATE_RUNNING @instance.save! - @instance.instance_key.should_not == nil + @instance.instance_key.name.should != @cloud_account.instance_key.name end end
On Mon, 2011-02-07 at 13:56 +0100, jprovazn@redhat.com wrote:
From: Jan Provaznik jprovazn@redhat.com
When instance is started unique ssh key is generated and uploaded to instance. New instance key is not generated with deltacloud api because api returns only private part of key but with openssl lib.
This patch requires net_scp and delayed_job gems and delayedjob worker should be started with: 'rake jobs:work' command
Still reviewing testing, just an initial couple notes (and yes, they all apply now)
For these deps:
---Missing these required gems: delayed_job ~> 2.0.4 net-scp
Have you talked to morazi or vondruch? We need to get tasks added to redmine for these, and get them packaged up in order for this to go in a release.
We also need to decide how to handle the rake jobs command for rpm install/puppet config.
src/Rakefile | 7 ++++ src/app/models/cloud_account_observer.rb | 2 +- src/app/models/instance.rb | 17 ++++++++++ src/app/models/instance_key.rb | 34 ++++++++++++++++++++ src/app/models/instance_observer.rb | 11 +++--- src/config/environment.rb | 2 + src/config/initializers/delayed_job.rb | 2 + .../migrate/20110124103216_create_delayed_jobs.rb | 21 ++++++++++++ src/dbomatic/dbomatic | 5 ++- src/spec/models/instance_observer_spec.rb | 10 +++--- 10 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 src/config/initializers/delayed_job.rb create mode 100644 src/db/migrate/20110124103216_create_delayed_jobs.rb
diff --git a/src/Rakefile b/src/Rakefile index f2d8d21..ad7f788 100644 --- a/src/Rakefile +++ b/src/Rakefile @@ -12,4 +12,11 @@ begin rescue LoadError end
+begin
- #gem 'delayed_job', :version => '~>2.0.4'
- require 'delayed/tasks'
+rescue LoadError
- STDERR.puts "Run `rake gems:install` to install delayed_job"
+end
require 'tasks/rails' diff --git a/src/app/models/cloud_account_observer.rb b/src/app/models/cloud_account_observer.rb index 299b9db..551c74d 100644 --- a/src/app/models/cloud_account_observer.rb +++ b/src/app/models/cloud_account_observer.rb @@ -7,7 +7,7 @@ class CloudAccountObserver < ActiveRecord::Observer create_bucket(account) end if key = account.generate_auth_key
account.update_attribute(:instance_key, InstanceKey.create!(:pem => key.pem, :name => key.id, :instance_key_owner => account))
end endaccount.update_attribute(:instance_key, InstanceKey.create!(:pem => key.pem.first, :name => key.id, :instance_key_owner => account))
diff --git a/src/app/models/instance.rb b/src/app/models/instance.rb index 3ae921d..1172998 100644 --- a/src/app/models/instance.rb +++ b/src/app/models/instance.rb @@ -170,6 +170,23 @@ class Instance < ActiveRecord::Base end end
- def create_unique_key
- client = self.cloud_account.connect
- # TODO: what if dcloud driver is not running
- return unless client && client.feature?(:instances, :authentication_key)
- # deltacloud/ec2 api's create_key method returns only private part of
- # key -> we don't know public part, so we generate new ssh key
- # and replace whole authorized_keys file
- begin
self.instance_key.replace_key(self.public_addresses)
self.events.create!(:summary => "successfully updated ssh key", :event_time => Time.now)
- rescue
msg = "failed to upload ssh key: #{$!}"
self.last_error = msg
self.events.create!(:summary => msg, :event_time => Time.now)
- end
- end
- def self.get_user_instances_stats(user) stats = { :running_instances => 0,
diff --git a/src/app/models/instance_key.rb b/src/app/models/instance_key.rb index 9c941fe..3ec3e51 100644 --- a/src/app/models/instance_key.rb +++ b/src/app/models/instance_key.rb @@ -19,8 +19,42 @@ # Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers. #
+require 'openssl' +require 'base64'
class InstanceKey < ActiveRecord::Base
belongs_to :instance_key_owner, :polymorphic => true
- REMOTE_USER = 'ec2-user'
- REMOTE_HOME = '/home/ec2-user'
- def replace_key(addr)
- transaction do
key = generate_ssh_key
replace_on_server(addr, key[:public])
self.pem = key[:private]
save!
- end
- end
- private
- def replace_on_server(addr, new_pub)
- Net::SCP::start(addr, REMOTE_USER, :key_data => [self.pem], :keys => []) do |scp|
scp.upload! StringIO.new(new_pub), File.join(REMOTE_HOME, '/.ssh/authorized_keys')
- end
- end
- def generate_ssh_key
- key = OpenSSL::PKey::RSA.generate(1024)
- writer = Net::SSH::Buffer.new
- writer.write_key key
- ssh_key = Base64.encode64( writer.to_s ).strip.gsub( /[\n\r\t ]/, "" )
- {
:private => key.export,
:public => "#{key.ssh_type} #{ssh_key} #{ENV['USER']}@#{ENV['HOSTNAME']}"
- }
- end
end diff --git a/src/app/models/instance_observer.rb b/src/app/models/instance_observer.rb index 80b39c5..a0c5cb1 100644 --- a/src/app/models/instance_observer.rb +++ b/src/app/models/instance_observer.rb @@ -61,12 +61,13 @@ class InstanceObserver < ActiveRecord::Observer end end
- def before_update(instance)
- def after_update(instance) # we try to generate key only when instance is running
- # and instance_key is not generated yet
- return if instance.state != Instance::STATE_RUNNING or instance.instance_key
- if key = instance.cloud_account.generate_auth_key
instance.instance_key = InstanceKey.create!(:pem => key.pem, :name => key.id, :instance_key_owner => instance)
- # and instance_key is same is cloud account key (same names - in dbomatic we
- # copy cloud account key to instance key)
- if instance.state_changed? and instance.state == Instance::STATE_RUNNING and
instance.instance_key and instance.instance_key.name == instance.cloud_account.instance_key.name
end endinstance.delay.create_unique_key
diff --git a/src/config/environment.rb b/src/config/environment.rb index a897feb..1f49f25 100644 --- a/src/config/environment.rb +++ b/src/config/environment.rb @@ -54,6 +54,8 @@ Rails::Initializer.run do |config| config.gem "rb-inotify" config.gem 'rack-restful_submit', :version => '1.1.2' config.gem 'sunspot_rails', :lib => 'sunspot/rails'
config.gem 'delayed_job', :version => '~>2.0.4'
config.gem 'net-scp', :lib => 'net/scp'
config.middleware.swap Rack::MethodOverride, 'Rack::RestfulSubmit'
diff --git a/src/config/initializers/delayed_job.rb b/src/config/initializers/delayed_job.rb new file mode 100644 index 0000000..a8684b4 --- /dev/null +++ b/src/config/initializers/delayed_job.rb @@ -0,0 +1,2 @@ +Delayed::Worker.backend = :active_record +Delayed::Worker.max_attempts = 1 diff --git a/src/db/migrate/20110124103216_create_delayed_jobs.rb b/src/db/migrate/20110124103216_create_delayed_jobs.rb new file mode 100644 index 0000000..943ff9b --- /dev/null +++ b/src/db/migrate/20110124103216_create_delayed_jobs.rb @@ -0,0 +1,21 @@ +class CreateDelayedJobs < ActiveRecord::Migration
- def self.up
- create_table :delayed_jobs, :force => true do |table|
table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
table.text :handler # YAML-encoded string of the object that will do work
table.text :last_error # reason for last failure (See Note below)
table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
table.datetime :locked_at # Set when a client is working on this object
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
table.string :locked_by # Who is working on this object (if locked)
table.timestamps
- end
- add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
- end
- def self.down
- drop_table :delayed_jobs
- end
+end diff --git a/src/dbomatic/dbomatic b/src/dbomatic/dbomatic index bf3a987..e021ff6 100755 --- a/src/dbomatic/dbomatic +++ b/src/dbomatic/dbomatic @@ -212,7 +212,10 @@ class CondorEventLog < Nokogiri::XML::SAX::Document # FIXME: we are updating the instance_key_id here, but this is really not # the right way or place to do this. This will have to be revisited when # we come up with a real key management architecture
- inst.instance_key_id = cloud_account.instance_key.id
new_key = InstanceKey.new(cloud_account.instance_key.attributes)
new_key.instance_key_owner = inst
new_key.save!
inst.instance_key = new_key
inst.save! end
diff --git a/src/spec/models/instance_observer_spec.rb b/src/spec/models/instance_observer_spec.rb index be2665e..d237786 100644 --- a/src/spec/models/instance_observer_spec.rb +++ b/src/spec/models/instance_observer_spec.rb @@ -156,16 +156,16 @@ describe InstanceObserver do
it "should generate instance key when instance is running" do client = mock('DeltaCloud', :null_object => true)
- key = mock('Key', :null_object => true)
- key.stub!(:pem).and_return("PEM")
- key.stub!(:id).and_return("1_user") client.stub!(:"feature?").and_return(true)
- client.stub!(:"create_key").and_return(key) @cloud_account.stub!(:connect).and_return(client) @instance.stub!(:cloud_account).and_return(@cloud_account)
- @instance.instance_key = Factory(:instance_key, :name => 'key1', :instance_key_owner => @instance)
- @instance.instance_key.stub!(:replace_on_server).and_return(true)
- @cloud_account.instance_key = Factory(:instance_key, :name => 'key1', :instance_key_owner => @cloud_account)
- @instance.state = Instance::STATE_RUNNING @instance.save!
- @instance.instance_key.should_not == nil
- @instance.instance_key.name.should != @cloud_account.instance_key.name end
end
On 02/07/2011 06:16 PM, Jason Guiditta wrote:
On Mon, 2011-02-07 at 13:56 +0100, jprovazn@redhat.com wrote:
From: Jan Provaznikjprovazn@redhat.com
When instance is started unique ssh key is generated and uploaded to instance. New instance key is not generated with deltacloud api because api returns only private part of key but with openssl lib.
This patch requires net_scp and delayed_job gems and delayedjob worker should be started with: 'rake jobs:work' command
Still reviewing testing, just an initial couple notes (and yes, they all apply now)
For these deps:
---Missing these required gems: delayed_job ~> 2.0.4 net-scp
Have you talked to morazi or vondruch? We need to get tasks added to redmine for these, and get them packaged up in order for this to go in a release.
Not yet - my plan was to ask vondruch when patch is ACKed (packaging could be waste of his time if patch will be NACKed), though now I see there could be some time pressure now, so I can ask vondruch tomorrow.
We also need to decide how to handle the rake jobs command for rpm install/puppet config.
Yes, will ask packaging guys.
src/Rakefile | 7 ++++ src/app/models/cloud_account_observer.rb | 2 +- src/app/models/instance.rb | 17 ++++++++++ src/app/models/instance_key.rb | 34 ++++++++++++++++++++ src/app/models/instance_observer.rb | 11 +++--- src/config/environment.rb | 2 + src/config/initializers/delayed_job.rb | 2 + .../migrate/20110124103216_create_delayed_jobs.rb | 21 ++++++++++++ src/dbomatic/dbomatic | 5 ++- src/spec/models/instance_observer_spec.rb | 10 +++--- 10 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 src/config/initializers/delayed_job.rb create mode 100644 src/db/migrate/20110124103216_create_delayed_jobs.rb
diff --git a/src/Rakefile b/src/Rakefile index f2d8d21..ad7f788 100644 --- a/src/Rakefile +++ b/src/Rakefile @@ -12,4 +12,11 @@ begin rescue LoadError end
+begin
- #gem 'delayed_job', :version => '~>2.0.4'
- require 'delayed/tasks'
+rescue LoadError
- STDERR.puts "Run `rake gems:install` to install delayed_job"
+end
- require 'tasks/rails'
diff --git a/src/app/models/cloud_account_observer.rb b/src/app/models/cloud_account_observer.rb index 299b9db..551c74d 100644 --- a/src/app/models/cloud_account_observer.rb +++ b/src/app/models/cloud_account_observer.rb @@ -7,7 +7,7 @@ class CloudAccountObserver< ActiveRecord::Observer create_bucket(account) end if key = account.generate_auth_key
account.update_attribute(:instance_key, InstanceKey.create!(:pem => key.pem, :name => key.id, :instance_key_owner => account))
endaccount.update_attribute(:instance_key, InstanceKey.create!(:pem => key.pem.first, :name => key.id, :instance_key_owner => account)) end
diff --git a/src/app/models/instance.rb b/src/app/models/instance.rb index 3ae921d..1172998 100644 --- a/src/app/models/instance.rb +++ b/src/app/models/instance.rb @@ -170,6 +170,23 @@ class Instance< ActiveRecord::Base end end
- def create_unique_key
- client = self.cloud_account.connect
- # TODO: what if dcloud driver is not running
- return unless client&& client.feature?(:instances, :authentication_key)
- # deltacloud/ec2 api's create_key method returns only private part of
- # key -> we don't know public part, so we generate new ssh key
- # and replace whole authorized_keys file
- begin
self.instance_key.replace_key(self.public_addresses)
self.events.create!(:summary => "successfully updated ssh key", :event_time => Time.now)
- rescue
msg = "failed to upload ssh key: #{$!}"
self.last_error = msg
self.events.create!(:summary => msg, :event_time => Time.now)
- end
- end
- def self.get_user_instances_stats(user) stats = { :running_instances => 0,
diff --git a/src/app/models/instance_key.rb b/src/app/models/instance_key.rb index 9c941fe..3ec3e51 100644 --- a/src/app/models/instance_key.rb +++ b/src/app/models/instance_key.rb @@ -19,8 +19,42 @@ # Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers. #
+require 'openssl' +require 'base64'
class InstanceKey< ActiveRecord::Base
belongs_to :instance_key_owner, :polymorphic => true
REMOTE_USER = 'ec2-user'
REMOTE_HOME = '/home/ec2-user'
def replace_key(addr)
transaction do
key = generate_ssh_key
replace_on_server(addr, key[:public])
self.pem = key[:private]
save!
end
end
private
def replace_on_server(addr, new_pub)
Net::SCP::start(addr, REMOTE_USER, :key_data => [self.pem], :keys => []) do |scp|
scp.upload! StringIO.new(new_pub), File.join(REMOTE_HOME, '/.ssh/authorized_keys')
end
end
def generate_ssh_key
key = OpenSSL::PKey::RSA.generate(1024)
writer = Net::SSH::Buffer.new
writer.write_key key
ssh_key = Base64.encode64( writer.to_s ).strip.gsub( /[\n\r\t ]/, "" )
{
:private => key.export,
:public => "#{key.ssh_type} #{ssh_key} #{ENV['USER']}@#{ENV['HOSTNAME']}"
}
end end
diff --git a/src/app/models/instance_observer.rb b/src/app/models/instance_observer.rb index 80b39c5..a0c5cb1 100644 --- a/src/app/models/instance_observer.rb +++ b/src/app/models/instance_observer.rb @@ -61,12 +61,13 @@ class InstanceObserver< ActiveRecord::Observer end end
- def before_update(instance)
- def after_update(instance) # we try to generate key only when instance is running
- # and instance_key is not generated yet
- return if instance.state != Instance::STATE_RUNNING or instance.instance_key
- if key = instance.cloud_account.generate_auth_key
instance.instance_key = InstanceKey.create!(:pem => key.pem, :name => key.id, :instance_key_owner => instance)
- # and instance_key is same is cloud account key (same names - in dbomatic we
- # copy cloud account key to instance key)
- if instance.state_changed? and instance.state == Instance::STATE_RUNNING and
instance.instance_key and instance.instance_key.name == instance.cloud_account.instance_key.name
endinstance.delay.create_unique_key end
diff --git a/src/config/environment.rb b/src/config/environment.rb index a897feb..1f49f25 100644 --- a/src/config/environment.rb +++ b/src/config/environment.rb @@ -54,6 +54,8 @@ Rails::Initializer.run do |config| config.gem "rb-inotify" config.gem 'rack-restful_submit', :version => '1.1.2' config.gem 'sunspot_rails', :lib => 'sunspot/rails'
config.gem 'delayed_job', :version => '~>2.0.4'
config.gem 'net-scp', :lib => 'net/scp'
config.middleware.swap Rack::MethodOverride, 'Rack::RestfulSubmit'
diff --git a/src/config/initializers/delayed_job.rb b/src/config/initializers/delayed_job.rb new file mode 100644 index 0000000..a8684b4 --- /dev/null +++ b/src/config/initializers/delayed_job.rb @@ -0,0 +1,2 @@ +Delayed::Worker.backend = :active_record +Delayed::Worker.max_attempts = 1 diff --git a/src/db/migrate/20110124103216_create_delayed_jobs.rb b/src/db/migrate/20110124103216_create_delayed_jobs.rb new file mode 100644 index 0000000..943ff9b --- /dev/null +++ b/src/db/migrate/20110124103216_create_delayed_jobs.rb @@ -0,0 +1,21 @@ +class CreateDelayedJobs< ActiveRecord::Migration
- def self.up
- create_table :delayed_jobs, :force => true do |table|
table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
table.text :handler # YAML-encoded string of the object that will do work
table.text :last_error # reason for last failure (See Note below)
table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
table.datetime :locked_at # Set when a client is working on this object
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
table.string :locked_by # Who is working on this object (if locked)
table.timestamps
- end
- add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
- end
- def self.down
- drop_table :delayed_jobs
- end
+end diff --git a/src/dbomatic/dbomatic b/src/dbomatic/dbomatic index bf3a987..e021ff6 100755 --- a/src/dbomatic/dbomatic +++ b/src/dbomatic/dbomatic @@ -212,7 +212,10 @@ class CondorEventLog< Nokogiri::XML::SAX::Document # FIXME: we are updating the instance_key_id here, but this is really not # the right way or place to do this. This will have to be revisited when # we come up with a real key management architecture
- inst.instance_key_id = cloud_account.instance_key.id
new_key = InstanceKey.new(cloud_account.instance_key.attributes)
new_key.instance_key_owner = inst
new_key.save!
inst.instance_key = new_key
inst.save! end
diff --git a/src/spec/models/instance_observer_spec.rb b/src/spec/models/instance_observer_spec.rb index be2665e..d237786 100644 --- a/src/spec/models/instance_observer_spec.rb +++ b/src/spec/models/instance_observer_spec.rb @@ -156,16 +156,16 @@ describe InstanceObserver do
it "should generate instance key when instance is running" do client = mock('DeltaCloud', :null_object => true)
- key = mock('Key', :null_object => true)
- key.stub!(:pem).and_return("PEM")
- key.stub!(:id).and_return("1_user") client.stub!(:"feature?").and_return(true)
- client.stub!(:"create_key").and_return(key) @cloud_account.stub!(:connect).and_return(client) @instance.stub!(:cloud_account).and_return(@cloud_account)
- @instance.instance_key = Factory(:instance_key, :name => 'key1', :instance_key_owner => @instance)
- @instance.instance_key.stub!(:replace_on_server).and_return(true)
- @cloud_account.instance_key = Factory(:instance_key, :name => 'key1', :instance_key_owner => @cloud_account)
@instance.state = Instance::STATE_RUNNING @instance.save!
- @instance.instance_key.should_not == nil
- @instance.instance_key.name.should != @cloud_account.instance_key.name end end
On Mon, 2011-02-07 at 13:56 +0100, jprovazn@redhat.com wrote:
From: Jan Provaznik jprovazn@redhat.com
When instance is started unique ssh key is generated and uploaded to instance. New instance key is not generated with deltacloud api because api returns only private part of key but with openssl lib.
This patch requires net_scp and delayed_job gems and delayedjob worker should be started with: 'rake jobs:work' command
src/Rakefile | 7 ++++ src/app/models/cloud_account_observer.rb | 2 +- src/app/models/instance.rb | 17 ++++++++++ src/app/models/instance_key.rb | 34 ++++++++++++++++++++ src/app/models/instance_observer.rb | 11 +++--- src/config/environment.rb | 2 + src/config/initializers/delayed_job.rb | 2 + .../migrate/20110124103216_create_delayed_jobs.rb | 21 ++++++++++++ src/dbomatic/dbomatic | 5 ++- src/spec/models/instance_observer_spec.rb | 10 +++--- 10 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 src/config/initializers/delayed_job.rb create mode 100644 src/db/migrate/20110124103216_create_delayed_jobs.rb
diff --git a/src/Rakefile b/src/Rakefile index f2d8d21..ad7f788 100644 --- a/src/Rakefile +++ b/src/Rakefile @@ -12,4 +12,11 @@ begin rescue LoadError end
+begin
- #gem 'delayed_job', :version => '~>2.0.4'
- require 'delayed/tasks'
+rescue LoadError
- STDERR.puts "Run `rake gems:install` to install delayed_job"
+end
require 'tasks/rails' diff --git a/src/app/models/cloud_account_observer.rb b/src/app/models/cloud_account_observer.rb index 299b9db..551c74d 100644 --- a/src/app/models/cloud_account_observer.rb +++ b/src/app/models/cloud_account_observer.rb @@ -7,7 +7,7 @@ class CloudAccountObserver < ActiveRecord::Observer create_bucket(account) end if key = account.generate_auth_key
account.update_attribute(:instance_key, InstanceKey.create!(:pem => key.pem, :name => key.id, :instance_key_owner => account))
end endaccount.update_attribute(:instance_key, InstanceKey.create!(:pem => key.pem.first, :name => key.id, :instance_key_owner => account))
diff --git a/src/app/models/instance.rb b/src/app/models/instance.rb index 3ae921d..1172998 100644 --- a/src/app/models/instance.rb +++ b/src/app/models/instance.rb @@ -170,6 +170,23 @@ class Instance < ActiveRecord::Base end end
- def create_unique_key
- client = self.cloud_account.connect
- # TODO: what if dcloud driver is not running
- return unless client && client.feature?(:instances, :authentication_key)
- # deltacloud/ec2 api's create_key method returns only private part of
- # key -> we don't know public part, so we generate new ssh key
- # and replace whole authorized_keys file
- begin
self.instance_key.replace_key(self.public_addresses)
self.events.create!(:summary => "successfully updated ssh key", :event_time => Time.now)
- rescue
msg = "failed to upload ssh key: #{$!}"
self.last_error = msg
self.events.create!(:summary => msg, :event_time => Time.now)
- end
- end
- def self.get_user_instances_stats(user) stats = { :running_instances => 0,
diff --git a/src/app/models/instance_key.rb b/src/app/models/instance_key.rb index 9c941fe..3ec3e51 100644 --- a/src/app/models/instance_key.rb +++ b/src/app/models/instance_key.rb @@ -19,8 +19,42 @@ # Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers. #
+require 'openssl' +require 'base64'
class InstanceKey < ActiveRecord::Base
belongs_to :instance_key_owner, :polymorphic => true
- REMOTE_USER = 'ec2-user'
- REMOTE_HOME = '/home/ec2-user'
I don't like this being so ec2-specific, as this clearly will not work anywhere else, but maybe that is ok for a first pass, since ec2 is our primary test case for this sprint.
If we leave this, we'll have to put in a task to alter as soon as we start hooking up to rhev, vmware or whatever.
- def replace_key(addr)
- transaction do
key = generate_ssh_key
replace_on_server(addr, key[:public])
self.pem = key[:private]
save!
- end
- end
- private
- def replace_on_server(addr, new_pub)
- Net::SCP::start(addr, REMOTE_USER, :key_data => [self.pem], :keys => []) do |scp|
scp.upload! StringIO.new(new_pub), File.join(REMOTE_HOME, '/.ssh/authorized_keys')
- end
- end
- def generate_ssh_key
- key = OpenSSL::PKey::RSA.generate(1024)
- writer = Net::SSH::Buffer.new
- writer.write_key key
- ssh_key = Base64.encode64( writer.to_s ).strip.gsub( /[\n\r\t ]/, "" )
- {
:private => key.export,
:public => "#{key.ssh_type} #{ssh_key} #{ENV['USER']}@#{ENV['HOSTNAME']}"
- }
- end
This could be just me, but when I tried this, though the key was different, the name it wanted to download was the same - maybe this is the way name is set in the observer when new InstanceKey object is created?
end diff --git a/src/app/models/instance_observer.rb b/src/app/models/instance_observer.rb index 80b39c5..a0c5cb1 100644 --- a/src/app/models/instance_observer.rb +++ b/src/app/models/instance_observer.rb @@ -61,12 +61,13 @@ class InstanceObserver < ActiveRecord::Observer end end
- def before_update(instance)
- def after_update(instance) # we try to generate key only when instance is running
- # and instance_key is not generated yet
- return if instance.state != Instance::STATE_RUNNING or instance.instance_key
- if key = instance.cloud_account.generate_auth_key
instance.instance_key = InstanceKey.create!(:pem => key.pem, :name => key.id, :instance_key_owner => instance)
- # and instance_key is same is cloud account key (same names - in dbomatic we
- # copy cloud account key to instance key)
- if instance.state_changed? and instance.state == Instance::STATE_RUNNING and
instance.instance_key and instance.instance_key.name == instance.cloud_account.instance_key.name
end endinstance.delay.create_unique_key
diff --git a/src/config/environment.rb b/src/config/environment.rb index a897feb..1f49f25 100644 --- a/src/config/environment.rb +++ b/src/config/environment.rb @@ -54,6 +54,8 @@ Rails::Initializer.run do |config| config.gem "rb-inotify" config.gem 'rack-restful_submit', :version => '1.1.2' config.gem 'sunspot_rails', :lib => 'sunspot/rails'
config.gem 'delayed_job', :version => '~>2.0.4'
config.gem 'net-scp', :lib => 'net/scp'
config.middleware.swap Rack::MethodOverride, 'Rack::RestfulSubmit'
diff --git a/src/config/initializers/delayed_job.rb b/src/config/initializers/delayed_job.rb new file mode 100644 index 0000000..a8684b4 --- /dev/null +++ b/src/config/initializers/delayed_job.rb @@ -0,0 +1,2 @@ +Delayed::Worker.backend = :active_record +Delayed::Worker.max_attempts = 1 diff --git a/src/db/migrate/20110124103216_create_delayed_jobs.rb b/src/db/migrate/20110124103216_create_delayed_jobs.rb new file mode 100644 index 0000000..943ff9b --- /dev/null +++ b/src/db/migrate/20110124103216_create_delayed_jobs.rb @@ -0,0 +1,21 @@ +class CreateDelayedJobs < ActiveRecord::Migration
- def self.up
- create_table :delayed_jobs, :force => true do |table|
table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
table.text :handler # YAML-encoded string of the object that will do work
table.text :last_error # reason for last failure (See Note below)
table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
table.datetime :locked_at # Set when a client is working on this object
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
table.string :locked_by # Who is working on this object (if locked)
table.timestamps
- end
- add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
- end
- def self.down
- drop_table :delayed_jobs
- end
+end diff --git a/src/dbomatic/dbomatic b/src/dbomatic/dbomatic index bf3a987..e021ff6 100755 --- a/src/dbomatic/dbomatic +++ b/src/dbomatic/dbomatic @@ -212,7 +212,10 @@ class CondorEventLog < Nokogiri::XML::SAX::Document # FIXME: we are updating the instance_key_id here, but this is really not # the right way or place to do this. This will have to be revisited when # we come up with a real key management architecture
- inst.instance_key_id = cloud_account.instance_key.id
new_key = InstanceKey.new(cloud_account.instance_key.attributes)
new_key.instance_key_owner = inst
new_key.save!
inst.instance_key = new_key
inst.save! end
diff --git a/src/spec/models/instance_observer_spec.rb b/src/spec/models/instance_observer_spec.rb index be2665e..d237786 100644 --- a/src/spec/models/instance_observer_spec.rb +++ b/src/spec/models/instance_observer_spec.rb @@ -156,16 +156,16 @@ describe InstanceObserver do
it "should generate instance key when instance is running" do client = mock('DeltaCloud', :null_object => true)
- key = mock('Key', :null_object => true)
- key.stub!(:pem).and_return("PEM")
- key.stub!(:id).and_return("1_user") client.stub!(:"feature?").and_return(true)
- client.stub!(:"create_key").and_return(key) @cloud_account.stub!(:connect).and_return(client) @instance.stub!(:cloud_account).and_return(@cloud_account)
- @instance.instance_key = Factory(:instance_key, :name => 'key1', :instance_key_owner => @instance)
- @instance.instance_key.stub!(:replace_on_server).and_return(true)
- @cloud_account.instance_key = Factory(:instance_key, :name => 'key1', :instance_key_owner => @cloud_account)
- @instance.state = Instance::STATE_RUNNING @instance.save!
- @instance.instance_key.should_not == nil
- @instance.instance_key.name.should != @cloud_account.instance_key.name end
end
I'd be willing to let the ec2 thing go for this rev, but the file name definitely needs to be fixed. Keep in mind, even if I ACK this, it can't go in until we have those other items sewn up that I mentioned in my previous reply, so I hope you have it in its own topic branch (if not, you might want to put it there).
Event patch looks good, seems to capture events correctly.
Tests seem fine in isolation, I have a few issues similar to what I have seen others report in the last couple days, so they are probably unrelated to this patch. Overall works good, great job!
-j
From: Jan Provaznik jprovazn@redhat.com
Stubbed cloud account's generate_auth_key method --- src/features/support/custom.rb | 6 ++++++ src/spec/factories/cloud_account.rb | 1 + src/spec/models/cloud_account_observer_spec.rb | 1 + src/spec/models/cloud_account_spec.rb | 1 + 4 files changed, 9 insertions(+), 0 deletions(-)
diff --git a/src/features/support/custom.rb b/src/features/support/custom.rb index c2c4987..cd073a8 100644 --- a/src/features/support/custom.rb +++ b/src/features/support/custom.rb @@ -62,3 +62,9 @@ Template.class_eval do true end end + +InstanceKey.class_eval do + def replace_on_server(addr, new) + true + end +end diff --git a/src/spec/factories/cloud_account.rb b/src/spec/factories/cloud_account.rb index 6e301c8..743add3 100644 --- a/src/spec/factories/cloud_account.rb +++ b/src/spec/factories/cloud_account.rb @@ -7,6 +7,7 @@ Factory.define :cloud_account do |f| f.x509_cert_pub "x509 public key" f.association :provider f.association :quota + f.after_build {|acc| acc.stub!(:generate_auth_key).and_return(nil) if acc.respond_to?(:stub!)} end
Factory.define :mock_cloud_account, :parent => :cloud_account do |f| diff --git a/src/spec/models/cloud_account_observer_spec.rb b/src/spec/models/cloud_account_observer_spec.rb index 48491c0..4bf790d 100644 --- a/src/spec/models/cloud_account_observer_spec.rb +++ b/src/spec/models/cloud_account_observer_spec.rb @@ -14,6 +14,7 @@ describe CloudAccountObserver do
cloud_account = Factory.build :ec2_cloud_account cloud_account.stub!(:connect).and_return(@client) + cloud_account.stub!(:generate_auth_key).and_return(@key) cloud_account.save cloud_account.instance_key.should_not == nil cloud_account.instance_key.pem == "PEM" diff --git a/src/spec/models/cloud_account_spec.rb b/src/spec/models/cloud_account_spec.rb index fb04e40..676af32 100644 --- a/src/spec/models/cloud_account_spec.rb +++ b/src/spec/models/cloud_account_spec.rb @@ -43,6 +43,7 @@ describe CloudAccount do
cloud_account = Factory.build :ec2_cloud_account cloud_account.stub!(:connect).and_return(@client) + cloud_account.stub!(:generate_auth_key).and_return(@key) cloud_account.save cloud_account.instance_key.should_not == nil cloud_account.instance_key.pem == "PEM"
aeolus-devel@lists.fedorahosted.org