rest_connection 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc.starting.point +202 -0
- data/VERSION +1 -1
- data/lib/rest_connection/rightscale/mc_server.rb +34 -20
- data/lib/rest_connection/rightscale/server.rb +2 -2
- data/login_to_connection_irb.rb +22 -0
- metadata +11 -6
@@ -0,0 +1,202 @@
|
|
1
|
+
= Rest Connection
|
2
|
+
|
3
|
+
The rest_connection gem simplifies the use of RightScale's MultiCloud API. It provides
|
4
|
+
a simple object model of the API resources, and handles all of the fine details involved
|
5
|
+
in making HTTP calls and translating their responses.
|
6
|
+
It is assumed that users are already familiar with the RightScale API:
|
7
|
+
- API Documentation: http://support.rightscale.com/12-Guides/RightScale_API_1.5
|
8
|
+
- API Reference Docs: http://support.rightscale.com/api1.5
|
9
|
+
|
10
|
+
== Installation
|
11
|
+
Ruby 1.8.7 or higher is required.
|
12
|
+
gem install rest_connection
|
13
|
+
|
14
|
+
== Versioning
|
15
|
+
The rest_connection gem is versioned using the usual X.Y.Z notation, where X.Y is the
|
16
|
+
RightScale API version, and Z is the client version. For example, if you want to use
|
17
|
+
RightScale API 1.5, you should use the latest version of the 1.5 gem. This will ensure
|
18
|
+
that you get the latest bug fixes for the client that is compatible with that API version.
|
19
|
+
|
20
|
+
== Usage Instructions
|
21
|
+
New users can start with the following few lines of code and navigate their way around the API by following
|
22
|
+
the available methods. You can find your account id by logging into the RightScale dashboard (https://my.rightscale.com),
|
23
|
+
navigate to the Settings > Account Settings page. The account is is at the end of the browser address bar.
|
24
|
+
require 'rest_connection'
|
25
|
+
@client = RightApi::Client.new(:email => 'my@email.com', :password => 'my_password', :account_id => 'my_account_id')
|
26
|
+
puts "Available methods: #{@client.api_methods}"
|
27
|
+
|
28
|
+
The client makes working with and getting to know the API much easier. It spiders the API dynamically to
|
29
|
+
discover its resources on the fly. At every step, the user has the ability to query api_methods(), which
|
30
|
+
indicates the potential methods that can be called. The <tt>config/login.yml.example</tt> file provides
|
31
|
+
details of different login parameters.
|
32
|
+
|
33
|
+
=== Making API calls
|
34
|
+
Essentially, just follow the RightScale API documentation (available from http://support.rightscale.com)
|
35
|
+
and treat every resource in the paths as objects that can call other objects using the dot (.) operator:
|
36
|
+
|
37
|
+
Examples:
|
38
|
+
- Index: /api/clouds/:cloud_id/datacenters => @client.clouds(:id => :cloud_id).show.datacenters.index
|
39
|
+
- Show: /api/clouds/:cloud_id/datacenters/:id => @client.clouds(:id => :cloud_id).show.datacenters(:id => :datacenter_id).show
|
40
|
+
- Create: /api/deployments/:deployment_id/servers => @client.deployments(:id => :deployment_id).show.servers.create
|
41
|
+
- Update: /api/deployments/:deployment_id/servers/:id => @client.deployments(:id => :deployment_id).show.servers(:id => :server_id).update
|
42
|
+
- Destroy: /api/deployments/:deployment_id/servers/:id => @client.deployments(:id => :deployment_id).show.servers(:id => :server_id).destroy
|
43
|
+
- An action: /api/servers/:server_id/launch => @client.servers(:id => :server_id).show.launch
|
44
|
+
|
45
|
+
As seen above, whenever you need to chain methods, you must call .show before specifying the next method.
|
46
|
+
|
47
|
+
=== Parameters
|
48
|
+
Pass-in parameters to the method that they belong to. Lets say you want to filter on the index for deployments:
|
49
|
+
@client.deployments.index(:filter => ['name==my_deployment'])
|
50
|
+
The filter is the parameter for the index call and not the deployment call.
|
51
|
+
|
52
|
+
=== Logging HTTP Requests
|
53
|
+
The HTTP calls made by rest_connection can be logged in two ways:
|
54
|
+
1. Log to a file
|
55
|
+
@client.log('~/rest_connection.log')
|
56
|
+
2. Log to SDTOUT
|
57
|
+
@client.log(STDOUT)
|
58
|
+
|
59
|
+
== Examples
|
60
|
+
Get a list of all servers (aka doing an Index call)
|
61
|
+
@client.servers.index
|
62
|
+
|
63
|
+
Get a list of all servers in a deployment
|
64
|
+
@client.deployments(:id => 'my_deployment_id').show.servers.index
|
65
|
+
|
66
|
+
Get a particular server (aka doing a Show call)
|
67
|
+
@client.servers(:id => 'my_server_id').show
|
68
|
+
|
69
|
+
Creating a server involves setting up the required parameters, then calling the create method
|
70
|
+
server_template_href = @client.server_templates.index(:filter => ['name==Base ServerTemplate']).first.href
|
71
|
+
cloud = @client.clouds(:id => 'my_cloud_id').show
|
72
|
+
params = { :server => {
|
73
|
+
:name => 'Test Server',
|
74
|
+
:deployment_href => @client.deployments(:id => 'my_deployment_id').show.href,
|
75
|
+
:instance => {
|
76
|
+
:server_template_href => server_template_href,
|
77
|
+
:cloud_href => cloud.href,
|
78
|
+
:security_group_hrefs => [cloud.security_groups.index(:filter => ['name==default']).first.href],
|
79
|
+
:ssh_key_href => cloud.ssh_keys.index.first.href,
|
80
|
+
:datacenter_href => cloud.datacenters.index.first.href
|
81
|
+
}}}
|
82
|
+
new_server = @client.servers.create(params)
|
83
|
+
new_server.api_methods
|
84
|
+
|
85
|
+
Launch the newly created server. Inputs are a bit tricky so they have to be set in a long string
|
86
|
+
inputs = "inputs[][name]=NAME1&inputs[][value]=text:VALUE1&inputs[][name]=NAME2&inputs[][value]=text:VALUE2"
|
87
|
+
new_server.show.launch(inputs)
|
88
|
+
|
89
|
+
Run a script on the server. The API does not currently expose right_scripts, hence, the script href has
|
90
|
+
to be retrieved from the dashboard and put in the following href format.
|
91
|
+
script_href = "right_script_href=/api/right_scripts/382371"
|
92
|
+
task = new_server.show.current_instance.show.run_executable(script_href + "&inputs[][name]=TEST_NAME&inputs[][value]=text:VALUE1")
|
93
|
+
task.show.api_methods
|
94
|
+
|
95
|
+
Update the server's name
|
96
|
+
params = { :server => {:name => 'New Server Name'}}
|
97
|
+
@client.servers(:id => 'my_server_id').update(params)
|
98
|
+
|
99
|
+
Terminate the server (i.e. shutdown its current_instance)
|
100
|
+
@client.servers(:id => 'my_server_id').show.terminate
|
101
|
+
|
102
|
+
Destroy the server (i.e. delete it)
|
103
|
+
@client.servers(:id => 'my_server_id').destroy
|
104
|
+
|
105
|
+
== Object Types
|
106
|
+
The client returns 3 types of objects:
|
107
|
+
- <b>Resources</b>: returned when you are querying a collection of resources, e.g.: <tt>client.deployments</tt>
|
108
|
+
- <b>Resource</b>: returned when you specify an id and therefore a specific resource, e.g.: <tt>@client.deployments(:id => :deployment_id)</tt>
|
109
|
+
- When the content-type is type=collection then an array of Resource objects will be returned, e.g.: <tt>@client.deployments.index</tt>
|
110
|
+
- When the content-type is not a collection then a Resource object will be returned, e.g.: <tt>@client.deployments(:id => deployment_id).show</tt>
|
111
|
+
- <b>ResourceDetail</b>: returned when you do a .show on a Resource, e.g.: <tt>client.deployments(:id => deployment_id).show</tt>
|
112
|
+
<b>On all 3 types of objects you can query <tt>.api_methods</tt> to see a list of available methods, e.g.: <tt>client.deployments.api_methods</tt></b>
|
113
|
+
|
114
|
+
=== Exceptions:
|
115
|
+
- <tt>inputs.index</tt> will return an array of ResourceDetail objects since you cannot do a .show on an input
|
116
|
+
- <tt>session.index</tt> will return a ResourceDetail object since you cannot do a .show on a session
|
117
|
+
- <tt>tags.by_resource, tags.by_tag</tt> will return an array of ResourceDetail objects since you cannot do a .show on a resource_tag
|
118
|
+
- <tt>monitoring_metrics(:id => :m_m_id).show.data</tt> will return a ResourceDetail object since you cannot do
|
119
|
+
a .show on a monitoring_metric_data
|
120
|
+
|
121
|
+
== Instance Facing Calls:
|
122
|
+
The client also supports 'instance facing calls', which use the instance_token to login.
|
123
|
+
Unlike regular email-password logins, instance-facing-calls are limited in the amount of allowable calls.
|
124
|
+
Since in most of the cases, the calls are scoped to the instance's cloud (or the instance itself), the cloud_id and
|
125
|
+
the instance_id will be automatically recorded by the client, so that the user does not need to specify it.
|
126
|
+
|
127
|
+
=== Examples
|
128
|
+
@instance_client = RightApi::Client.new(:instance_token => 'my_token', :account_id => 'my_account_id')
|
129
|
+
@instance_client.volume_attachments links to /api/clouds/:cloud_id/volume_attachments
|
130
|
+
@instance_client.volumes_snapshots links to /api/clouds/:cloud_id/volumes_snapshots
|
131
|
+
@instance_client.volumes_types links to /api/clouds/:cloud_id/volumes_types
|
132
|
+
@instance_client.volumes links to /api/clouds/:cloud_id/volumes
|
133
|
+
@instance_client.backups links to /api/backups
|
134
|
+
@instance_client.live_tasks(:id) links to /api/clouds/:cloud_id/instances/:instance_id/live/tasks/:id
|
135
|
+
|
136
|
+
=== Notes
|
137
|
+
For volume_attachments and volumes_snapshots you can also go through the volume:
|
138
|
+
@instance_client.volumes(:id => :volume_id).show.volume_attachments
|
139
|
+
which maps to:
|
140
|
+
/api/clouds/:cloud_id/volumes/:volume_id/volume_attachment
|
141
|
+
The instance's volume_attachments can be accessed using:
|
142
|
+
@instance_client.get_instance.volume_attachments
|
143
|
+
which maps to:
|
144
|
+
/api/clouds/:cloud_id/instances/:instance_id/volume_attachments
|
145
|
+
|
146
|
+
Because the cloud_id and the instance_id are automatically added by the client, scripts that work for regular
|
147
|
+
email-password logins will have to be modified for instance-facing calls. The main reason behind this is the
|
148
|
+
inability for instance-facing calls to access the clouds resource (i.e.: <tt>@instance_client.clouds(:id=> :cloud_id).show</tt> will fail)
|
149
|
+
|
150
|
+
When you query <tt>api_methods</tt>, it will list all of the methods that one sees with regular email-password logins.
|
151
|
+
Due to the limiting scope of the instance-facing calls, only a subset of these methods can be called
|
152
|
+
(see the API Reference Docs for valid methods). If you call a method that instance's are not authorized to access,
|
153
|
+
you will get a 403 Permission Denied error.
|
154
|
+
|
155
|
+
|
156
|
+
= Design Decisions
|
157
|
+
In the code, we only hard-code CRUD operations for resources. We use the .show and .index methods to make the client
|
158
|
+
more efficient. Since it dynamically creates methods it needs to query the API at times. The .show and the .index make
|
159
|
+
it explicit that querying needs to take place. Without them a GET would have to be queried every step of the way
|
160
|
+
(i.e.: the index call would be client.deployments, and the create call would be client.deployments.create which would
|
161
|
+
first do an index call).
|
162
|
+
|
163
|
+
<b>In general, when a new API resource is added, you need to indicate in the client whether index, show, create, update
|
164
|
+
and delete methods are allowed for that resource.</b>
|
165
|
+
|
166
|
+
== Special Cases
|
167
|
+
=== Returning resource_types that are not actual API resources:
|
168
|
+
- tags:
|
169
|
+
- by_resource, by_tag: both return a COLLECTION of resource_type = RESOURCE_TAG
|
170
|
+
- no show or index is defined for that resource_type, therefore return a collection of ResourceDetail objects
|
171
|
+
- data:
|
172
|
+
- querying .data for monitoring_metrics:
|
173
|
+
- no show is defined for that resource_type, therefore return a ResourceDetail object
|
174
|
+
|
175
|
+
=== Index call does not act like an index call
|
176
|
+
- session:
|
177
|
+
- session.index should act like a show call and not like an index call (since you cannot query show).
|
178
|
+
Therefore it should return a ResourceDetail object
|
179
|
+
- inputs
|
180
|
+
- inputs.index cannot return a collection of Resource objects since .show is not allowed. Therefore it should
|
181
|
+
return a collection of ResourceDetail objects
|
182
|
+
|
183
|
+
=== Having a resource_type that cannot be accurately determined from the URL:
|
184
|
+
- In server_arrays.show: resource_type = current_instance(s) (although it should be instance(s))
|
185
|
+
- In multi_cloud_images.show: resource_type = setting(s) (although it should be multi_cloud_image_setting)
|
186
|
+
Put these special cases in the <tt>RightApi::Helper::INCONSISTENT_RESOURCE_TYPES</tt> hash.
|
187
|
+
|
188
|
+
=== Method defined on the generic resource_type itself
|
189
|
+
- 'instances' => {:multi_terminate => 'do_post', :multi_run_executable => 'do_post'},
|
190
|
+
- 'inputs' => {:multi_update => 'do_put'},
|
191
|
+
- 'tags' => {:by_tag => 'do_post', :by_resource => 'do_post', :multi_add => 'do_post', :multi_delete =>'do_post'},
|
192
|
+
- 'backups' => {:cleanup => 'do_post'}
|
193
|
+
Put these special cases in the <tt>RightApi::Helper::RESOURCE_TYPE_SPECIAL_ACTIONS</tt> hash.
|
194
|
+
|
195
|
+
=== Resources are not linked together
|
196
|
+
- In ResourceDetail, resource_type = Instance, need live_tasks as a method.
|
197
|
+
|
198
|
+
= Troubleshooting
|
199
|
+
|
200
|
+
== Wrong ruby version
|
201
|
+
|
202
|
+
Ruby 1.8.7 or higher is required.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.2
|
@@ -60,32 +60,32 @@ class McServer < Server
|
|
60
60
|
begin
|
61
61
|
connection.post(t.path + '/launch')
|
62
62
|
rescue Exception => e
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
63
|
+
if connection.settings[:azure_hack_on]
|
64
|
+
puts "**** [AZURE_HACK is ON] - McServer.launch() nickname: #{nickname}, caught exception #{e.message}"
|
65
|
+
puts "**** connection.settings[:azure_hack_retry_count] = #{connection.settings[:azure_hack_retry_count]}"
|
66
|
+
puts "**** connection.settings[:azure_hack_sleep_seconds] = #{connection.settings[:azure_hack_sleep_seconds]}"
|
67
|
+
|
68
|
+
# 504 Gateway should always be treated as a successful launch
|
69
|
+
target_504_gateway_timeout_error_message = "504 Gateway Time-out"
|
70
|
+
|
71
|
+
# All 422 exceptions should be retried
|
72
|
+
target_422_error_message = "Invalid response HTTP code: 422:"
|
73
|
+
|
74
|
+
if e.message =~ /#{target_504_gateway_timeout_error_message}/
|
75
|
+
exception_matched_message = "**** McServer.launch(): Caught #{e.message}, treating as a successful launch..."
|
76
|
+
puts(exception_matched_message)
|
77
|
+
connection.logger(exception_matched_message)
|
78
|
+
true
|
79
|
+
elsif e.message =~ /#{target_422_error_message}/
|
80
80
|
azure_hack_retry_count = connection.settings[:azure_hack_retry_count]
|
81
|
-
exception_matched_message = "
|
81
|
+
exception_matched_message = "**** McServer.launch(): Caught #{e.message}, retrying launch..."
|
82
82
|
puts(exception_matched_message)
|
83
83
|
connection.logger(exception_matched_message)
|
84
84
|
|
85
85
|
retry_count = 1
|
86
86
|
loop do
|
87
87
|
# sleep for azure_hack_sleep_seconds seconds
|
88
|
-
sleep_message = "
|
88
|
+
sleep_message = "**** McServer.launch(): Sleeping for #{connection.settings[:azure_hack_sleep_seconds]} seconds and then retrying (#{retry_count}) launch..."
|
89
89
|
puts(sleep_message)
|
90
90
|
connection.logger(sleep_message)
|
91
91
|
sleep(connection.settings[:azure_hack_sleep_seconds])
|
@@ -94,23 +94,37 @@ class McServer < Server
|
|
94
94
|
begin
|
95
95
|
connection.post(t.path + '/launch')
|
96
96
|
rescue Exception => e2
|
97
|
-
|
97
|
+
exception_caught_message = "**** McServer.launch(): Retry caught #{e2.message}..."
|
98
|
+
puts(exception_caught_message)
|
99
|
+
connection.logger(exception_caught_message)
|
100
|
+
|
101
|
+
if e2.message =~ /#{target_422_error_message}/
|
98
102
|
azure_hack_retry_count -= 1
|
99
103
|
if azure_hack_retry_count > 0
|
100
104
|
retry_count += 1
|
105
|
+
|
106
|
+
# Try again on next iteration
|
101
107
|
next
|
102
108
|
else
|
109
|
+
# Azure Hack maximum retries exceeded so rethrow the new 422 exception
|
103
110
|
raise
|
104
111
|
end
|
105
112
|
else
|
113
|
+
# On this re-launch we got some other exception so rethrow it
|
106
114
|
raise
|
107
115
|
end
|
108
116
|
end
|
117
|
+
|
118
|
+
# Fell through so launch worked and we need to break out of the retry do loop
|
109
119
|
break
|
110
120
|
end
|
111
121
|
else
|
122
|
+
# Didn't match on any target exception so rethrow the original exception
|
112
123
|
raise
|
113
124
|
end
|
125
|
+
else
|
126
|
+
# Azure Hack isn't enabled so rethrow the original exception
|
127
|
+
raise
|
114
128
|
end
|
115
129
|
end
|
116
130
|
elsif self.state == "inactive"
|
@@ -97,7 +97,7 @@ class Server
|
|
97
97
|
reload
|
98
98
|
connection.logger("#{nickname} is #{self.state}")
|
99
99
|
step = 15
|
100
|
-
catch_early_terminated =
|
100
|
+
catch_early_terminated = 1200 / step
|
101
101
|
while(timeout > 0)
|
102
102
|
return true if state =~ /#{st}/
|
103
103
|
return true if state =~ /terminated|stopped/ && st =~ /terminated|stopped/
|
@@ -106,7 +106,7 @@ class Server
|
|
106
106
|
connection.logger("waiting for server #{nickname} to go #{st}, state is #{state}")
|
107
107
|
if state =~ /terminated|stopped|inactive|error/ and st !~ /terminated|stopped|inactive|error/
|
108
108
|
if catch_early_terminated <= 0
|
109
|
-
raise "FATAL error, this server
|
109
|
+
raise "FATAL error, this server entered #{state} state waiting for #{st}: #{nickname}"
|
110
110
|
end
|
111
111
|
catch_early_terminated -= 1
|
112
112
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# A quick way to login to the API and jump into IRB so you can experiment with the connection.
|
2
|
+
# Add this to your bash profile to make it simpler:
|
3
|
+
# alias connection='bundle exec ruby login_to_connection_irb.rb'
|
4
|
+
|
5
|
+
require File.expand_path('../lib/rest_connection', __FILE__)
|
6
|
+
require 'yaml'
|
7
|
+
require 'irb'
|
8
|
+
|
9
|
+
begin
|
10
|
+
@connection = RestConnection::Connection.new()
|
11
|
+
puts "logged-in to the API, use the '@connection' variable to use the connection"
|
12
|
+
end
|
13
|
+
|
14
|
+
IRB.start
|
15
|
+
|
16
|
+
#
|
17
|
+
#require 'rubygems'
|
18
|
+
#require 'rest_connection'
|
19
|
+
#require 'ruby-debug'
|
20
|
+
#
|
21
|
+
#debugger
|
22
|
+
#puts "done!"
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rest_connection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 19
|
5
|
+
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 1.0.
|
9
|
+
- 2
|
10
|
+
version: 1.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jeremy Deininger
|
@@ -16,7 +16,8 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2012-11-
|
19
|
+
date: 2012-11-29 00:00:00 -08:00
|
20
|
+
default_executable:
|
20
21
|
dependencies:
|
21
22
|
- !ruby/object:Gem::Dependency
|
22
23
|
name: activesupport
|
@@ -116,6 +117,7 @@ extensions: []
|
|
116
117
|
extra_rdoc_files:
|
117
118
|
- LICENSE
|
118
119
|
- README.rdoc
|
120
|
+
- README.rdoc.starting.point
|
119
121
|
files:
|
120
122
|
- LICENSE
|
121
123
|
- README.rdoc
|
@@ -208,6 +210,7 @@ files:
|
|
208
210
|
- lib/rest_connection/rightscale/vpc_dhcp_option.rb
|
209
211
|
- lib/rest_connection/ssh_hax.rb
|
210
212
|
- log_api_call_parser
|
213
|
+
- login_to_connection_irb.rb
|
211
214
|
- spec/ec2_server_array_spec.rb
|
212
215
|
- spec/ec2_ssh_key_internal_spec.rb
|
213
216
|
- spec/image_jockey.rb
|
@@ -222,6 +225,8 @@ files:
|
|
222
225
|
- spec/server_template_internal.rb
|
223
226
|
- spec/spec_helper.rb
|
224
227
|
- spec/tag_spec.rb
|
228
|
+
- README.rdoc.starting.point
|
229
|
+
has_rdoc: true
|
225
230
|
homepage: http://github.com/rightscale/rest_connection
|
226
231
|
licenses: []
|
227
232
|
|
@@ -251,7 +256,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
256
|
requirements: []
|
252
257
|
|
253
258
|
rubyforge_project:
|
254
|
-
rubygems_version: 1.
|
259
|
+
rubygems_version: 1.3.7
|
255
260
|
signing_key:
|
256
261
|
specification_version: 3
|
257
262
|
summary: Modular RESTful API library
|