incubator-deltacloud-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Toby Crawley <tcraw...@redhat.com>
Subject Re: [PATCH core] Initial run-on-instance support for EC2 and GoGrid
Date Wed, 26 Jan 2011 19:25:04 GMT
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.

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





Mime
View raw message