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 +7 -0
- data/LICENSE +9 -0
- data/README.md +149 -0
- data/Rakefile +19 -0
- data/lib/chef/provisioning/driver_init/oneview.rb +3 -0
- data/lib/chef/provisioning/oneview/oneview_api.rb +467 -0
- data/lib/chef/provisioning/oneview/v1.20/api.rb +3 -0
- data/lib/chef/provisioning/oneview/v2.0/api.rb +3 -0
- data/lib/chef/provisioning/oneview/version.rb +5 -0
- data/lib/chef/provisioning/oneview_driver.rb +173 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/fake_icsp.rb +21 -0
- data/spec/support/fake_oneview.rb +21 -0
- data/spec/support/fixtures/icsp/v102/error_404.json +14 -0
- data/spec/support/fixtures/icsp/v102/login.json +5 -0
- data/spec/support/fixtures/icsp/v102/os-deployment-build-plans.json +99 -0
- data/spec/support/fixtures/icsp/v102/os-deployment-servers_managed.json +179 -0
- data/spec/support/fixtures/icsp/v102/server_by_sn.json +45 -0
- data/spec/support/fixtures/icsp/v102/version.json +4 -0
- data/spec/support/fixtures/oneview/v120/error_404.json +14 -0
- data/spec/support/fixtures/oneview/v120/login.json +5 -0
- data/spec/support/fixtures/oneview/v120/server-hardware.json +1476 -0
- data/spec/support/fixtures/oneview/v120/server-profiles.json +747 -0
- data/spec/support/fixtures/oneview/v120/server-profiles_specific.json +133 -0
- data/spec/support/fixtures/oneview/v120/version.json +4 -0
- data/spec/unit/oneview_driver_spec.rb +98 -0
- data/spec/unit/support/fake_action_handler.rb +6 -0
- metadata +159 -0
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,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
|