incubator-deltacloud-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Michal Fojtik <mfoj...@redhat.com>
Subject Re: [PATCH core] Initial run-on-instance support for EC2 and GoGrid
Date Thu, 27 Jan 2011 12:53:23 GMT
On 26/01/11 14:25 -0500, Toby Crawley wrote:
>Michal:
>
>I tested this from the http ui, against ec2, and it works fine for me. I don't have a
gogrid account set up, so I did not test that.
>
>I'd like to test it from the client, but don't see an action on the instance to run a
command. Is there currently a way to do it from the client? If I can get it working from the
client, I'll ACK.

Yes, client was missing support for this feature, however I implemented it
to client and now you can do this magic:

client = DeltaCloud::new(user, pass, url)
puts client.instance('i-1234565').run('uname -a', :pem => path_to_pem_file)

Note: I added 'run' link to instance in EC2 to instruct client library to
pick it up and wrap with run logic.

Pushed to master.

   -- Michal

>Toby
>
>On Jan 17, 2011, at 6:28 AM, mfojtik@redhat.com wrote:
>
>> From: Michal Fojtik <mfojtik@redhat.com>
>>
>> ---
>> server/deltacloud.rb                               |    1 +
>> server/lib/deltacloud/drivers/ec2/ec2_driver.rb    |   16 +++-
>> .../lib/deltacloud/drivers/gogrid/gogrid_driver.rb |   15 +++
>> server/lib/deltacloud/runner.rb                    |  129 ++++++++++++++++++++
>> server/lib/deltacloud/validation.rb                |    8 +-
>> server/server.rb                                   |   27 ++++
>> server/views/docs/operation.html.haml              |    2 +
>> server/views/instances/index.html.haml             |    2 +
>> server/views/instances/run.html.haml               |    9 ++
>> server/views/instances/run.xml.haml                |    7 +
>> server/views/instances/run_command.html.haml       |   16 +++
>> 11 files changed, 229 insertions(+), 3 deletions(-)
>> create mode 100644 server/lib/deltacloud/runner.rb
>> create mode 100644 server/views/instances/run.html.haml
>> create mode 100644 server/views/instances/run.xml.haml
>> create mode 100644 server/views/instances/run_command.html.haml
>>
>> diff --git a/server/deltacloud.rb b/server/deltacloud.rb
>> index 516963e..83f7cfb 100644
>> --- a/server/deltacloud.rb
>> +++ b/server/deltacloud.rb
>> @@ -38,3 +38,4 @@ require 'deltacloud/models/load_balancer'
>>
>> require 'deltacloud/validation'
>> require 'deltacloud/helpers'
>> +require 'deltacloud/runner'
>> diff --git a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
>> index 7a4b394..5533c73 100644
>> --- a/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
>> +++ b/server/lib/deltacloud/drivers/ec2/ec2_driver.rb
>> @@ -200,6 +200,20 @@ module Deltacloud
>>             new_instance
>>           end
>>         end
>> +
>> +        def run_on_instance(credentials, opts={})
>> +          target = instance(credentials, :id => opts[:id])
>> +          param = {}
>> +          param[:credentials] = {
>> +            :username => 'root', # Default for EC2 Linux instances
>> +          }
>> +          param[:port] = opts[:port] || '22'
>> +          param[:ip] = target.public_addresses
>> +          param[:private_key] = (opts[:private_key].length > 1) ? opts[:private_key]
: nil
>> +          safely do
>> +            Deltacloud::Runner.execute(opts[:cmd], param)
>> +          end
>> +        end
>>
>>         def reboot_instance(credentials, instance_id)
>>           ec2 = new_client(credentials)
>> @@ -686,7 +700,7 @@ module Deltacloud
>>           {
>>             :auth => [], # [ ::Aws::AuthFailure ],
>>             :error => [ ::Aws::AwsError ],
>> -            :glob => [ /AWS::(\w+)/ ]
>> +            :glob => [ /AWS::(\w+)/, /Deltacloud::Runner::(\w+)/ ]
>>           }
>>         end
>>
>> diff --git a/server/lib/deltacloud/drivers/gogrid/gogrid_driver.rb b/server/lib/deltacloud/drivers/gogrid/gogrid_driver.rb
>> index 8b9bf9d..909fade 100644
>> --- a/server/lib/deltacloud/drivers/gogrid/gogrid_driver.rb
>> +++ b/server/lib/deltacloud/drivers/gogrid/gogrid_driver.rb
>> @@ -121,6 +121,21 @@ class GogridDriver < Deltacloud::BaseDriver
>>     end
>>   end
>>
>> +  def run_on_instance(credentials, opts={})
>> +    target = instance(credentials, opts[:id])
>> +    param = {}
>> +    param[:credentials] = {
>> +      :username => target.username,
>> +      :password => target.password,
>> +    }
>> +    param[:credentials].merge!({ :password => opts[:password]}) if opts[:password].length>0
>> +    param[:port] = opts[:port] || '22'
>> +    param[:ip] = target.public_addresses
>> +    Deltacloud::Runner.execute(opts[:cmd], param)
>> +  end
>> +
>> +
>> +
>>   def list_instances(credentials, id)
>>     instances = []
>>     safely do
>> diff --git a/server/lib/deltacloud/runner.rb b/server/lib/deltacloud/runner.rb
>> new file mode 100644
>> index 0000000..a4e5e25
>> --- /dev/null
>> +++ b/server/lib/deltacloud/runner.rb
>> @@ -0,0 +1,129 @@
>> +# Copyright (C) 2009, 2010  Red Hat, Inc.
>> +#
>> +# Licensed to the Apache Software Foundation (ASF) under one or more
>> +# contributor license agreements.  See the NOTICE file distributed with
>> +# this work for additional information regarding copyright ownership.  The
>> +# ASF licenses this file to you under the Apache License, Version 2.0 (the
>> +# "License"); you may not use this file except in compliance with the
>> +# License.  You may obtain a copy of the License at
>> +#
>> +#       http://www.apache.org/licenses/LICENSE-2.0
>> +#
>> +# Unless required by applicable law or agreed to in writing, software
>> +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
>> +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
>> +# License for the specific language governing permissions and limitations
>> +# under the License.
>> +
>> +require 'net/ssh'
>> +require 'socket'
>> +require 'tempfile'
>> +
>> +module Deltacloud
>> +
>> +  module Runner
>> +
>> +    class RunnerError < StandardError
>> +      attr_reader :message
>> +      def initialize(message)
>> +        @message = message
>> +        super
>> +      end
>> +    end
>> +
>> +    class InstanceSSHError < RunnerError; end
>> +
>> +    def self.execute(command, opts={})
>> +
>> +      if opts[:credentials] and (not opts[:credentials][:password] and not opts[:private_key])
>> +        raise RunnerError::new("Either password or key must be specified")
>> +      end
>> +
>> +      # First check networking and firewalling
>> +      network = Network::new(opts[:ip], opts[:port])
>> +
>> +      # Then check SSH availability
>> +      ssh = SSH::new(network, opts[:credentials], opts[:private_key])
>> +
>> +      # Finaly execute SSH command on instance
>> +      ssh.execute(command)
>> +    end
>> +
>> +    class Network
>> +      attr_accessor :ip, :port
>> +
>> +      def initialize(ip, port)
>> +        @ip, @port = ip, port
>> +      end
>> +    end
>> +
>> +    class SSH
>> +
>> +      attr_reader :network
>> +      attr_accessor :credentials, :key
>> +      attr_reader :command
>> +
>> +      def initialize(network, credentials, key=nil)
>> +        @network, @credentials, @key = network, credentials, key
>> +        @result = ""
>> +      end
>> +
>> +      def execute(command)
>> +        @command = command
>> +        config = ssh_config(@network, @credentials, @key)
>> +        begin
>> +          session = nil
>> +          Timeout::timeout(5) do
>> +            session = Net::SSH.start(@network.ip, 'root', config)
>> +          end
>> +          session.open_channel do |channel|
>> +            channel.on_data do |ch, data|
>> +              @result += data
>> +            end
>> +            channel.exec(command)
>> +            session.loop
>> +          end
>> +          session.close
>> +        rescue Exception => e
>> +          raise InstanceSSHError.new("#{e.class.name}: #{e.message}")
>> +        ensure
>> +          # FileUtils.rm(config[:keys].first) rescue nil
>> +        end
>> +        Deltacloud::Runner::Response.new(self, @result)
>> +      end
>> +
>> +      private
>> +
>> +      def ssh_config(network, credentials, key)
>> +        config = { :port => network.port }
>> +        config.merge!({ :password => credentials[:password ]}) if credentials[:password]
>> +        config.merge!({ :keys => [ keyfile(key) ] }) unless key.nil?
>> +        config
>> +      end
>> +
>> +      # Right now there is no way howto pass private_key using String
>> +      # eg. without saving key to temporary file.
>> +      def keyfile(key)
>> +        keyfile = Tempfile.new("ec2_private.key")
>> +        key_material = ""
>> +        key.split("\n").each { |line| key_material+="#{line.strip}\n" if line.strip.size>0
}
>> +        keyfile.write(key_material) && keyfile.close
>> +        puts "[*] Using #{keyfile.path} as private key"
>> +        keyfile.path
>> +      end
>> +
>> +    end
>> +
>> +    class Response
>> +
>> +      attr_reader :body
>> +      attr_reader :ssh
>> +
>> +      def initialize(ssh, response_body)
>> +        @body, @ssh = response_body, ssh
>> +      end
>> +
>> +    end
>> +
>> +  end
>> +end
>> diff --git a/server/lib/deltacloud/validation.rb b/server/lib/deltacloud/validation.rb
>> index 18e71cc..e3ca911 100644
>> --- a/server/lib/deltacloud/validation.rb
>> +++ b/server/lib/deltacloud/validation.rb
>> @@ -37,8 +37,12 @@ module Deltacloud::Validation
>>       @name = args[0]
>>       @klass = args[1] || :string
>>       @type = args[2] || :optional
>> -      @options = args[3] || []
>> -      @description = args[4] || ''
>> +      if args[3] and args[3].class.eql?(String)
>> +        @description = args[3]
>> +        @options = []
>> +      end
>> +      @options ||= args[3] || []
>> +      @description ||= args[4] || ''
>>     end
>>
>>     def required?
>> diff --git a/server/server.rb b/server/server.rb
>> index 8c3b72c..bbcf6c6 100644
>> --- a/server/server.rb
>> +++ b/server/server.rb
>> @@ -211,6 +211,13 @@ get "/api/instances/new" do
>>   end
>> end
>>
>> +get '/api/instances/:id/run' do
>> +  @instance = driver.instance(credentials, :id => params[:id])
>> +  respond_to do |format|
>> +    format.html { haml :"instances/run_command" }
>> +  end
>> +end
>> +
>> get '/api/load_balancers/new' do
>>   @realms = driver.realms(credentials)
>>   @instances = driver.instances(credentials) if driver_has_feature?(:register_instance,
:load_balancers)
>> @@ -356,6 +363,26 @@ END
>>     param :id,           :string, :required
>>     control { instance_action(:destroy) }
>>   end
>> +
>> +  operation :run, :method => :post, :member => true do
>> +    description <<END
>> +  Run command on instance. Either password or private key must be send
>> +  in order to execute command. Authetication method should be advertised
>> +  in instance.
>> +END
>> +    with_capability :run_on_instance
>> +    param :id,          :string,  :required
>> +    param :cmd,         :string,  :required, "Shell command to run on instance"
>> +    param :private_key, :string,  :optional, "Private key in PEM format for authentication"
>> +    param :password,    :string,  :optional, "Password used for authentication"
>> +    control do
>> +      @output = driver.run_on_instance(credentials, params)
>> +      respond_to do |format|
>> +        format.xml { haml :"instances/run" }
>> +        format.html { haml :"instances/run" }
>> +      end
>> +    end
>> +  end
>> end
>>
>> collection :hardware_profiles do
>> diff --git a/server/views/docs/operation.html.haml b/server/views/docs/operation.html.haml
>> index 4b483b8..b7c3538 100644
>> --- a/server/views/docs/operation.html.haml
>> +++ b/server/views/docs/operation.html.haml
>> @@ -17,6 +17,7 @@
>>       %th Type
>>       %th Class
>>       %th Valid values
>> +      %th Description
>>   %tbody
>>     - @operation.each_param do |p|
>>       %tr
>> @@ -25,3 +26,4 @@
>>         %td{:style => "width:10em"} #{p.type}
>>         %td #{p.klass}
>>         %td{:style => "width:10em"} #{p.options.join(',')}
>> +        %td #{p.description}
>> diff --git a/server/views/instances/index.html.haml b/server/views/instances/index.html.haml
>> index 2bd4607..e855439 100644
>> --- a/server/views/instances/index.html.haml
>> +++ b/server/views/instances/index.html.haml
>> @@ -28,3 +28,5 @@
>>         %td
>>           -instance.actions.each do |action|
>>             =link_to_action action, self.send(:"#{action}_instance_url", instance.id),
instance_action_method(action)
>> +          - if driver.respond_to?(:run_on_instance) and instance.state=="RUNNING"
>> +            =link_to_action "Run command", url_for("/api/instances/#{instance.id}/run"),
:get
>> diff --git a/server/views/instances/run.html.haml b/server/views/instances/run.html.haml
>> new file mode 100644
>> index 0000000..88c91ee
>> --- /dev/null
>> +++ b/server/views/instances/run.html.haml
>> @@ -0,0 +1,9 @@
>> +%h1 Run command on instance #{params[:id]}
>> +
>> +%p
>> +  %label Command:
>> +  %em #{@output.ssh.command}
>> +%p
>> +  %strong Command output
>> +%pre
>> +  =@output.body
>> diff --git a/server/views/instances/run.xml.haml b/server/views/instances/run.xml.haml
>> new file mode 100644
>> index 0000000..4201e20
>> --- /dev/null
>> +++ b/server/views/instances/run.xml.haml
>> @@ -0,0 +1,7 @@
>> +%instance{:id => params[:id], :href=> instance_url(params[:id])}
>> +  %public_address
>> +    =@output.ssh.network.ip
>> +  %command
>> +    =@output.ssh.command
>> +  %output<
>> +    =@output.body
>> diff --git a/server/views/instances/run_command.html.haml b/server/views/instances/run_command.html.haml
>> new file mode 100644
>> index 0000000..68e5046
>> --- /dev/null
>> +++ b/server/views/instances/run_command.html.haml
>> @@ -0,0 +1,16 @@
>> +%h1
>> +  Run command on
>> +  = @instance.id
>> +
>> +%form{ :action => run_instance_url(@instance.id), :method => :post }
>> +  %p
>> +    %label{ :for => :cmd } Desired command:
>> +    %input{ :name => :cmd, :value => "", :type => :text}
>> +  %p
>> +    %label{ :for => :private_key } Private key:
>> +  %p
>> +    %small Leave private key blank if using password authentication method
>> +  %p
>> +    %textarea{ :name => :private_key, :cols => 65, :rows => 20 }
>> +  %p
>> +    %input{ :type => :submit, :value => "Execute" }
>> --
>> 1.7.3.4
>>
>
>---
>Toby Crawley
>tcrawley@redhat.com
>
>
>
>

-- 
--------------------------------------------------------
Michal Fojtik, mfojtik@redhat.com
Deltacloud API: http://deltacloud.org
--------------------------------------------------------

Mime
View raw message