rest_connection 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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