chef-provisioning-oneview 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +43 -3
  3. data/lib/chef/provisioning/create_machine.rb +52 -0
  4. data/lib/chef/provisioning/customize_machine.rb +78 -0
  5. data/lib/chef/provisioning/icsp/api_v104.rb +27 -0
  6. data/lib/chef/provisioning/icsp/icsp_api.rb +282 -0
  7. data/lib/chef/provisioning/oneview/oneview_api.rb +56 -357
  8. data/lib/chef/provisioning/oneview/san_storage.rb +91 -0
  9. data/lib/chef/provisioning/oneview/v1.2/api.rb +3 -0
  10. data/lib/chef/provisioning/oneview_driver.rb +42 -5
  11. data/lib/chef/provisioning/rest.rb +51 -0
  12. data/lib/chef/provisioning/{oneview/version.rb → version.rb} +1 -1
  13. data/spec/shared_context.rb +77 -0
  14. data/spec/spec_helper.rb +8 -6
  15. data/spec/{unit/support → support}/fake_action_handler.rb +0 -0
  16. data/spec/support/fake_icsp.rb +73 -21
  17. data/spec/support/fake_machine_spec.rb +18 -0
  18. data/spec/support/fake_oneview.rb +148 -21
  19. data/spec/support/fixtures/icsp/v102/error_404.json +13 -13
  20. data/spec/support/fixtures/icsp/v102/login.json +4 -4
  21. data/spec/support/fixtures/icsp/v102/os-deployment-build-plans.json +99 -99
  22. data/spec/support/fixtures/icsp/v102/{os-deployment-servers_managed.json → os-deployment-servers.json} +178 -178
  23. data/spec/support/fixtures/icsp/v102/os-deployment-servers_1670001.json +83 -0
  24. data/spec/support/fixtures/icsp/v102/os-deployment-servers_fakesn.json +9 -0
  25. data/spec/support/fixtures/icsp/v102/{server_by_sn.json → server_by_sn_VCGE9KB041.json} +44 -44
  26. data/spec/support/fixtures/icsp/v102/server_by_sn_empty.json +16 -0
  27. data/spec/support/fixtures/icsp/v102/version.json +3 -3
  28. data/spec/support/fixtures/oneview/v120/error_404.json +13 -13
  29. data/spec/support/fixtures/oneview/v120/login.json +4 -4
  30. data/spec/support/fixtures/oneview/v120/server-hardware.json +1475 -1475
  31. data/spec/support/fixtures/oneview/v120/server-hardware_Template-WebServer.json +468 -0
  32. data/spec/support/fixtures/oneview/v120/server-hardware_specific.json +151 -0
  33. data/spec/support/fixtures/oneview/v120/server-profiles.json +368 -746
  34. data/spec/support/fixtures/oneview/v120/server-profiles_invalid_filter.json +14 -0
  35. data/spec/support/fixtures/oneview/v120/{server-profiles_specific.json → server-profiles_name_Template-WebServer.json} +132 -132
  36. data/spec/support/fixtures/oneview/v120/server-profiles_name_Template-WebServerWithSAN.json +200 -0
  37. data/spec/support/fixtures/oneview/v120/server-profiles_name_chef-web01.json +133 -0
  38. data/spec/support/fixtures/oneview/v120/server-profiles_name_chef-web03.json +133 -0
  39. data/spec/support/fixtures/oneview/v120/server-profiles_name_empty.json +15 -0
  40. data/spec/support/fixtures/oneview/v120/server-profiles_sn_VCGE9KB041.json +133 -0
  41. data/spec/support/fixtures/oneview/v120/server-profiles_sn_VCGE9KB042.json +206 -0
  42. data/spec/support/fixtures/oneview/v120/server-profiles_sn_empty.json +15 -0
  43. data/spec/support/fixtures/oneview/v120/storage-volumes_1B5D3CA2-6C5B-41C2-8B97-1821F1883F22.json +26 -0
  44. data/spec/support/fixtures/oneview/v120/tasks_fake_active.json +5 -0
  45. data/spec/support/fixtures/oneview/v120/tasks_fake_complete.json +5 -0
  46. data/spec/support/fixtures/oneview/v120/version.json +3 -3
  47. data/spec/support/fixtures/oneview/v200/server-profile-templates_WebServerTemplate.json +109 -0
  48. data/spec/support/fixtures/oneview/v200/server-profile-templates_WebServerTemplateWithSAN.json +144 -0
  49. data/spec/support/fixtures/oneview/v200/server-profile-templates_invalid.json +16 -0
  50. data/spec/support/fixtures/oneview/v200/server-profile-templates_new-profile_WebServerTemplate.json +125 -0
  51. data/spec/support/fixtures/oneview/v200/server-profile-templates_new-profile_WebServerTemplateWithSAN.json +178 -0
  52. data/spec/support/fixtures/oneview/v200/version.json +4 -0
  53. data/spec/unit/create_machine_spec.rb +78 -0
  54. data/spec/unit/destroy_spec.rb +26 -0
  55. data/spec/unit/icsp_nic_teams_spec.rb +38 -0
  56. data/spec/unit/icsp_search_spec.rb +25 -0
  57. data/spec/unit/oneview_driver_spec.rb +37 -64
  58. data/spec/unit/oneview_login_spec.rb +23 -0
  59. data/spec/unit/oneview_power_spec.rb +51 -0
  60. data/spec/unit/oneview_san_spec.rb +86 -0
  61. data/spec/unit/oneview_search_spec.rb +63 -0
  62. data/spec/unit/rest_api_spec.rb +115 -0
  63. metadata +90 -9
  64. data/lib/chef/provisioning/oneview/v1.20/api.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b9d6fc4eeb70e68a685026500dd2f1b740bc2258
