oneview-sdk 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +2 -0
  3. data/.gitignore +29 -0
  4. data/.rubocop.yml +73 -0
  5. data/.travis.yml +8 -0
  6. data/CHANGELOG.md +39 -0
  7. data/Gemfile +2 -0
  8. data/LICENSE +201 -0
  9. data/README.md +317 -0
  10. data/Rakefile +90 -0
  11. data/bin/oneview-sdk-ruby +4 -0
  12. data/lib/oneview-sdk.rb +9 -0
  13. data/lib/oneview-sdk/cli.rb +407 -0
  14. data/lib/oneview-sdk/client.rb +163 -0
  15. data/lib/oneview-sdk/config_loader.rb +20 -0
  16. data/lib/oneview-sdk/resource.rb +313 -0
  17. data/lib/oneview-sdk/resource/enclosure.rb +169 -0
  18. data/lib/oneview-sdk/resource/enclosure_group.rb +98 -0
  19. data/lib/oneview-sdk/resource/ethernet_network.rb +60 -0
  20. data/lib/oneview-sdk/resource/fc_network.rb +31 -0
  21. data/lib/oneview-sdk/resource/fcoe_network.rb +25 -0
  22. data/lib/oneview-sdk/resource/firmware_bundle.rb +37 -0
  23. data/lib/oneview-sdk/resource/firmware_driver.rb +21 -0
  24. data/lib/oneview-sdk/resource/interconnect.rb +87 -0
  25. data/lib/oneview-sdk/resource/lig_uplink_set.rb +86 -0
  26. data/lib/oneview-sdk/resource/logical_enclosure.rb +84 -0
  27. data/lib/oneview-sdk/resource/logical_interconnect.rb +283 -0
  28. data/lib/oneview-sdk/resource/logical_interconnect_group.rb +92 -0
  29. data/lib/oneview-sdk/resource/server_hardware.rb +88 -0
  30. data/lib/oneview-sdk/resource/server_hardware_type.rb +27 -0
  31. data/lib/oneview-sdk/resource/server_profile.rb +37 -0
  32. data/lib/oneview-sdk/resource/server_profile_template.rb +24 -0
  33. data/lib/oneview-sdk/resource/storage_pool.rb +41 -0
  34. data/lib/oneview-sdk/resource/storage_system.rb +63 -0
  35. data/lib/oneview-sdk/resource/uplink_set.rb +119 -0
  36. data/lib/oneview-sdk/resource/volume.rb +188 -0
  37. data/lib/oneview-sdk/resource/volume_snapshot.rb +27 -0
  38. data/lib/oneview-sdk/resource/volume_template.rb +106 -0
  39. data/lib/oneview-sdk/rest.rb +163 -0
  40. data/lib/oneview-sdk/ssl_helper.rb +75 -0
  41. data/lib/oneview-sdk/version.rb +4 -0
  42. data/oneview-sdk.gemspec +31 -0
  43. metadata +204 -0
