oneview-sdk 1.0.0

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.
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 }