chef-provisioning-oneview 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b9d6fc4eeb70e68a685026500dd2f1b740bc2258
4
+ data.tar.gz: a6b1a761bbfda4d5a7df5254992eb7da07f7bc0a
5
+ SHA512:
6
+ metadata.gz: 1998337d50d17e87c542b76cf73974f2caf0403e12e5535bb4cb648edbb846bcece9fff6017a48b527771477c81d2f12b4ad3be67d7b544c3e0a9263cd20b47f
7
+ data.tar.gz: 92a234a482898954435ce47c131a62ba8d71ad0c7b1f9c3514e6ccdd4d0ce6f0de59af4c0e2ccfd951b9167b2197aa699a53e2c8797fd329b341365a7ada8aa9
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ © Copyright 2015 Hewlett Packard Enterprise Development LP
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
4
+ You may obtain a copy of the License at
5
+
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+
8
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+ See the License for the specific language governing permissions and limitations under the License.
data/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # chef-provisioning-oneview
2
+ Chef Provisioning driver for HP OneView
3
+
4
+ Currently supports OneView v1.2.0 and ICsp v7.4.0
5
+
6
+ # Installation
7
+
8
+ - Require the gem in your Gemfile: `gem 'chef-provisioning-oneview'`
9
+
10
+ Then run `$ bundle install`
11
+ - Or run the command:
12
+
13
+ ```ruby
14
+ $ gem install chef-provisioning-oneview
15
+ ```
16
+
17
+
18
+
19
+ # Prerequisites
20
+ - Set up your `knife.rb` file with the information the driver needs to connect to OneView and Insight Control Server Provisioning
21
+
22
+ ```ruby
23
+ # (knife.rb)
24
+ # (in addition to all the normal stuff like node_name, client_key, validation_client_name, validation_key, chef_server_url, etc.)
25
+ knife[:oneview_url] = 'https://my-oneview.my-domain.com'
26
+ knife[:oneview_username] = 'Administrator'
27
+ knife[:oneview_password] = 'password123'
28
+ knife[:oneview_ignore_ssl] = true # For self-signed certs
29
+
30
+ knife[:icsp_url] = 'https://my-icsp.my-domain.com'
31
+ knife[:icsp_username] = 'Administrator'
32
+ knife[:icsp_password] = 'password123'
33
+ knife[:icsp_ignore_ssl] = true # For self-signed certs
34
+
35
+ knife[:node_root_password] = 'password123'
36
+
37
+ # If your Chef server has self-signed certs:
38
+ verify_api_cert false
39
+ ssl_verify_mode :verify_none
40
+ ```
41
+
42
+ - Your OneView, Insight Controll Server Provisioning(ICSP), and Chef server must be trusted by your certificate stores. See [examples/ssl_issues.md](examples/ssl_issues.md) for more info on how to do this.
43
+ - Your OneView and ICSP servers must be set up beforehand. Unfortunately, this driver doesn't do that for you too. See the wiki pages [OneView Configuration](https://github.com/HewlettPackard/chef-provisioning-oneview/wiki/OneView-Configuration) and [ICsp Configuration](https://github.com/HewlettPackard/chef-provisioning-oneview/wiki/ICsp-Configuration) for details about how to set them up.
44
+
45
+ # Usage
46
+
47
+ Example recipe:
48
+ ```ruby
49
+ require 'chef/provisioning'
50
+
51
+ with_driver 'oneview'
52
+
53
+ with_chef_server "https://my-chef.my-domain.com/organizations/my-org",
54
+ :client_name => Chef::Config[:node_name], # NOTE: This must have node & client creation privileges (ie admin group)
55
+ :signing_key_filename => Chef::Config[:client_key] # NOTE: This must have node & client creation privileges (ie admin group)
56
+
57
+ machine 'web01' do
58
+ recipe 'my_server_cookbook::default'
59
+
60
+ machine_options :driver_options => {
61
+ :server_template => 'Web Server Template',
62
+ :os_build => 'CHEF-RHEL-6.5-x64',
63
+ :host_name => 'chef-web01',
64
+ :ip_address => 'xx.xx.xx.xx', # For bootstrapping only.
65
+
66
+ :domainType => 'workgroup',
67
+ :domainName => 'sub.domain.com',
68
+ :mask => '255.255.255.0', # Can set here or in individual connections below
69
+ :dhcp => false,
70
+ :gateway => 'xx.xx.xx.1',
71
+ :dns => 'xx.xx.xx.xx,xx.xx.xx.xx,xx.xx.xx.xx',
72
+ :connections => {
73
+ #1 => { ... } (Reserved for PXE on our setup)
74
+ 2 => {
75
+ :ip4Address => 'xx.xx.xx.xx',
76
+ :mask => '255.255.254.0', # Optional. Overrides mask property above
77
+ :dhcp => false # Optional. Overrides dhcp property above
78
+ :gateway => 'xx.xx.xx.1' # Optional. Overrides gateway property above
79
+ :dns => 'xx.xx.xx.xx' # Optional. Overrides dns property above
80
+ }
81
+ },
82
+ :custom_attributes => {
83
+ :chefCert => 'ssh-rsa AA...' # Optional
84
+ }
85
+ },
86
+ :transport_options => {
87
+ :user => 'root', # Optional. Defaults to 'root'
88
+ :ssh_options => {
89
+ :password => Chef::Config.knife[:node_root_password]
90
+ }
91
+ },
92
+ :convergence_options => {
93
+ :ssl_verify_mode => :verify_none, # Optional. For Chef servers with self-signed certs
94
+ :bootstrap_proxy => 'http://proxy.domain.com:8080' # Optional
95
+ }
96
+
97
+ chef_environment '_default'
98
+ converge true
99
+ end
100
+ ```
101
+
102
+ See https://github.com/chef/chef-provisioning-ssh for more transport_options.
103
+
104
+ ### Custom Attributes
105
+ Insided the custom attributes hash, you can specify any data that you would like to pass into your ICsp build plan scripts or configuration files. For example, to specify a list of trusted public keys to be placed into the node's .ssh/authorized_keys file, add a custom attribute to the machine resource definition:
106
+
107
+ ```ruby
108
+ :custom_attributes => {
109
+ :chefCert => 'ssh-rsa AA...'
110
+ }
111
+ ```
112
+
113
+ Then create/modify a custom build script in ICsp that will do something with this data. To access it, use the format: `@variable_name@` or `@variable_name:default_value@`. For our example, we could do something like:
114
+
115
+ ```bash
116
+ #!/bin/bash
117
+ authorized_keys = @chefCert@
118
+ if [ -n "$authorized_keys"]; then
119
+ echo -e "$authorized_keys" > /mnt/sysimage/root/.ssh/authorized_keys
120
+ fi
121
+ ```
122
+
123
+ ### Behind a proxy
124
+ Add `:bootstrap_proxy => 'http://proxy.domain.com:8080'` to your convergence_options hash.
125
+ Also, make sure your OS build plans set up the proxy configuration in a post OS install script.
126
+
127
+
128
+ # Doing a test run
129
+ This repo contains everything you need to get started, including example recipes and knife configuration files. See the README in the [examples](examples/) directory for how to begin provisioning.
130
+
131
+
132
+ # Contributing
133
+ You know the drill. Fork it, branch it, change it, commit it, pull-request it. We're passionate about improving this driver, and glad to accept help to make it better.
134
+
135
+ ### Building the Gem
136
+ To build this gem, run `$ rake build` or `gem build chef-provisioning-oneview.gemspec`.
137
+
138
+ Then once it's built you can install it by running `$ rake install` or `$ gem install ./chef-provisioning-oneview-<VERSION>.gem`.
139
+
140
+ ### Testing
141
+ - RuboCop: `$ rake rubocop` or `$ rubocop .`
142
+ - Rspec: `$ rake spec` or `$ rspec`
143
+ - Both: Run `$ rake test` to run both RuboCop and Rspec tests.
144
+
145
+ # Authors
146
+ - Jared Smartt - [@jsmartt](https://github.com/jsmartt)
147
+ - Gunjan Kamle - [@kgunjan](https://github.com/kgunjan)
148
+ - Matthew Frahry - [@mbfrahry](https://github.com/mbfrahry)
149
+ - Andy Claiborne - [@veloandy](https://github.com/veloandy)
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+ require 'rubocop/rake_task'
5
+
6
+ task default: :spec
7
+
8
+ desc 'Run specs'
9
+ RSpec::Core::RakeTask.new(:spec) do |spec|
10
+ spec.pattern = 'spec/**/*_spec.rb'
11
+ end
12
+
13
+ RuboCop::RakeTask.new
14
+
15
+ desc 'Runs rubocop and rspec'
16
+ task :test do
17
+ Rake::Task[:rubocop].invoke
18
+ Rake::Task[:spec].invoke
19
+ end
@@ -0,0 +1,3 @@
1
+ require 'chef/provisioning/oneview_driver'
2
+
3
+ Chef::Provisioning.register_driver_class('oneview', Chef::Provisioning::OneViewDriver)
@@ -0,0 +1,467 @@
1
+ require_relative 'v1.20/api'
2
+ require_relative 'v2.0/api'
3
+
4
+ module OneViewAPI
5
+ private
6
+
7
+ include OneViewAPIv1_20
8
+ include OneViewAPIv2_0
9
+
10
+ # API calls for OneView and ICSP
11
+ def rest_api(host, type, path, options = {})
12
+ disable_ssl = false
13
+ case host
14
+ when 'icsp', :icsp
15
+ uri = URI.parse(URI.escape(@icsp_base_url + path))
16
+ options['X-API-Version'] ||= @icsp_api_version unless [:put, 'put'].include?(type.downcase)
17
+ options['auth'] ||= @icsp_key
18
+ disable_ssl = true if @icsp_disable_ssl
19
+ when 'oneview', :oneview
20
+ uri = URI.parse(URI.escape(@oneview_base_url + path))
21
+ options['X-API-Version'] ||= @oneview_api_version
22
+ options['auth'] ||= @oneview_key
23
+ disable_ssl = true if @oneview_disable_ssl
24
+ else
25
+ fail "Invalid rest host: #{host}"
26
+ end
27
+
28
+ http = Net::HTTP.new(uri.host, uri.port)
29
+ http.use_ssl = true if uri.scheme == 'https'
30
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if disable_ssl
31
+
32
+ case type.downcase
33
+ when 'get', :get
34
+ request = Net::HTTP::Get.new(uri.request_uri)
35
+ when 'post', :post
36
+ request = Net::HTTP::Post.new(uri.request_uri)
37
+ when 'put', :put
38
+ request = Net::HTTP::Put.new(uri.request_uri)
39
+ when 'delete', :delete
40
+ request = Net::HTTP::Delete.new(uri.request_uri)
41
+ else
42
+ fail "Invalid rest call: #{type}"
43
+ end
44
+ options['Content-Type'] ||= 'application/json'
45
+ options.delete('Content-Type') if [:none, 'none', nil].include?(options['Content-Type'])
46
+ options.delete('X-API-Version') if [:none, 'none', nil].include?(options['X-API-Version'])
47
+ options.delete('auth') if [:none, 'none', nil].include?(options['auth'])
48
+ options.each do |key, val|
49
+ if key.downcase == 'body'
50
+ request.body = val.to_json rescue val
51
+ else
52
+ request[key] = val
53
+ end
54
+ end
55
+
56
+ response = http.request(request)
57
+ JSON.parse(response.body) rescue response
58
+ end
59
+
60
+ def get_oneview_api_version
61
+ begin
62
+ version = rest_api(:oneview, :get, '/rest/version', { 'Content-Type' => :none, 'X-API-Version' => :none, 'auth' => :none })['currentVersion']
63
+ fail "Couldn't get API version" unless version
64
+ if version.class != Fixnum
65
+ version = version.to_i
66
+ fail 'API version type mismatch' if !version > 0
67
+ end
68
+ rescue
69
+ puts 'Failed to get OneView API version. Setting to default (120)'
70
+ version = 120
71
+ end
72
+ version
73
+ end
74
+
75
+ def get_icsp_api_version
76
+ begin
77
+ version = rest_api(:icsp, :get, '/rest/version', { 'Content-Type' => :none, 'X-API-Version' => :none, 'auth' => :none })['currentVersion']
78
+ fail "Couldn't get API version" unless version
79
+ if version.class != Fixnum
80
+ version = version.to_i
81
+ fail 'API version type mismatch' if !version > 0
82
+ end
83
+ rescue
84
+ puts 'Failed to get ICSP API version. Setting to default (102)'
85
+ version = 102
86
+ end
87
+ version
88
+ end
89
+
90
+ # Login functions
91
+ def auth_tokens
92
+ @icsp_key ||= login_to_icsp
93
+ @oneview_key ||= login_to_oneview
94
+ { 'icsp_key' => @icsp_key, 'oneview_key' => @oneview_key }
95
+ end
96
+
97
+ def login_to_icsp
98
+ path = '/rest/login-sessions'
99
+ options = {
100
+ 'body' => {
101
+ 'userName' => @icsp_username,
102
+ 'password' => @icsp_password,
103
+ 'authLoginDomain' => 'LOCAL'
104
+ }
105
+ }
106
+ response = rest_api(:icsp, :post, path, options)
107
+ return response['sessionID'] if response['sessionID']
108
+ fail("\nERROR! Couldn't log into OneView server at #{@oneview_base_url}. Response:\n#{response}")
109
+ end
110
+
111
+ def login_to_oneview
112
+ path = '/rest/login-sessions'
113
+ options = {
114
+ 'body' => {
115
+ 'userName' => @oneview_username,
116
+ 'password' => @oneview_password,
117
+ 'authLoginDomain' => 'LOCAL'
118
+ }
119
+ }
120
+ response = rest_api(:oneview, :post, path, options)
121
+ return response['sessionID'] if response['sessionID']
122
+ fail("\nERROR! Couldn't log into OneView server at #{@oneview_base_url}. Response:\n#{response}")
123
+ end
124
+
125
+
126
+ def get_oneview_profile_by_sn(serialNumber)
127
+ matching_profiles = rest_api(:oneview, :get, "/rest/server-profiles?filter=serialNumber matches '#{serialNumber}'&sort=name:asc")
128
+ return matching_profiles['members'].first if matching_profiles['count'] > 0
129
+ nil
130
+ end
131
+
132
+ def get_icsp_server_by_sn(serialNumber)
133
+ search_result = rest_api(:icsp, :get,
134
+ "/rest/index/resources?category=osdserver&query='osdServerSerialNumber:\"#{serial_number}\"'")['members'] rescue nil
135
+ if search_result && search_result.size == 1 && search_result.first['attributes']['osdServerSerialNumber'] == serial_number
136
+ my_server = search_result.first
137
+ end
138
+ unless my_server && my_server['uri']
139
+ os_deployment_servers = rest_api(:icsp, :get, '/rest/os-deployment-servers')
140
+ # Pick the relevant os deployment server from icsp
141
+ my_server = nil
142
+ os_deployment_servers['members'].each do |server|
143
+ if server['serialNumber'] == serialNumber
144
+ my_server = server
145
+ break
146
+ end
147
+ end
148
+ end
149
+ my_server
150
+ end
151
+
152
+
153
+ def power_on(action_handler, machine_spec, hardware_uri = nil)
154
+ set_power_state(action_handler, machine_spec, 'on', hardware_uri)
155
+ end
156
+
157
+ def power_off(action_handler, machine_spec, hardware_uri = nil)
158
+ set_power_state(action_handler, machine_spec, 'off', hardware_uri)
159
+ end
160
+
161
+ def set_power_state(action_handler, machine_spec, state, hardware_uri = nil)
162
+ case state
163
+ when :on, 'on', true
164
+ state = 'on'
165
+ when :off, 'off', false
166
+ state = 'off'
167
+ else fail "Invalid power state #{state}"
168
+ end
169
+
170
+ if hardware_uri.nil?
171
+ profile = get_oneview_profile_by_sn(machine_spec.reference['serial_number'])
172
+ hardware_uri = profile['serverHardwareUri']
173
+ end
174
+
175
+ hardware_info = rest_api(:oneview, :get, hardware_uri)
176
+ unless hardware_info['powerState'].downcase == state
177
+ action_handler.perform_action "Power #{state} server #{hardware_info['name']} for #{machine_spec.name}" do
178
+ action_handler.report_progress "INFO: Powering #{state} server #{hardware_info['name']} for #{machine_spec.name}"
179
+ task = rest_api(:oneview, :put, "#{hardware_uri}/powerState", { 'body' => { 'powerState' => state.capitalize, 'powerControl' => 'MomentaryPress' } })
180
+ task_uri = task['uri']
181
+ 60.times do # Wait for up to 10 minutes
182
+ task = rest_api(:oneview, :get, task_uri)
183
+ break if task['taskState'].downcase == 'completed'
184
+ print '.'
185
+ sleep 10
186
+ end
187
+ fail "Powering #{state} machine #{machine_spec.name} failed!" unless task['taskState'].downcase == 'completed'
188
+ end
189
+ end
190
+ hardware_uri
191
+ end
192
+
193
+ # Chef oneview provisioning
194
+ def create_machine(action_handler, machine_spec, machine_options)
195
+ host_name = machine_options[:driver_options][:host_name]
196
+ server_template = machine_options[:driver_options][:server_template]
197
+
198
+ auth_tokens # Login (to both ICSP and OneView)
199
+
200
+ # Check if profile exists first
201
+ matching_profiles = rest_api(:oneview, :get, "/rest/server-profiles?filter=name matches '#{host_name}'&sort=name:asc")
202
+
203
+ if matching_profiles['count'] > 0
204
+ profile = matching_profiles['members'].first
205
+ power_on(action_handler, machine_spec, profile['serverHardwareUri']) # Make sure server is started
206
+ return profile
207
+ end
208
+
209
+
210
+ # Get HPOVProfile by name (to see if it already exists)
211
+ # For 120 verion of Oneview , we are going to retrive a predefined unassociated server profile
212
+ templates = rest_api(:oneview, :get, "/rest/server-profiles?filter=name matches '#{server_template}'&sort=name:asc")
213
+ unless templates['members'] && templates['members'].count > 0
214
+ fail "Template '#{server_template}' not found! Please match the template name with one that exists on OneView."
215
+ end
216
+
217
+ template_uri = templates['members'].first['uri']
218
+ server_hardware_type_uri = templates['members'].first['serverHardwareTypeUri']
219
+ enclosure_group_uri = templates['members'].first['enclosureGroupUri']
220
+
221
+ # Get availabe (and compatible) HP OV server blades. Take first one.
222
+ blades = rest_api(:oneview, :get, "/rest/server-hardware?sort=name:asc&filter=serverHardwareTypeUri='#{server_hardware_type_uri}'&filter=serverGroupUri='#{enclosure_group_uri}'")
223
+ fail 'Error! No available blades that are compatible with the server profile!' unless blades['count'] > 0
224
+ chosen_blade = nil
225
+ blades['members'].each do |member|
226
+ if member['state'] != 'ProfileApplied' && member['state'] != 'ApplyingProfile'
227
+ chosen_blade = member
228
+ break
229
+ end
230
+ end
231
+ if chosen_blade.nil? # TODO
232
+ # Every bay is full and no more machines can be allocated
233
+ fail 'No more blades are available for provisioning!'
234
+ end
235
+
236
+ power_off(action_handler, machine_spec, chosen_blade['uri'])
237
+ # New-HPOVProfileFromTemplate
238
+ # Create new profile instance from template
239
+ action_handler.perform_action "Initialize creation of server template for #{machine_spec.name}" do
240
+ action_handler.report_progress "INFO: Initializing creation of server template for #{machine_spec.name}"
241
+
242
+ new_template_profile = rest_api(:oneview, :get, "#{template_uri}")
243
+
244
+ # Take response, add name & hardware uri, and post back to /rest/server-profiles
245
+ new_template_profile['name'] = host_name
246
+ new_template_profile['uri'] = nil
247
+ new_template_profile['serialNumber'] = nil
248
+ new_template_profile['uuid'] = nil
249
+ new_template_profile['connections'].each do |c|
250
+ c['wwnn'] = nil
251
+ c['wwpn'] = nil
252
+ c['mac'] = nil
253
+ end
254
+
255
+ new_template_profile['serverHardwareUri'] = chosen_blade['uri']
256
+ task = rest_api(:oneview, :post, '/rest/server-profiles', { 'body' => new_template_profile })
257
+ task_uri = task['uri']
258
+ # Poll task resource to see when profile has finished being applied
259
+ 60.times do # Wait for up to 5 min
260
+ matching_profiles = rest_api(:oneview, :get, "/rest/server-profiles?filter=name matches '#{host_name}'&sort=name:asc")
261
+ break if matching_profiles['count'] > 0
262
+ print '.'
263
+ sleep 5
264
+ end
265
+ unless matching_profiles['count'] > 0
266
+ task = rest_api(:oneview, :get, task_uri)
267
+ fail "Server template coudln't be applied! #{task['taskStatus']}. #{task['taskErrors'].first['message']}"
268
+ end
269
+ end
270
+ matching_profiles['members'].first
271
+ end
272
+
273
+
274
+ # Use ICSP to install OS
275
+ def customize_machine(action_handler, machine_spec, machine_options, profile)
276
+ auth_tokens # Login (to both ICSP and OneView)
277
+
278
+ # Wait for server profile to finish building
279
+ unless profile['state'] == 'Normal'
280
+ action_handler.perform_action "Wait for #{machine_spec.name} server to start and profile to be applied" do
281
+ action_handler.report_progress "INFO: Waiting for #{machine_spec.name} server to start and profile to be applied"
282
+ task_uri = profile['taskUri']
283
+ build_server_template_task = rest_api(:oneview, :get, task_uri)
284
+ # Poll task resource to see when profile has finished being applied
285
+ 240.times do # Wait for up to 40 min
286
+ build_server_template_task = rest_api(:oneview, :get, task_uri)
287
+ break if build_server_template_task['taskState'].downcase == 'completed'
288
+ if build_server_template_task['taskState'].downcase == 'error'
289
+ server_template = machine_options[:driver_options][:server_template]
290
+ fail "Error creating server profile from template #{server_template}: #{build_server_template_task['taskErrors'].first['message']}"
291
+ end
292
+ print '.'
293
+ sleep 10
294
+ end
295
+ fail 'Timed out waiting for server to start and profile to be applied' unless build_server_template_task['taskState'].downcase == 'completed'
296
+ end
297
+ profile = get_oneview_profile_by_sn(machine_spec.reference['serial_number']) # Refresh profile
298
+ fail "Server profile state '#{profile['state']}' not 'Normal'" unless profile['state'] == 'Normal'
299
+ end
300
+
301
+ # Make sure server is started
302
+ power_on(action_handler, machine_spec, profile['serverHardwareUri'])
303
+
304
+ # Get ICSP servers to poll and wait until server PXE complete (to make sure ICSP is available).
305
+ my_server = nil
306
+ action_handler.perform_action "Wait for #{machine_spec.name} to boot" do
307
+ action_handler.report_progress "INFO: Waiting for #{machine_spec.name} to PXE boot. This may take a while..."
308
+ 360.times do # Wait for up to 1 hr
309
+ os_deployment_servers = rest_api(:icsp, :get, '/rest/os-deployment-servers')
310
+
311
+ # TODO: Maybe check for opswLifecycle = 'UNPROVISIONED' instead of serialNumber existance
312
+ os_deployment_servers['members'].each do |server|
313
+ if server['serialNumber'] == profile['serialNumber']
314
+ my_server = server
315
+ break
316
+ end
317
+ end
318
+ break if !my_server.nil?
319
+ print '.'
320
+ sleep 10
321
+ end
322
+ fail "Timeout waiting for server #{machine_spec.name} to register with ICSP" if my_server.nil?
323
+ end
324
+
325
+ # Consume any custom attributes that were specified
326
+ if machine_options[:driver_options][:custom_attributes]
327
+ curr_server = rest_api(:icsp, :get, my_server['uri'])
328
+ machine_options[:driver_options][:custom_attributes].each do |key, val|
329
+ curr_server['customAttributes'].push({
330
+ 'values' => [{ 'scope' => 'server', 'value' => val.to_s }],
331
+ 'key' => key.to_s
332
+ })
333
+ end
334
+ options = { 'body' => curr_server }
335
+ rest_api(:icsp, :put, my_server['uri'], options)
336
+ end
337
+
338
+ # Run OS install on a server
339
+ unless my_server['opswLifecycle'] == 'MANAGED' # Skip if already in MANAGED state
340
+ os_build = machine_options[:driver_options][:os_build]
341
+ action_handler.perform_action "Install OS: #{os_build} on #{machine_spec.name}" do
342
+ action_handler.report_progress "INFO: Installing OS: #{os_build} on #{machine_spec.name}"
343
+ # Get os-deployment-build-plans
344
+ build_plan_uri = nil
345
+ os_deployment_build_plans = rest_api(:icsp, :get, '/rest/os-deployment-build-plans')
346
+ os_deployment_build_plans['members'].each do |bp|
347
+ if bp['name'] == os_build
348
+ build_plan_uri = bp['uri']
349
+ break
350
+ end
351
+ end
352
+ fail "OS build plan #{os_build} not found!" if build_plan_uri.nil?
353
+
354
+ # Do the OS deployment
355
+ options = { 'body' => {
356
+ 'osbpUris' => [build_plan_uri],
357
+ 'serverData' => [{ 'serverUri' => my_server['uri'] }]
358
+ } }
359
+ os_deployment_task = rest_api(:icsp, :post, '/rest/os-deployment-jobs/?force=true', options)
360
+ os_deployment_task_uri = os_deployment_task['uri']
361
+ 720.times do # Wait for up to 2 hr
362
+ os_deployment_task = rest_api(:icsp, :get, os_deployment_task_uri, options) # TODO: Need options?
363
+ break if os_deployment_task['running'] == 'false'
364
+ print '.'
365
+ sleep 10
366
+ end
367
+ unless os_deployment_task['state'] == 'STATUS_SUCCESS'
368
+ fail "Error running OS build plan #{os_build}: #{os_deployment_task['jobResult'].first['jobMessage']}\n#{os_deployment_task['jobResult'].first['jobResultErrorDetails']}"
369
+ end
370
+ end
371
+ end
372
+
373
+ # Perform network personalization
374
+ action_handler.perform_action "Perform network personalization on #{machine_spec.name}" do
375
+ action_handler.report_progress "INFO: Performing network personalization on #{machine_spec.name}"
376
+ nics = []
377
+ if machine_options[:driver_options][:connections]
378
+ machine_options[:driver_options][:connections].each do |id, data|
379
+ c = data
380
+ c[:macAddress] = profile['connections'].select {|x| x['id'] == id}.first['mac']
381
+ c[:mask] ||= machine_options[:driver_options][:mask]
382
+ c[:dhcp] ||= machine_options[:driver_options][:dhcp] || false
383
+ c[:gateway] ||= machine_options[:driver_options][:gateway]
384
+ c[:dns] ||= machine_options[:driver_options][:dns]
385
+ c[:ip4Address] ||= machine_options[:driver_options][:ip_address]
386
+ nics.push c
387
+ end
388
+ end
389
+ options = { 'body' => [{
390
+ 'serverUri' => my_server['uri'],
391
+ 'personalityData' => {
392
+ 'hostName' => machine_options[:driver_options][:host_name],
393
+ 'domainType' => machine_options[:driver_options][:domainType],
394
+ 'domainName' => machine_options[:driver_options][:domainName],
395
+ 'nics' => nics
396
+ }
397
+ }] }
398
+ network_personalization_task = rest_api(:icsp, :put, '/rest/os-deployment-apxs/personalizeserver', options)
399
+ network_personalization_task_uri = network_personalization_task['uri']
400
+ 60.times do # Wait for up to 10 min
401
+ network_personalization_task = rest_api(:icsp, :get, network_personalization_task_uri, options)
402
+ break if network_personalization_task['running'] == 'false'
403
+ print '.'
404
+ sleep 10
405
+ end
406
+ unless network_personalization_task['state'] == 'STATUS_SUCCESS'
407
+ fail "Error performing network personalization: #{network_personalization_task['jobResult'].first['jobResultLogDetails']}\n#{network_personalization_task['jobResult'].first['jobResultErrorDetails']}"
408
+ end
409
+ end
410
+ # Get all, search for yours. If not there or if it's in uninitialized state, pull again
411
+ my_server_uri = my_server['uri']
412
+ 30.times do # Wait for up to 5 min
413
+ my_server = rest_api(:icsp, :get, my_server_uri)
414
+ break if my_server['opswLifecycle'] == 'MANAGED'
415
+ print '.'
416
+ sleep 10
417
+ end
418
+
419
+ fail "Timeout waiting for server #{machine_spec.name} to finish network personalization" if my_server['opswLifecycle'] != 'MANAGED'
420
+ my_server
421
+ end
422
+
423
+
424
+ def destroy_icsp_server(action_handler, machine_spec)
425
+ my_server = get_icsp_server_by_sn(machine_spec.reference['serial_number'])
426
+ return false if my_server.nil? || my_server['uri'].nil?
427
+
428
+ action_handler.perform_action "Delete server #{machine_spec.name} from ICSP" do
429
+ task = rest_api(:icsp, :delete, my_server['uri']) # TODO: This returns nil instead of task info
430
+
431
+ if task['uri']
432
+ task_uri = task['uri']
433
+ 90.times do # Wait for up to 15 minutes
434
+ task = rest_api(:icsp, :get, task_uri)
435
+ break if task['taskState'].downcase == 'completed'
436
+ print '.'
437
+ sleep 10
438
+ end
439
+ fail "Deleting os deployment server #{machine_spec.name} at icsp failed!" unless task['taskState'].downcase == 'completed'
440
+ end
441
+ end
442
+ end
443
+
444
+
445
+ def destroy_oneview_profile(action_handler, machine_spec, profile = nil)
446
+ profile ||= get_oneview_profile_by_sn(machine_spec.reference['serial_number'])
447
+
448
+ hardware_info = rest_api(:oneview, :get, profile['serverHardwareUri'])
449
+ if hardware_info.nil?
450
+ action_handler.report_progress "INFO: #{machine_spec.name} is already deleted."
451
+ else
452
+ action_handler.perform_action "Delete server #{machine_spec.name} from oneview" do
453
+ action_handler.report_progress "INFO: Deleting server profile #{machine_spec.name}"
454
+ task = rest_api(:oneview, :Delete, "#{profile['uri']}")
455
+ task_uri = task['uri']
456
+
457
+ 60.times do # Wait for up to 10 minutes
458
+ task = rest_api(:oneview, :get, task_uri)
459
+ break if task['taskState'].downcase == 'completed'
460
+ print '.'
461
+ sleep 10
462
+ end
463
+ fail "Deleting server profile #{machine_spec.name} failed!" unless task['taskState'].downcase == 'completed'
464
+ end
465
+ end
466
+ end
467
+ end