4
- data.tar.gz: a6b1a761bbfda4d5a7df5254992eb7da07f7bc0a
3
+ metadata.gz: 9c3667ee64a73be8e4461ef96c4809810bea95e8
4
+ data.tar.gz: b1e38d4a95c2b93d25c665cec20e1ec9080557bc
5
5
  SHA512:
6
- metadata.gz: 1998337d50d17e87c542b76cf73974f2caf0403e12e5535bb4cb648edbb846bcece9fff6017a48b527771477c81d2f12b4ad3be67d7b544c3e0a9263cd20b47f
7
- data.tar.gz: 92a234a482898954435ce47c131a62ba8d71ad0c7b1f9c3514e6ccdd4d0ce6f0de59af4c0e2ccfd951b9167b2197aa699a53e2c8797fd329b341365a7ada8aa9
6
+ metadata.gz: 9cf35136389e6bb5dcffced8755ae54929f7bb35e9cea7d6501ed9d5ef1f457640e31d5cb6f6d831d7b859c6947a28118f2f856142884b539b73c04ad57e4c2d
7
+ data.tar.gz: 421361c479b72770676bd397e8591fe979c4d8b809813233522b059b01ab538dfa6e081b956ed67d229e3fbd1465b7b46c9b6601f877296d5456b04fba74d355
data/README.md CHANGED
@@ -1,7 +1,16 @@
1
1
  # chef-provisioning-oneview
2
2
  Chef Provisioning driver for HP OneView
3
3
 
