On Mon, 2011-02-07 at 13:56 +0100, jprovazn(a)redhat.com wrote:
From: Jan Provaznik <jprovazn(a)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'
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
+ 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
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