@@ -0,0 +1,163 @@
1
+ require 'logger'
2
+ require_relative 'config_loader'
3
+ require_relative 'rest'
4
+ require_relative 'ssl_helper'
5
+
6
+ module OneviewSDK
7
+ # The client defines the connection to the OneView server and handles communication with it.
8
+ class Client
9
+ DEFAULT_API_VERSION = 200
10
+
11
+ attr_reader :url, :user, :token, :password, :max_api_version
12
+ attr_accessor :ssl_enabled, :api_version, :logger, :log_level, :cert_store, :print_wait_dots
13
+
14
+ include Rest
15
+
16
+ # Create client object, establish connection, and set up logging and api version.
17
+ # @param [Hash] options the options to configure the client
18
+ # @option options [Logger] :logger (Logger.new(STDOUT)) Logger object to use.
19
+ # Must implement debug(String), info(String), warn(String), error(String), & level=
20
+ # @option options [Symbol] :log_level (:info) Log level. Logger must define a constant with this name. ie Logger::INFO
21
+ # @option options [Boolean] :print_wait_dots (false) When true, prints status dots while waiting on tasks to complete.
22
+ # @option options [String] :url URL of OneView appliance
23
+ # @option options [String] :user ('Administrator') Username to use for authentication with OneView appliance
24
+ # @option options [String] :password (ENV['ONEVIEWSDK_PASSWORD']) Password to use for authentication with OneView appliance
25
+ # @option options [String] :token (ENV['ONEVIEWSDK_TOKEN']) Token to use for authentication with OneView appliance
26
+ # Use this OR the username and password (not both). Token has precedence.
27
+ # @option options [Integer] :api_version (200) API Version to use by default for requests
28
+ # @option options [Boolean] :ssl_enabled (true) Use ssl for requests? Respects ENV['ONEVIEWSDK_SSL_ENABLED']
29
+ def initialize(options = {})
30
+ options = Hash[options.map { |k, v| [k.to_sym, v] }] # Convert string hash keys to symbols
31
+ @logger = options[:logger] || Logger.new(STDOUT)
32
+ [:debug, :info, :warn, :error, :level=].each { |m| fail "Logger must respond to #{m} method " unless @logger.respond_to?(m) }
33
+ @log_level = options[:log_level] || :info
34
+ @logger.level = @logger.class.const_get(@log_level.upcase) rescue @log_level
35
+ @print_wait_dots = options.fetch(:print_wait_dots, false)
36
+ @url = options[:url] || ENV['ONEVIEWSDK_URL']
37
+ fail 'Must set the url option' unless @url
38
+ @max_api_version = appliance_api_version
39
+ if options[:api_version] && options[:api_version].to_i > @max_api_version
40
+ logger.warn "API version #{options[:api_version]} is greater than the appliance API version (#{@max_api_version})"
41
+ end
42
+ @api_version = options[:api_version] || [DEFAULT_API_VERSION, @max_api_version].min
43
+ @ssl_enabled = true
44
+ if ENV.key?('ONEVIEWSDK_SSL_ENABLED')
45
+ if %w(true false 1 0).include?(ENV['ONEVIEWSDK_SSL_ENABLED'])
46
+ @ssl_enabled = ! %w(false 0).include?(ENV['ONEVIEWSDK_SSL_ENABLED'])
47
+ else
48
+ @logger.warn "Unrecognized ssl_enabled value '#{ENV['ONEVIEWSDK_SSL_ENABLED']}'. Valid options are 'true' & 'false'"
49
+ end
50
+ end
51
+ @ssl_enabled = options[:ssl_enabled] unless options[:ssl_enabled].nil?
52
+ @cert_store = OneviewSDK::SSLHelper.load_trusted_certs if @ssl_enabled
53
+ @token = options[:token] || ENV['ONEVIEWSDK_TOKEN']
54
+ return if @token
55
+ @logger.warn 'User option not set. Using default (Administrator)' unless options[:user] || ENV['ONEVIEWSDK_USER']
56
+ @user = options[:user] || ENV['ONEVIEWSDK_USER'] || 'Administrator'
57
+ @password = options[:password] || ENV['ONEVIEWSDK_PASSWORD']
58
+ fail 'Must set user & password options or token option' unless @password
59
+ @token = login
60
+ end
61
+
62
+ # Tell OneView to create the resource using the current attribute data
63
+ # @param [Resource] resource the object to create
64
+ def create(resource)
65
+ resource.client = self
66
+ resource.create
67
+ end
68
+
69
+ # Set attribute data and save to OneView
70
+ # @param [Resource] resource the object to update
71
+ def update(resource, attributes = {})
72
+ resource.client = self
73
+ resource.update(attributes)
74
+ end
75
+
76
+ # Updates this object using the data that exists on OneView
77
+ # @param [Resource] resource the object to refresh
78
+ def refresh(resource)
79
+ resource.client = self
80
+ resource.refresh
81
+ end
82
+
83
+ # Deletes this object from OneView
84
+ # @param [Resource] resource the object to delete
85
+ def delete(resource)
86
+ resource.client = self
87
+ resource.delete
88
+ end
89
+
90
+ # Get array of all resources of a specified type
91
+ # @param [String] type Resource type
92
+ # @return [Array<Resource>] Results
93
+ # @example Get all Ethernet Networks
94
+ # networks = @client.get_all('EthernetNetworks')
95
+ def get_all(type)
96
+ OneviewSDK.resource_named(type).get_all(self)
97
+ rescue StandardError
98
+ raise "Invalid resource type '#{type}'"
99
+ end
100
+
101
+ # Wait for a task to complete
102
+ # @param [String] task_uri
103
+ # @raise [RuntimeError] if the task resulted in an error or early termination.
104
+ # @return [Hash] if the task completed sucessfully, return the task details
105
+ def wait_for(task_uri)
106
+ fail 'Must specify a task_uri!' if task_uri.nil? || task_uri.empty?
107
+ loop do
108
+ task_uri.gsub!(%r{/https:(.*)\/rest/}, '/rest')
109
+ task = rest_get(task_uri)
110
+ body = JSON.parse(task.body)
111
+ case body['taskState'].downcase
112
+ when 'completed'
113
+ return body
114
+ when 'warning'
115
+ @logger.warn "Task ended with warning status. Details: #{JSON.pretty_generate(body['taskErrors']) rescue body}"
116
+ return body
117
+ when 'error', 'killed', 'terminated'
118
+ msg = "Task ended with bad state: '#{body['taskState']}'.\nResponse: "
119
+ msg += body['taskErrors'] ? JSON.pretty_generate(body['taskErrors']) : JSON.pretty_generate(body)
120
+ fail(msg)
121
+ else
122
+ print '.' if @print_wait_dots
123
+ sleep 10
124
+ end
125
+ end
126
+ end
127
+
128
+
129
+ private
130
+
131
+ # Get current api version from the OneView appliance
132
+ def appliance_api_version
133
+ options = { 'Content-Type' => :none, 'X-API-Version' => :none, 'auth' => :none }
134
+ response = rest_api(:get, '/rest/version', options)
135
+ version = response_handler(response)['currentVersion']
136
+ fail "Couldn't get API version" unless version
137
+ version = version.to_i if version.class != Fixnum
138
+ version
139
+ rescue
140
+ @logger.warn "Failed to get OneView max api version. Using default (#{DEFAULT_API_VERSION})"
141
+ DEFAULT_API_VERSION
142
+ end
143
+
144
+ # Log in to OneView appliance and set max_api_version
145
+ def login(retries = 2)
146
+ options = {
147
+ 'body' => {
148
+ 'userName' => @user,
149
+ 'password' => @password,
150
+ 'authLoginDomain' => 'LOCAL'
151
+ }
152
+ }
153
+ response = rest_post('/rest/login-sessions', options)
154
+ body = response_handler(response)
155
+ return body['sessionID'] if body['sessionID']
156
+ fail "\nERROR! Couldn't log into OneView server at #{@url}. Response: #{response}\n#{response.body}"
157
+ rescue StandardError => e
158
+ raise e unless retries > 0
159
+ @logger.debug 'Failed to log in to OneView. Retrying...'
160
+ return login(retries - 1)
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,20 @@
1
+ require 'yaml'
2
+ require 'json'
3
+
4
+ module OneviewSDK
5
+ # Configuration helper class to allow .yaml & .json files to be easily used to specify OneView Configuration
6
+ class Config
7
+
8
+ # Load config from .yaml or .json file
9
+ # @param [String] path The full path to the configuration file
10
+ # @return [Hash] hash of the configuration
11
+ def self.load(path)
12
+ path = File.join(Dir.pwd, path) unless Pathname.new(path).absolute?
13
+ expanded_path = File.expand_path(path)
14
+ JSON.parse(IO.read(expanded_path))
15
+ rescue
16
+ data = YAML.load_file(expanded_path)
17
+ Hash[data.map { |k, v| [k.to_s, v] }]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,313 @@
1
+ require_relative 'client'
2
+
3
+ # OneviewSDK Resources
4
+ module OneviewSDK
5
+ # Resource base class that defines all common resource functionality.
6
+ class Resource
7
+ BASE_URI = '/rest'.freeze
8
+
9
+ attr_accessor \
10
+ :client,
11
+ :data,
12
+ :api_version,
13
+ :logger
14
+
15
+ # Create a resource object, associate it with a client, and set its properties.
16
+ # @param [Client] client The Client object with a connection to the OneView appliance
17
+ # @param [Hash] params The options for this resource (key-value pairs)
18
+ # @param [Integer] api_ver The api version to use when interracting with this resource.
19
+ # Defaults to client.api_version if exists, or OneviewSDK::Client::DEFAULT_API_VERSION.
20
+ def initialize(client, params = {}, api_ver = nil)
21
+ fail 'Must specify a valid client' unless client.is_a?(OneviewSDK::Client)
22
+ @client = client
23
+ @logger = @client.logger
24
+ @api_version = api_ver || @client.api_version
25
+ if @api_version > @client.max_api_version
26
+ fail "#{self.class.name} api_version '#{@api_version}' is greater than the client's max_api_version '#{@client.max_api_version}'"
27
+ end
28
+ @data ||= {}
29
+ set_all(params)
30
+ end
31
+
32
+ # Retrieve resource details based on this resource's name or URI.
33
+ # @note Name or URI must be specified inside resource
34
+ # @return [Boolean] Whether or not retrieve was successful
35
+ def retrieve!
36
+ fail 'Must set resource name or uri before trying to retrieve!' unless @data['name'] || @data['uri']
37
+ results = self.class.find_by(@client, name: @data['name']) if @data['name']
38
+ results = self.class.find_by(@client, uri: @data['uri']) if @data['uri'] && (!results || results.empty?)
39
+ return false unless results.size == 1
40
+ set_all(results[0].data)
41
+ true
42
+ end
43
+
44
+ # Check if a resource exists
45
+ # @note name or uri must be specified inside resource
46
+ # @return [Boolean] Whether or not resource exists
47
+ def exists?
48
+ fail 'Must set resource name or uri before trying to retrieve!' unless @data['name'] || @data['uri']
49
+ return true if @data['name'] && self.class.find_by(@client, name: @data['name']).size == 1
50
+ return true if @data['uri'] && self.class.find_by(@client, uri: @data['uri']).size == 1
51
+ false
52
+ end
53
+
54
+ # Set the given hash of key-value pairs as resource data attributes
55
+ # @param [Hash, Resource] params The options for this resource (key-value pairs or Resource object)
56
+ # @note All top-level keys will be converted to strings
57
+ # @return [Resource] self
58
+ def set_all(params = {})
59
+ params = params.data if params.class <= Resource
60
+ params = Hash[params.map { |(k, v)| [k.to_s, v] }]
61
+ params.each { |key, value| set(key.to_s, value) }
62
+ self
63
+ end
64
+
65
+ # Set a resource attribute with the given value and call any validation method if necessary
66
+ # @param [String] key attribute name
67
+ # @param value value to assign to the given attribute
68
+ # @note Keys will be converted to strings
69
+ def set(key, value)
70
+ method_name = "validate_#{key}"
71
+ send(method_name.to_sym, value) if respond_to?(method_name.to_sym)
72
+ @data[key.to_s] = value
73
+ end
74
+
75
+ # Run block once for each data key-value pair
76
+ def each(&block)
77
+ @data.each(&block)
78
+ end
79
+
80
+ # Access data using hash syntax
81
+ # @param [String, Symbol] key Name of key to get value for
82
+ # @return The value of the given key. If not found, returns nil
83
+ # @note The key will be converted to a string
84
+ def [](key)
85
+ @data[key.to_s]
86
+ end
87
+
88
+ # Set data using hash syntax
89
+ # @param [String, Symbol] key Name of key to set the value for
90
+ # @param [Object] value to set for the given key
91
+ # @note The key will be converted to a string
92
+ # @return The value set for the given key
93
+ def []=(key, value)
94
+ set(key, value)
95
+ value
96
+ end
97
+
98
+ # Check equality of 2 resources. Same as eql?(other)
99
+ # @param [Resource] other The other resource to check equality for
100
+ # @return [Boolean] Whether or not the two objects are equal
101
+ def ==(other)
102
+ self_state = instance_variables.sort.map { |v| instance_variable_get(v) }
103
+ other_state = other.instance_variables.sort.map { |v| other.instance_variable_get(v) }
104
+ other.class == self.class && other_state == self_state
105
+ end
106
+
107
+ # Check equality of 2 resources. Same as ==(other)
108
+ # @param [Resource] other The other resource to check equality for
109
+ # @return [Boolean] Whether or not the two objects are equal
110
+ def eql?(other)
111
+ self == other
112
+ end
113
+
114
+ # Check equality of data on other resource with that of this resource.
115
+ # @note Doesn't check the client, logger, or api_version if another Resource is passed in
116
+ # @param [Hash, Resource] other Resource or hash to compare key-value pairs with
117
+ # @example Compare to hash
118
+ # myResource = OneviewSDK::Resource.new(client, { name: 'res1', description: 'example'}, 200)
119
+ # myResource.like?(description: '') # returns false
120
+ # myResource.like?(name: 'res1') # returns true
121
+ # @return [Boolean] Whether or not the two objects are alike
122
+ def like?(other)
123
+ recursive_like?(other, @data)
124
+ end
125
+
126
+ # Create the resource on OneView using the current data
127
+ # @note Calls the refresh method to set additional data
128
+ # @raise [RuntimeError] if the client is not set
129
+ # @raise [RuntimeError] if the resource creation fails
130
+ # @return [Resource] self
131
+ def create
132
+ ensure_client
133
+ response = @client.rest_post(self.class::BASE_URI, { 'body' => @data }, @api_version)
134
+ body = @client.response_handler(response)
135
+ set_all(body)
136
+ self
137
+ end
138
+
139
+ # Delete the resource from OneView if it exists, then create it using the current data
140
+ # @note Calls refresh method to set additional data
141
+ # @raise [RuntimeError] if the client is not set
142
+ # @raise [RuntimeError] if the resource creation fails
143
+ # @return [Resource] self
144
+ def create!
145
+ temp = self.class.new(@client, @data)
146
+ temp.delete if temp.retrieve!
147
+ create
148
+ end
149
+
150
+ # Updates this object using the data that exists on OneView
151
+ # @note Will overwrite any data that differs from OneView
152
+ # @return [Resource] self
153
+ def refresh
154
+ ensure_client && ensure_uri
155
+ response = @client.rest_get(@data['uri'], @api_version)
156
+ body = @client.response_handler(response)
157
+ set_all(body)
158
+ self
159
+ end
160
+
161
+ # Set data and save to OneView
162
+ # @param [Hash] attributes The attributes to add/change for this resource (key-value pairs)
163
+ # @raise [RuntimeError] if the client or uri is not set
164
+ # @raise [RuntimeError] if the resource save fails
165
+ # @return [Resource] self
166
+ def update(attributes = {})
167
+ set_all(attributes)
168
+ ensure_client && ensure_uri
169
+ response = @client.rest_put(@data['uri'], { 'body' => @data }, @api_version)
170
+ @client.response_handler(response)
171
+ self
172
+ end
173
+
174
+ # Delete resource from OneView
175
+ # @return [true] if resource was deleted successfully
176
+ def delete
177
+ ensure_client && ensure_uri
178
+ response = @client.rest_delete(@data['uri'], {}, @api_version)
179
+ @client.response_handler(response)
180
+ true
181
+ end
182
+
183
+ # Save resource to json or yaml file
184
+ # @param [String] file_path The full path to the file
185
+ # @param [Symbol] format The format. Options: [:json, :yml, :yaml]. Defaults to .json
186
+ # @note If a .yml or .yaml file extension is given in the file_path, the format will be set automatically
187
+ # @return [True] The Resource was saved successfully
188
+ def to_file(file_path, format = :json)
189
+ format = :yml if %w(.yml .yaml).include? File.extname(file_path)
190
+ temp_data = { type: self.class.name, api_version: @api_version, data: @data }
191
+ case format.to_sym
192
+ when :json
193
+ File.open(file_path, 'w') { |f| f.write(JSON.pretty_generate(temp_data)) }
194
+ when :yml, :yaml
195
+ File.open(file_path, 'w') { |f| f.write(temp_data.to_yaml) }
196
+ else
197
+ fail "Invalid format: #{format}"
198
+ end
199
+ true
200
+ end
201
+
202
+ # Get resource schema
203
+ # @note This may not be implemented in the API for every resource. Check the API docs
204
+ # @return [Hash] Schema
205
+ def schema
206
+ self.class.schema(@client)
207
+ end
208
+
209
+ # Get resource schema
210
+ # @param [Client] client
211
+ # @return [Hash] Schema
212
+ def self.schema(client)
213
+ response = client.rest_get("#{self::BASE_URI}/schema", client.api_version)
214
+ client.response_handler(response)
215
+ rescue StandardError => e
216
+ client.logger.error('This resource does not implement the schema endpoint!') if e.message =~ /404 NOT FOUND/
217
+ raise e
218
+ end
219
+
220
+ # Load resource from a .json or .yaml file
221
+ # @param [Client] client The client object to associate this resource with
222
+ # @param [String] file_path The full path to the file
223
+ # @return [Resource] New resource created from the file contents
224
+ def self.from_file(client, file_path)
225
+ resource = OneviewSDK::Config.load(file_path)
226
+ new(client, resource['data'], resource['api_version'])
227
+ end
228
+
229
+ # Make a GET request to the resource uri and return an array with results matching the search
230
+ # @param [Client] client
231
+ # @param [Hash] attributes Hash containing the attributes name and value
232
+ # @param [String] uri URI of the endpoint
233
+ # @return [Array<Resource>] Results matching the search
234
+ def self.find_by(client, attributes, uri = self::BASE_URI)
235
+ results = []
236
+ loop do
237
+ response = client.rest_get(uri)
238
+ body = client.response_handler(response)
239
+ members = body['members']
240
+ break unless members
241
+ members.each do |member|
242
+ temp = new(client, member)
243
+ results.push(temp) if temp.like?(attributes)
244
+ end
245
+ break unless body['nextPageUri']
246
+ uri = body['nextPageUri']
247
+ end
248
+ results
249
+ end
250
+
251
+ # Make a GET request to the resource base uri and return an array with all objects of this type
252
+ # @return [Array<Resource>] Results
253
+ def self.get_all(client)
254
+ find_by(client, {})
255
+ end
256
+
257
+ protected
258
+
259
+ # Fail unless @client is set for this resource.
260
+ def ensure_client
261
+ fail 'Please set client attribute before interacting with this resource' unless @client
262
+ true
263
+ end
264
+
265
+ # Fail unless @data['uri'] is set for this resource.
266
+ def ensure_uri
267
+ fail 'Please set uri attribute before interacting with this resource' unless @data['uri']
268
+ true
269
+ end
270
+
271
+ # Fail for methods that are not available for one resource
272
+ def unavailable_method
273
+ fail "The method ##{caller[0][/`.*'/][1..-2]} is unavailable for this resource"
274
+ end
275
+
276
+ private
277
+
278
+ # Recursive helper method for like?
279
+ # Allows comparison of nested hash structures
280
+ def recursive_like?(other, data = @data)
281
+ fail "Can't compare with object type: #{other.class}! Must respond_to :each" unless other.respond_to?(:each)
282
+ other.each do |key, val|
283
+ return false unless data && data.respond_to?(:[])
284
+ if val.is_a?(Hash)
285
+ return false unless data.class == Hash && recursive_like?(val, data[key.to_s])
286
+ elsif val != data[key.to_s]
287
+ return false
288
+ end
289
+ end
290
+ true
291
+ end
292
+
293
+ end
294
+
295
+ # Get resource class that matches the type given
296
+ # @param [String] type Name of the desired class type
297
+ # @return [Class] Resource class or nil if not found
298
+ def self.resource_named(type)
299
+ classes = {}
300
+ orig_classes = []
301
+ ObjectSpace.each_object(Class).select { |klass| klass < OneviewSDK::Resource }.each do |c|
302
+ name = c.name.split('::').last
303
+ orig_classes.push(name)
304
+ classes[name.downcase.delete('_').delete('-')] = c
305
+ classes["#{name.downcase.delete('_').delete('-')}s"] = c
306
+ end
307
+ new_type = type.to_s.downcase.gsub(/[ -_]/, '')
308
+ return classes[new_type] if classes.keys.include?(new_type)
309
+ end
310
+ end
311
+
312
+ # Load all resources:
313
+ Dir[File.dirname(__FILE__) + '/resource/*.rb'].each { |file| require file }