4
- Currently supports OneView v1.2.0 and ICsp v7.4.0
4
+
5
+ [![Build Status](https://travis-ci.org/HewlettPackard/chef-provisioning-oneview.svg?branch=master)](https://travis-ci.org/HewlettPackard/chef-provisioning-oneview)
6
+ [![Gem Version](https://badge.fury.io/rb/chef-provisioning-oneview.svg)](https://badge.fury.io/rb/chef-provisioning-oneview)
7
+
8
+ Questions or comments? Join the Gitter room [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/HewlettPackard/chef-provisioning-oneview?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
9
+
10
+
11
+ Currently supports:
12
+ - OneView v1.2.0 - 2.0.0
13
+ - ICsp v7.4.0 - 7.5.0
5
14
 
6
15
  # Installation
7
16
 
@@ -40,7 +49,7 @@ Currently supports OneView v1.2.0 and ICsp v7.4.0
40
49
  ```
41
50
 
42
51
  - 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.
52
+ - Your OneView and ICSP servers must be set up beforehand. Unfortunately, this driver doesn't do that for you too.
44
53
 
45
54
  # Usage
46
55
 
@@ -61,7 +70,7 @@ machine 'web01' do
61
70
  :server_template => 'Web Server Template',
62
71
  :os_build => 'CHEF-RHEL-6.5-x64',
63
72
  :host_name => 'chef-web01',
64
- :ip_address => 'xx.xx.xx.xx', # For bootstrapping only.
73
+ :ip_address => 'xx.xx.xx.xx', # For bootstrapping. Deprecated in favor of { bootstrap: true } in connection; see below
65
74
 
66
75
  :domainType => 'workgroup',
67
76
  :domainName => 'sub.domain.com',
@@ -77,6 +86,12 @@ machine 'web01' do
77
86
  :dhcp => false # Optional. Overrides dhcp property above
78
87
  :gateway => 'xx.xx.xx.1' # Optional. Overrides gateway property above
79
88
  :dns => 'xx.xx.xx.xx' # Optional. Overrides dns property above
89
+ :bootstrap => true # Set this on 1 connection only. Tells Chef which connection to use to bootstrap.
90
+ },
91
+ 3 => {
92
+ :dhcp => true # Optional. Overrides dhcp property above
93
+ :gateway => :none # Optional. Overrides gateway property above
94
+ :dns => :none # Optional. Overrides dns property above
80
95
  }
81
96
  },
82
97
  :custom_attributes => {
@@ -101,6 +116,8 @@ end
101
116
 
102
117
  See https://github.com/chef/chef-provisioning-ssh for more transport_options.
103
118
 
119
+ NOTE: Some basic connection settings such as :ip4Address and :dhcp are shown in the example recipe, but you can pass in any interface/nic options that exist in the ICsp api for POST requests to /rest/os-deployment-jobs
120
+
104
121
  ### Custom Attributes
105
122
  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
123
 
@@ -120,10 +137,33 @@ if [ -n "$authorized_keys"]; then
120
137
  fi
121
138
  ```
122
139
 
140
+ ### SSH Keys
141
+ To use SSH keys insead of passwords to connect to nodes, you'll need to modify your transport_options to look something like:
142
+
143
+ ```ruby
144
+ :transport_options => {
145
+ :ssh_options => {
146
+ :auth_methods => ['publickey'],
147
+ :keys => ['~/.ssh/id_rsa']
148
+ }
149
+ }
150
+ ```
151
+
152
+ You'll also need to put the corresponding public key(s) into the node's authorized_keys file during the OS setup. See the Custom Attributes section above for one way to do this.
153
+
123
154
  ### Behind a proxy
124
155
  Add `:bootstrap_proxy => 'http://proxy.domain.com:8080'` to your convergence_options hash.
125
156
  Also, make sure your OS build plans set up the proxy configuration in a post OS install script.
126
157
 
158
+ ### SAN Storage
159
+ In order to attach a SAN volume as a bootable volume, the volume name must start with 'boot'; it will be appended with the the profile name on creation.
160
+
161
+ ### Switching to a different network after provisioning
162
+ Add `1 => {:net => "Deadnetwork", :deployNet => "PXE Network", :dhcp => true}` to your connections hash.
163
+ This will flip the first connection of the newly provisioned machine off of your pxe network to your Deadnetwork right after provisioning. This is helpful for taking the newly provisioned machine off the PXE network as soon as possible.
164
+
165
+ ### Adding Nic Teams
166
+ Add `:team => 'TeamName'` into a connection in your connections hash. Make sure that you have 2 connections in a team and the name does not include hyphens. This information will be passed to ISCP as the 'teams' custom attribute in the format: `"TeamName1-mac1,mac2|TeamName2-mac6,mac7,mac8"` to be consumed in a custom build plan script.
127
167
 
128
168
  # Doing a test run
129
169
  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.
@@ -0,0 +1,52 @@
1
+ module CreateMachine
2
+ private
3
+
4
+ # Chef oneview provisioning
5
+ def create_machine(action_handler, machine_spec, machine_options)
6
+ host_name = machine_options[:driver_options][:host_name]
7
+
8
+ auth_tokens # Login (to both ICSP and OneView)
9
+
10
+ # Check if profile exists first
11
+ matching_profiles = rest_api(:oneview, :get, "/rest/server-profiles?filter=name matches '#{host_name}'&sort=name:asc")
12
+ if matching_profiles['count'] > 0
13
+ profile = matching_profiles['members'].first
14
+ return profile
15
+ end
16
+
17
+ # Search for OneView Template by name
18
+ template = get_oneview_template(machine_options[:driver_options][:server_template])
19
+
20
+ # Get first availabe (and compatible) HP OV server blade
21
+ chosen_blade = available_hardware_for_template(template)
22
+
23
+ power_off(action_handler, machine_spec, chosen_blade['uri'])
24
+
25
+ # Create new profile instance from template
26
+ action_handler.perform_action "Initialize creation of server profile for #{machine_spec.name}" do
27
+ action_handler.report_progress "INFO: Initializing creation of server profile for #{machine_spec.name}"
28
+
29
+ # Add name & hardware uri to template
30
+ template['name'] = host_name
31
+ template['serverHardwareUri'] = chosen_blade['uri']
32
+
33
+ update_san_info(machine_spec, template)
34
+
35
+ # Post back to /rest/server-profiles
36
+ options = { 'body' => template }
37
+ options['X-API-Version'] = 200 if @current_oneview_api_version >= 200 && template['type'] == 'ServerProfileV5'
38
+ task = rest_api(:oneview, :post, '/rest/server-profiles', options)
39
+ task_uri = task['uri']
40
+ fail "Failed to create OneView server profile #{host_name}. Details: " unless task_uri
41
+ # Wait for profile to be created
42
+ 60.times do # Wait for up to 5 min
43
+ matching_profiles = rest_api(:oneview, :get, "/rest/server-profiles?filter=name matches '#{host_name}'&sort=name:asc")
44
+ return matching_profiles['members'].first if matching_profiles['members'].first
45
+ print '.'
46
+ sleep 5
47
+ end
48
+ task = rest_api(:oneview, :get, task_uri)
49
+ fail "Server profile couldn't be created! #{task['taskStatus']}. #{task['taskErrors'].first['message']}"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,78 @@
1
+ module CustomizeMachine
2
+ private
3
+
4
+ # Use ICSP to install OS
5
+ def customize_machine(action_handler, machine_spec, machine_options, profile)
6
+ auth_tokens # Login (to both ICSP and OneView)
7
+
8
+ # Wait for server profile to finish building
9
+ unless profile['state'] == 'Normal'
10
+ action_handler.perform_action "Wait for #{machine_spec.name} server to start and profile to be applied" do
11
+ action_handler.report_progress "INFO: Waiting for #{machine_spec.name} server to start and profile to be applied"
12
+ task = oneview_wait_for(profile['taskUri'], 240) # Wait up to 40 min for profile to be created
13
+ fail 'Timed out waiting for server to start and profile to be applied' if task == false
14
+ unless task == true
15
+ server_template = machine_options[:driver_options][:server_template]
16
+ fail "Error creating server profile from template #{server_template}: #{task['taskErrors'].first['message']}"
17
+ end
18
+ end
19
+ profile = get_oneview_profile_by_sn(machine_spec.reference['serial_number']) # Refresh profile
20
+ fail "Server profile state '#{profile['state']}' not 'Normal'" unless profile['state'] == 'Normal'
21
+ end
22
+
23
+ # Configure SAN storage (if applicable)
24
+ enable_boot_from_san(action_handler, machine_spec, profile) unless machine_spec.reference['network_personalitation_finished']
25
+
26
+ # Make sure server is started
27
+ power_on(action_handler, machine_spec, profile['serverHardwareUri'])
28
+
29
+ # Get ICSP servers to poll and wait until server PXE complete (to make sure ICSP is available).
30
+ my_server = nil
31
+ action_handler.perform_action "Wait for #{machine_spec.name} to boot into HP Intelligent Provisioning" do
32
+ action_handler.report_progress "INFO: Waiting for #{machine_spec.name} to PXE boot into HP Intelligent Provisioning"
33
+ 360.times do # Wait for up to 1 hr
34
+ my_server = get_icsp_server_by_sn(profile['serialNumber'])
35
+ break if !my_server.nil?
36
+ print '.'
37
+ sleep 10
38
+ end
39
+ fail "Timeout waiting for server #{machine_spec.name} to register with ICSP" if my_server.nil?
40
+ end
41
+
42
+ icsp_configure_nic_teams(machine_options, profile)
43
+
44
+ icsp_set_custom_attributes(machine_options, my_server)
45
+
46
+ icsp_run_os_install(action_handler, machine_spec, machine_options, my_server, profile)
47
+
48
+ # Customize networking
49
+ if !machine_spec.reference['network_personalitation_finished'] || machine_options[:driver_options][:force_network_update]
50
+
51
+ icsp_configure_networking(action_handler, machine_spec, machine_options, my_server, profile)
52
+
53
+ # Switch deploy networks to post-deploy networks if specified
54
+ if machine_options[:driver_options][:connections]
55
+ available_networks = rest_api(:oneview, :get, "/rest/server-profiles/available-networks?serverHardwareTypeUri=#{profile['serverHardwareTypeUri']}&enclosureGroupUri=#{profile['enclosureGroupUri']}")
56
+ machine_options[:driver_options][:connections].each do |id, data|
57
+ next unless data && data[:net] && data[:deployNet]
58
+ action_handler.report_progress "INFO: Performing network flipping on #{machine_spec.name}, connection #{id}"
59
+ deploy_network = available_networks['ethernetNetworks'].find {|n| n['name'] == data[:deployNet] }
60
+ new_network = available_networks['ethernetNetworks'].find {|n| n['name'] == data[:net] }
61
+ fail "Failed to perform network flipping on #{machine_spec.name}, connection #{id}. '#{data[:net]}' network not found" if new_network.nil?
62
+ fail "Failed to perform network flipping on #{machine_spec.name}, connection #{id}. '#{data[:deployNet]}' network not found" if deploy_network.nil?
63
+ profile = get_oneview_profile_by_sn(machine_spec.reference['serial_number'])
64
+ profile['connections'].find {|c| c['networkUri'] == deploy_network['uri'] }['networkUri'] = new_network['uri']
65
+ options = { 'body' => profile }
66
+ task = rest_api(:oneview, :put, profile['uri'], options)
67
+ fail "Failed to perform network flipping on #{machine_spec.name}. Details: #{task['message'] || task}" unless task['uri']
68
+ task = oneview_wait_for(task['uri']) # Wait up to 10 min
69
+ fail "Timed out waiting for network flipping on #{machine_spec.name}" if task == false
70
+ fail "Error performing network flip on #{machine_spec.name}. Response: #{task}" unless task == true
71
+ end
72
+ end
73
+ machine_spec.reference['network_personalitation_finished'] = true
74
+ end
75
+
76
+ my_server = rest_api(:icsp, :get, my_server['uri'])
77
+ end
78
+ end
@@ -0,0 +1,27 @@
1
+ module ICspAPIv104
2
+ # Parse and clean connection data for api call
3
+ def icsp_v104_parse_connection(machine_options, c)
4
+ allowed_keys = %w(macAddress enabled dhcpv4 ipv6autoconfig provisioning dnsServers winsServers dnsSearch staticNetworks vlanid ipv4gateway ipv6gateway)
5
+ c[:enabled] ||= true
6
+ c[:vlanid] ||= '-1'
7
+ c[:dhcpv4] ||= c[:dhcp]
8
+ c[:ipv4gateway] ||= c[:gateway] || machine_options[:driver_options][:gateway]
9
+ c[:ipv4gateway] = nil if c[:ipv4gateway] == :none
10
+ c[:dnsServers] ||= c[:dns] || machine_options[:driver_options][:dns] || []
11
+ c[:dnsServers] = nil if c[:dnsServers] == :none
12
+ c[:dnsServers] = c[:dnsServers].split(',') if c[:dnsServers].class == String
13
+ c[:staticNetworks] ||= ["#{c[:ip4Address]}/#{c[:mask] || machine_options[:driver_options][:mask] || '24'}"] if c[:ip4Address]
14
+ c.keep_if {|k, _v| allowed_keys.include? k.to_s }
15
+ end
16
+
17
+ # Parse and clean personality_data data for api call
18
+ def icsp_v104_build_personality_data(machine_options, nics)
19
+ allowed_keys = %w(hostname domain workgroup)
20
+ personality_data = Marshal.load(Marshal.dump(machine_options[:driver_options])) || {}
21
+ personality_data.keep_if {|k, _v| allowed_keys.include? k.to_s }
22
+ personality_data['hostname'] ||= machine_options[:driver_options][:host_name]
23
+ personality_data['domain'] ||= machine_options[:driver_options][:domainName]
24
+ personality_data['interfaces'] = nics
25
+ personality_data
26
+ end
27
+ end
@@ -0,0 +1,282 @@
1
+ Dir[File.dirname(__FILE__) + '/**/*.rb'].each {|file| require file } # Include all helper files in this directory & subdirectories
2
+
3
+ module ICspAPI
4
+ private
5
+
6
+ include ICspAPIv104
7
+
8
+ def get_icsp_api_version
9
+ begin
10
+ version = rest_api(:icsp, :get, '/rest/version', { 'Content-Type' => :none, 'X-API-Version' => :none, 'auth' => :none })['currentVersion']
11
+ fail "Couldn't get API version" unless version
12
+ if version.class != Fixnum
13
+ version = version.to_i
14
+ fail 'API version type mismatch' if !version > 0
15
+ end
16
+ rescue
17
+ puts 'Failed to get ICSP API version. Setting to default (102)'
18
+ version = 102
19
+ end
20
+ version
21
+ end
22
+
23
+ def login_to_icsp
24
+ path = '/rest/login-sessions'
25
+ options = {
26
+ 'body' => {
27
+ 'userName' => @icsp_username,
28
+ 'password' => @icsp_password,
29
+ 'authLoginDomain' => 'LOCAL'
30
+ }
31
+ }
32
+ response = rest_api(:icsp, :post, path, options)
33
+ return response['sessionID'] if response['sessionID']
34
+ fail("\nERROR! Couldn't log into OneView server at #{@oneview_base_url}. Response:\n#{response}")
35
+ end
36
+
37
+ def get_icsp_server_by_sn(serialNumber)
38
+ fail 'Must specify a serialNumber!' if serialNumber.nil? || serialNumber.empty?
39
+ search_result = rest_api(:icsp, :get,
40
+ "/rest/index/resources?category=osdserver&query='osdServerSerialNumber:\"#{serialNumber}\"'")['members'] rescue nil
41
+ if search_result && search_result.size == 1 && search_result.first['attributes']['osdServerSerialNumber'] == serialNumber
42
+ my_server_uri = search_result.first['uri']
43
+ my_server = rest_api(:icsp, :get, my_server_uri)
44
+ end
45
+ unless my_server && my_server['uri']
46
+ os_deployment_servers = rest_api(:icsp, :get, '/rest/os-deployment-servers')
47
+ # Pick the relevant os deployment server from icsp
48
+ my_server = nil
49
+ os_deployment_servers['members'].each do |server|
50
+ if server['serialNumber'] == serialNumber
51
+ my_server = server
52
+ break
53
+ end
54
+ end
55
+ end
56
+ my_server
57
+ end
58
+
59
+ def icsp_wait_for(task_uri, wait_iterations = 60, sleep_seconds = 10)
60
+ fail 'Must specify a task_uri!' if task_uri.nil? || task_uri.empty?
61
+ wait_iterations.times do
62
+ task = rest_api(:icsp, :get, task_uri)
63
+ if task['taskState']
64
+ case task['taskState'].downcase
65
+ when 'completed'
66
+ return true
67
+ when 'error', 'killed', 'terminated'
68
+ return task
69
+ end
70
+ elsif task['running'] == 'false' && task['jobResult']
71
+ if task['state'] == 'STATUS_SUCCESS'
72
+ return true
73
+ else
74
+ return task
75
+ end
76
+ end
77
+ print '.'
78
+ sleep sleep_seconds
79
+ end
80
+ false
81
+ end
82
+
83
+ # Consume and set any custom attributes that were specified
84
+ def icsp_set_custom_attributes(machine_options, my_server)
85
+ if machine_options[:driver_options][:custom_attributes]
86
+ curr_server = rest_api(:icsp, :get, my_server['uri'])
87
+ machine_options[:driver_options][:custom_attributes].each do |key, val|
88
+ curr_server['customAttributes'].push({
89
+ 'values' => [{ 'scope' => 'server', 'value' => val.to_s }],
90
+ 'key' => key.to_s
91
+ })
92
+ end
93
+ options = { 'body' => curr_server }
94
+ rest_api(:icsp, :put, my_server['uri'], options)
95
+ end
96
+ end
97
+
98
+ def icsp_run_os_install(action_handler, machine_spec, machine_options, my_server, profile)
99
+ return if my_server['state'] == 'OK' # Skip if the OS has already been deployed
100
+
101
+ # Wait for my_server['state'] to be in MAINTENANCE mode
102
+ if my_server['state'] != 'MAINTENANCE'
103
+ action_handler.perform_action "Wait for #{machine_spec.name} to go into maintenance mode in ICsp" do
104
+ action_handler.report_progress "INFO: Waiting for #{machine_spec.name} to go into maintenance mode in ICsp"
105
+ 120.times do # Wait for up to 20 min
106
+ my_server = get_icsp_server_by_sn(profile['serialNumber'])
107
+ break if my_server['state'] != 'MAINTENANCE'
108
+ print '.'
109
+ sleep 10
110
+ end
111
+ fail "Timed out waiting for #{machine_spec.name} to go into maintenance mode in ICsp. State: #{my_server['state']}" unless my_server['state'] == 'MAINTENANCE'
112
+ end
113
+ end
114
+
115
+ # Get the specified OS Build Plan(s)
116
+ os_builds = machine_options[:driver_options][:os_build]
117
+ os_builds = [os_builds] if os_builds.class == String
118
+ build_plan_uris = []
119
+ action_handler.perform_action "Get OS Build Plan(s) info for #{machine_spec.name}" do
120
+ action_handler.report_progress "INFO: Getting OS Build Plan(s) for #{machine_spec.name}"
121
+ os_builds.each do |os_build|
122
+ uri = "/rest/index/resources?userQuery=\"'#{os_build}'\"&category=osdbuildplan"
123
+ while uri
124
+ matching_plans = rest_api(:icsp, :get, uri)
125
+ fail "Search failed for OSBP '#{os_build}'. Response: #{matching_plans}" unless matching_plans['members']
126
+ build_plan_uri = matching_plans['members'].find {|bp| bp['name'] == os_build}['uri'] rescue nil
127
+ break unless build_plan_uri.nil?
128
+ uri = URI.unescape(matching_plans['nextPageUri']) rescue nil
129
+ end
130
+ fail "OS build plan #{os_build} not found!" if build_plan_uri.nil?
131
+ build_plan_uris.push build_plan_uri
132
+ end
133
+ end
134
+
135
+ # Build options for the OS deployment
136
+ options = {}
137
+ options['X-API-Version'] = 104 if @current_icsp_api_version.between?(104, 108)
138
+ options['body'] = {
139
+ 'osbpUris' => build_plan_uris,
140
+ 'serverData' => [{
141
+ 'serverUri' => my_server['uri']
142
+ }]
143
+ }
144
+
145
+ # Do the OS deployment
146
+ action_handler.perform_action "Run: #{os_builds} OS Build Plan(s) on #{machine_spec.name}" do
147
+ action_handler.report_progress "INFO: Running: #{os_builds} OS Build Plan(s) on #{machine_spec.name}"
148
+ task = rest_api(:icsp, :post, '/rest/os-deployment-jobs/?force=true', options)
149
+ task_uri = task['uri']
150
+ fail "Failed to start OS Deployment Job. Details: #{task['details'] || task['message'] || task}" unless task_uri
151
+ task = icsp_wait_for(task_uri, 720)
152
+ fail "Error running OS build plan(s) #{os_builds}: #{task['jobResult'].first['jobMessage']}\n#{task['jobResult'].first['jobResultErrorDetails']}" unless task == true
153
+ end
154
+ end
155
+
156
+ def icsp_configure_networking(action_handler, machine_spec, machine_options, my_server, profile)
157
+ action_handler.perform_action "Configure networking on #{machine_spec.name}" do
158
+ action_handler.report_progress "INFO: Configuring networking on #{machine_spec.name}"
159
+ personality_data = icsp_build_personality_data(machine_options, profile)
160
+ options = {}
161
+ options['X-API-Version'] = 104 if @current_icsp_api_version.between?(104, 108)
162
+ options['body'] = {
163
+ 'serverData' => [{
164
+ 'serverUri' => my_server['uri'],
165
+ 'personalityData' => personality_data
166
+ }]
167
+ }
168
+
169
+ task = rest_api(:icsp, :post, '/rest/os-deployment-jobs/?force=true', options)
170
+ task_uri = task['uri']
171
+ fail "Failed to start network personalization job. Details: #{task['details']}" unless task_uri
172
+ task = icsp_wait_for(task_uri, 60) # Wait for up to 10 min
173
+ fail "Error running network personalization job: #{task['jobResult'].first['jobMessage']}\n#{task['jobResult'].first['jobResultErrorDetails']}" unless task == true
174
+
175
+ # Check if ICsp IP config matches machine options
176
+ requested_ips = []
177
+ machine_options[:driver_options][:connections].each do |_id, c|
178
+ requested_ips.push c[:ip4Address] if c[:ip4Address] && c[:dhcp] == false
179
+ end
180
+ my_server_connections = []
181
+ 10.times do
182
+ my_server_connections = rest_api(:icsp, :get, my_server['uri'])['interfaces']
183
+ my_server_connections.each { |c| requested_ips.delete c['ipv4Addr'] }
184
+ break if requested_ips.empty?
185
+ print ','
186
+ sleep 10
187
+ end
188
+ puts "\nWARN: The following IPs are not visible on ICsp, so they may not have gotten configured correctly: #{requested_ips}" unless requested_ips.empty?
189
+
190
+ # Set interface data as normal node attributes
191
+ my_server_connections.each do |c|
192
+ c['oneViewId'] = profile['connections'].find {|x| x['mac'] == c['macAddr']}['id'] rescue nil
193
+ end
194
+ machine_spec.data['normal']['icsp'] ||= {}
195
+ machine_spec.data['normal']['icsp']['interfaces'] = my_server_connections
196
+ end
197
+ end
198
+
199
+ # Build options for the network configuration
200
+ def icsp_build_personality_data(machine_options, profile)
201
+ nics = []
202
+ if machine_options[:driver_options][:connections]
203
+ machine_options[:driver_options][:connections].each do |id, data|
204
+ c = Marshal.load(Marshal.dump(data))
205
+ next unless c[:dhcp] || c[:dhcpv4] || c[:ip4Address] || c[:ipv6autoconfig] || c[:staticNetworks] # Invalid network or only switch networks specified
206
+ begin
207
+ c[:macAddress] = profile['connections'].find {|x| x['id'] == id}['mac']
208
+ rescue NoMethodError
209
+ ids = []
210
+ profile['connections'].each {|x| ids.push x['id']}
211
+ raise "Could not find connection id #{id} for #{profile['name']}. Available connection ids are: #{ids}. Please make sure the connection ids map to those on OneView."
212
+ end
213
+ if @current_icsp_api_version.between?(104, 108)
214
+ icsp_v104_parse_connection(machine_options, c)
215
+ else
216
+ c[:mask] ||= machine_options[:driver_options][:mask]
217
+ c[:dhcp] ||= false
218
+ c[:gateway] ||= machine_options[:driver_options][:gateway]
219
+ c[:dns] ||= machine_options[:driver_options][:dns]
220
+ end
221
+ nics.push c
222
+ end
223
+ end
224
+
225
+ if @current_icsp_api_version.between?(104, 108)
226
+ personality_data = icsp_v104_build_personality_data(machine_options, nics)
227
+ else
228
+ personality_data = {
229
+ 'hostName' => machine_options[:driver_options][:host_name],
230
+ 'domainType' => machine_options[:driver_options][:domainType],
231
+ 'domainName' => machine_options[:driver_options][:domainName],
232
+ 'nics' => nics
233
+ }
234
+ end
235
+ personality_data
236
+ end
237
+
238
+ def destroy_icsp_server(action_handler, machine_spec)
239
+ my_server = get_icsp_server_by_sn(machine_spec.reference['serial_number'])
240
+ return false if my_server.nil? || my_server['uri'].nil?
241
+
242
+ action_handler.perform_action "Delete server #{machine_spec.name} from ICSP" do
243
+ task = rest_api(:icsp, :delete, my_server['uri']) # TODO: This returns nil instead of task info
244
+ if task['uri']
245
+ task_uri = task['uri']
246
+ 90.times do # Wait for up to 15 minutes
247
+ task = rest_api(:icsp, :get, task_uri)
248
+ break if task['taskState'].downcase == 'completed'
249
+ print '.'
250
+ sleep 10
251
+ end
252
+ fail "Deleting os deployment server #{machine_spec.name} at icsp failed!" unless task['taskState'].downcase == 'completed'
253
+ end
254
+ end
255
+ end
256
+
257
+ def icsp_configure_nic_teams(machine_options, profile)
258
+ return false if machine_options[:driver_options][:connections].nil?
259
+ teams = {}
260
+
261
+ machine_options[:driver_options][:connections].each do |id, options|
262
+ next unless options.is_a?(Hash) && options[:team]
263
+ fail "#{options[:team]}: Team names must not include hyphens" if options[:team].to_s.match('-')
264
+ teams[options[:team].to_s] ||= []
265
+ begin
266
+ mac = profile['connections'].find {|x| x['id'] == id}['mac']
267
+ teams[options[:team].to_s].push mac
268
+ rescue NoMethodError
269
+ ids = []
270
+ profile['connections'].each {|x| ids.push x['id']}
271
+ raise "Failed to configure nic teams: Could not find connection id #{id} for #{profile['name']}. Available connection ids are: #{ids}. Please make sure the connection ids map to those on OneView."
272
+ end
273
+ end
274
+ team_strings = []
275
+ teams.each do |name, macs|
276
+ fail "Team '#{name}' must have at least 2 associated connections to form a NIC team" unless macs.size >= 2
277
+ team_strings.push "#{name}-#{macs.join(',')}"
278
+ end
279
+ machine_options[:driver_options][:custom_attributes] ||= {}
280
+ machine_options[:driver_options][:custom_attributes][:teams] = team_strings.join('|')
281
+ end
282
+ end # End module