On 02/07/2011 06:16 PM, Jason Guiditta wrote:
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
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))
> + 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