Skip to content

Commit

Permalink
Merge pull request #79 from dhirajh/master
Browse files Browse the repository at this point in the history
Add Openstack related changes to Rouster.
  • Loading branch information
chorankates-sfdc committed Sep 14, 2015
2 parents 5dfd79f + db03e1a commit 76b3be7
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 11 deletions.
51 changes: 49 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The first implementation of Rouster was in Perl, called [Salesforce::Vagrant](ht
* log4r
* net-scp
* net-ssh
* fog (only if using AWS)
* fog (only if using AWS or OpenStack)

Note: Rouster should work exactly the same on Windows as it does on \*nix and OSX (minus rouster/deltas.rb functionality, at least currently),
but no real testing has been done to confirm this. Please file issues as appropriate.
Expand Down Expand Up @@ -149,13 +149,14 @@ app.destroy()

### advanced instantiation (passthroughs!)

detailed options in ```examples/passthrough.rb``` and ```examples/aws.rb```
detailed options in ```examples/passthrough.rb```, ```examples/aws.rb``` and ```examples/openstack.rb```

since Rouster only requires an SSH connection to control a machine, why stop at Vagrant?

```rb
require 'rouster'
require 'rouster/plugins/aws'
require 'rouster/plugins/openstack'

# control the machine rouster itself is running on
local = Rouster.new(:name => 'local', :passthrough => { :type => :local } }
Expand Down Expand Up @@ -195,6 +196,38 @@ aws_start_me_up = Rouster.new(
}
)

# create a remote OpenStack instance
ostack = Rouster.new(
:name => 'ostack-testing',
:passthrough => {
:type => :openstack,
:openstack_auth_url => 'http://hostname.domain.com:5000/v2.0/tokens',
:openstack_username => 'some_console_user',
:openstack_tenant => 'tenant_id',
:user => 'some_ssh_userid',
:keypair => 'keypair_name',
:image_ref => 'c0340afb-577d-4db6-1234-aebdd6d1838f',
:flavor_ref => '547d9af5-096c-44a3-1234-7d23162556b8',
:openstack_api_key => 'some_api_key',
:key => '/path/to/private/key.pem',
},
:sudo => true, # false by default, enabling requires that sshd is not enforcing 'requiretty'
)

# control a running OpenStack instance
openstack_already_running = Rouster.new(
:name => 'ostack-copy',
:passthrough => {
:type => :openstack,
:openstack_auth_url => 'http://hostname.domain.com:5000/v2.0/tokens',
:openstack_username => 'some_console_user',
:openstack_tenant => 'tenant_id',
:user => 'ssh_user',
:keypair => 'keypair_name',
:instance => 'your-instance-id',
},
)

```

### functional puppet test
Expand Down Expand Up @@ -353,3 +386,17 @@ irb(main):004:0> pp (Rouster.new(:name => 'aws', :passthrough => { :type => :aws
...
]
```

## Openstack methods

```rb
[
:ostack_connect,
:ostack_describe_instance,
:ostack_destroy,
:ostack_get_instance_id,
:ostack_get_ip,
:ostack_status,
:ostack_up
]
```
61 changes: 61 additions & 0 deletions examples/openstack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
require sprintf('%s/../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')

require 'rouster'
require 'plugins/openstack' # brings in fog and some helpers

ostack = Rouster.new(
:name => 'ostack-testing',
:sudo => false,
:logfile => true,
:passthrough => {
:type => :openstack, # Indicate OpenStack provider
:openstack_auth_url => 'http://hostname.acme.com:5000/v2.0/tokens', # OpenStack API endpoint
:openstack_username => 'some_user', # OpenStack console username
:openstack_tenant => 'tenant_id', # Tenant ID
:user => 'ssh_user_id', # SSH login ID
:keypair => 'openstack_key_name', # Name of ssh keypair in OpenStack
:image_ref => 'c0340afb-577d-1234-87b2-aebdd6d1838f', # Image ID in OpenStack
:flavor_ref => '547d9af5-096c-1234-98df-7d23162556b8', # Flavor ID in OpenStack
:openstack_api_key => 'secret_openstack_key', # OpenStack console password
:key => '/path/to/ssh_keys.pem', # SSH key filename
},
:sshtunnel => false,
:verbosity => 1,
)

p "UP(): #{ostack.up}"
p "STATUS(): #{ostack.status}"

ostack_copy = Rouster.new(
:name => 'ostack-copy',
:sudo => false,
:logfile => true,
:passthrough => {
:type => :openstack, # Indicate OpenStack provider
:openstack_auth_url => 'http://hostname.acme.com:5000/v2.0/tokens', # OpenStack API endpoint
:openstack_username => 'some_user', # OpenStack console username
:openstack_tenant => 'tenant_id', # Tenant ID
:user => 'ssh_user_id', # SSH login ID
:keypair => 'openstack_key_name', # Name of ssh keypair in OpenStack
:openstack_api_key => 'secret_openstack_key', # OpenStack console password
:instance => ostack.ostack_get_instance_id, # ID of a running OpenStack instance.
},
:sshtunnel => false,
:verbosity => 1,
)

[ ostack, ostack_copy ].each do |o|
p "ostack_get_instance_id: #{o.ostack_get_instance_id}"

p "status: #{o.status}"

p "ostack_get_ip(): #{o.ostack_get_ip()}"

p "run(uptime): #{o.run('uptime')}"
p "get(/etc/hosts): #{o.get('/etc/hosts')}"
p "put(/etc/hosts, /tmp): #{o.put('/etc/hosts', '/tmp')}"

end

p "DESTROY(): #{ostack.destroy}"
exit
23 changes: 22 additions & 1 deletion lib/rouster.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,30 @@ def initialize(opts = nil)

raise ArgumentError.new('AWS passthrough requires valid :sshkey specification, should be path to private half') unless File.file?(@passthrough[:key])
@sshkey = @passthrough[:key]
elsif @passthrough[:type].eql?(:openstack)
@logger.debug(sprintf('instantiating an %s passthrough worker', @passthrough[:type]))
@sshkey = @passthrough[:key]

ostack_defaults = {
:ssh_port => 22,
}
@passthrough = ostack_defaults.merge(@passthrough)

[:openstack_auth_url, :openstack_username, :openstack_tenant, :openstack_api_key,
:key ].each do |r|
raise ArgumentError.new(sprintf('OpenStack passthrough requires %s specification', r)) if @passthrough[r].nil?
end

if @passthrough.has_key?(:image_ref)
@logger.debug(':image_ref specified, will start new Nova instance')
elsif @passthrough.has_key?(:instance)
@logger.debug(':instance specified, will connect to existing OpenStack instance')
inst_details = self.ostack_describe_instance(@passthrough[:instance])
raise ArgumentError.new(sprintf('No such instance found in OpenStack - %s', @passthrough[:instance])) if inst_details.nil?
@passthrough[:host] = inst_details.addresses["NextGen"][0]["addr"]
end
else
raise ArgumentError.new(sprintf('passthrough :type [%s] unknown, allowed: :aws, :local, :remote', @passthrough[:type]))
raise ArgumentError.new(sprintf('passthrough :type [%s] unknown, allowed: :aws, :openstack, :local, :remote', @passthrough[:type]))
end

else
Expand Down
33 changes: 25 additions & 8 deletions lib/rouster/vagrant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,14 @@ def up
@logger.info('up()')

# don't like putting this here, may be refactored
if self.is_passthrough? and (self.passthrough[:type].equal?(:aws) or self.passthrough[:type].equal?(:raiden))
self.aws_up()
if self.is_passthrough?
if (self.passthrough[:type].equal?(:aws) or self.passthrough[:type].equal?(:raiden))
self.aws_up()
elsif (self.passthrough[:type].equal?(:openstack))
self.ostack_up()
else
self.vagrant(sprintf('up %s', @name))
end
else
self.vagrant(sprintf('up %s', @name))
end
Expand Down Expand Up @@ -81,8 +87,14 @@ def destroy
@logger.info('destroy()')

# don't like putting this here, may be refactored
if self.is_passthrough? and (self.passthrough[:type].equal?(:aws) or self.passthrough[:type].equal?(:raiden))
self.aws_destroy()
if self.is_passthrough?
if (self.passthrough[:type].equal?(:aws) or self.passthrough[:type].equal?(:raiden))
self.aws_destroy()
elsif self.is_passthrough? and self.passthrough[:type].equal?(:openstack)
self.ostack_destroy()
else
raise InternalError.new(sprintf('failed to execute destroy(), unsupported passthrough type %s', self.passthrough[:type]))
end
else
self.vagrant(sprintf('destroy -f %s', @name))
end
Expand All @@ -109,8 +121,14 @@ def status

# don't like putting this here, may be refactored
@logger.info('status()')
if self.is_passthrough? and (self.passthrough[:type].equal?(:aws) or self.passthrough[:type].equal?(:raiden))
status = self.aws_status()
if self.is_passthrough?
if (self.passthrough[:type].equal?(:aws) or self.passthrough[:type].equal?(:raiden))
status = self.aws_status()
elsif self.passthrough[:type].equal?(:openstack)
status = self.ostack_status()
else
raise InternalError.new(sprintf('failed to execute status(), unsupported passthrough type %s', self.passthrough[:type]))
end
else
self.vagrant(sprintf('status %s', @name))

Expand Down Expand Up @@ -274,5 +292,4 @@ def sandbox_commit
end
end


end
end
129 changes: 129 additions & 0 deletions plugins/openstack.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/ruby
## plugins/openstack.rb - provide helper functions for Rouster objects running on OpenStack/Compute

require sprintf('%s/../%s', File.dirname(File.expand_path(__FILE__)), 'path_helper')

require 'fog'
require 'uri'

class Rouster

attr_reader :nova # expose OpenStack workers
attr_reader :instance_data # the result of the runInstances request

# return a hash containing meta-data items
def ostack_get_instance_id ()
# The instance id is kept in @passthrough[:instance] or
# can be obtained from @instance_data which has all instance
# details.
if ! @instance_data.nil? and ! @instance_data.id.nil?
return @instance_data.id # we already know the id
elsif @passthrough.has_key?(:instance)
return @passthrough[:instance] # we know the id we want
else
@logger.debug(sprintf('unable to determine id from instance_data[%s] or passthrough specification[%s]', @instance_data, @passthrough))
return nil # we don't have an id yet, likely a up() call
end
end

def ostack_up
# wait for machine to transition to running state and become sshable (TODO maybe make the second half optional)
self.ostack_connect
# This will check if instance_id has been provided. If so, it will check on status of the instance.
status = self.status()
if status.eql?('running')
self.passthrough[:instance] = self.ostack_get_instance_id
@logger.debug(sprintf('Connecting to running instance [%s] while calling ostack_up()', self.passthrough[:instance]))
self.connect_ssh_tunnel
else
server = @nova.servers.create(:name => @name, :flavor_ref => @passthrough[:flavor_ref],
:image_ref => @passthrough[:image_ref], :key_name => @passthrough[:keypair])
server.wait_for { ready? }
@instance_data = server
self.passthrough[:host] = server.addresses["NextGen"][0]["addr"]
self.passthrough[:instance] = self.ostack_get_instance_id
end
self.passthrough[:instance]
end

def ostack_get_ip()
self.passthrough[:host]
end

def ostack_destroy
server = self.ostack_describe_instance
raise sprintf("instance[%s] not found by destroy()", self.ostack_get_instance_id) if server.nil?
server.destroy
@instance_data = nil
self.passthrough.delete(:instance)
end

def ostack_describe_instance(instance_id = ostack_get_instance_id)

if @cache_timeout
if @cache.has_key?(:ostack_describe_instance)
if (Time.now.to_i - @cache[:ostack_describe_instance][:time]) < @cache_timeout
@logger.debug(sprintf('using cached ostack_describe_instance?[%s] from [%s]', @cache[:ostack_describe_instance][:instance], @cache[:ostack_describe_instance][:time]))
return @cache[:ostack_describe_instance][:instance]
end
end
end
# We don't have a instance.
return nil if instance_id.nil?
self.ostack_connect
response = @nova.servers.get(instance_id)
return nil if response.nil?
@instance_data = response

if @cache_timeout
@cache[:ostack_describe_instance] = Hash.new unless @cache[:ostack_describe_instance].class.eql?(Hash)
@cache[:ostack_describe_instance][:time] = Time.now.to_i
@cache[:ostack_describe_instance][:instance] = response
@logger.debug(sprintf('caching is_available_via_ssh?[%s] at [%s]', @cache[:ostack_describe_instance][:instance], @cache[:ostack_describe_instance][:time]))
end

@instance_data
end

def ostack_status
self.ostack_describe_instance
return 'not-created' if @instance_data.nil?
if @instance_data.state.eql?('ACTIVE')
# Make this consistent with AWS response.
return 'running'
else
return @instance_data.state
end
end


# TODO this will throw at the first error - should we catch?
# run some commands, return an array of the output
def ostack_bootstrap (commands)
self.ostack_connect
commands = (commands.is_a?(Array)) ? commands : [ commands ]
output = Array.new

commands.each do |command|
output << self.run(command)
end

return output
end

def ostack_connect
# Instantiates an Object which can communicate with OS Compute.
# No instance specific information is set at this time.
return @nova unless @nova.nil?

config = {
:provider => 'openstack', # OpenStack Fog provider
:openstack_auth_url => self.passthrough[:openstack_auth_url], # OpenStack Keystone endpoint
:openstack_username => self.passthrough[:openstack_username], # Your OpenStack Username
:openstack_tenant => self.passthrough[:openstack_tenant], # Your tenant id
:openstack_api_key => self.passthrough[:openstack_api_key], # Your OpenStack Password
:connection_options => self.passthrough[:connection_options] # Optional
}
@nova = Fog::Compute.new(config)
end
end

0 comments on commit 76b3be7

Please sign in to comment.