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.
@@ -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
+ 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
- puts "************* McServer.launch(): Caught exception #{e.inspect}"
64
- puts "************* McServer.launch(): connection.settings[:azure_hack_on] = #{connection.settings[:azure_hack_on]}"
65
- puts "************* McServer.launch(): connection.settings[:azure_hack_retry_count] = #{connection.settings[:azure_hack_retry_count]}"
66
- puts "************* McServer.launch(): connection.settings[:azure_hack_sleep_seconds] = #{connection.settings[:azure_hack_sleep_seconds]}"
67
- # THIS IS A TEMPORARY HACK TO GET AROUND AZURE SERVER LAUNCH PROBLEMS AND SHOULD BE REMOVED ONCE MICROSOFT
68
- # FIXES THIS BUG ON THEIR END!
69
-
70
- # Retry on 422 conflict exception (ONLY MS AZURE WILL GENERATE THIS EXCEPTION)
71
- target_422_conflict_error_message = "Invalid response HTTP code: 422: CloudException: ConflictError:"
72
- target_504_gateway_timeout_error_message = "504 Gateway Time-out"
73
- if e.message =~ /#{target_504_gateway_timeout_error_message}/
74
- exception_matched_message = "McServer.launch(): Caught #{e.message}, treating as a successful launch..."
75
- puts(exception_matched_message)
76
- connection.logger(exception_matched_message)
77
- true
78
- elsif e.message =~ /#{target_422_conflict_error_message}/
79
- if connection.settings[:azure_hack_on]
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 = "************* McServer.launch(): Matched Azure exception: \"#{target_422_conflict_error_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 = "************* McServer.launch(): Sleeping for #{connection.settings[:azure_hack_sleep_seconds]} seconds and then retrying (#{retry_count}) launch..."
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
- if e2.message =~ /#{target_422_conflict_error_message}/
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 = 60 / step
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 terminated when waiting for #{st}: #{nickname}"
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: 21
5
- prerelease:
4
+ hash: 19
5
+ prerelease: false
6
6
  segments:
7
7
  - 1
8
8
  - 0
9
- - 1
10
- version: 1.0.1
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-20 00:00:00 Z
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.8.15
259
+ rubygems_version: 1.3.7
255
260
  signing_key:
256
261
  specification_version: 3
257
262
  summary: Modular RESTful API library