chef-provisioning-crowbar 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +23 -0
- data/README.md +136 -0
- data/Rakefile +22 -0
- data/lib/chef/provisioning/crowbar_driver/driver.rb +280 -0
- data/lib/chef/provisioning/crowbar_driver/version.rb +21 -0
- data/lib/chef/provisioning/driver_init/crowbar.rb +17 -0
- data/lib/crowbar/core.rb +280 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 749999debb44d64aaa65360b79ba838692756f80
|
4
|
+
data.tar.gz: 48a387ef467e1786ecf054e0b458923004f80216
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e24a4d37cef67b30a675bbeb9f32e4a2d4336de5d561ab9c6eb348e56c3cb8c8484241da35199a2ea7074433c8fdf99935f6f6207ddfdd39e25b7cf52a2c2e12
|
7
|
+
data.tar.gz: 440d09ad30356e528eee54d1cf70659f03276d8fdca9e47c9de2e7160b2945339860a64e1208bc14306a90624f43c96c5c93b3959b2de11dc8637af605fa2d77
|
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Apache 2
|
2
|
+
|
3
|
+
This submodule of OpenCrowbar is Apache 2 licensed.
|
4
|
+
|
5
|
+
## Copyright 2014, Rob Hirschfeld
|
6
|
+
## Copyright 2015, Judd Maltin
|
7
|
+
|
8
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
you may not use this file except in compliance with the License.
|
10
|
+
You may obtain a copy of the License at
|
11
|
+
|
12
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
|
14
|
+
Unless required by applicable law or agreed to in writing, software
|
15
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
See the License for the specific language governing permissions and
|
18
|
+
limitations under the License.
|
19
|
+
|
20
|
+
|
21
|
+
## For details
|
22
|
+
|
23
|
+
See [[doc/licenses/README.md]]
|
data/README.md
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# OpenCrowbar for Chef-Provisioning
|
2
|
+
|
3
|
+
**Chef Provisioning with Crowbar to treat your metal like a cloud**
|
4
|
+
|
5
|
+
This repo contains the interface between Chef Provisioning (https://github.com/opscode/chef-provisioning/) and OpenCrowbar.
|
6
|
+
|
7
|
+
> make sure you are running Ruby 1.9 and related gems for this tool. check `ruby -v` to verify.
|
8
|
+
|
9
|
+
> Better yet: *use chef-dk* - it now has chef-provisioning gems included.
|
10
|
+
|
11
|
+
## Background
|
12
|
+
|
13
|
+
Crowbar discovers and manages your gear - preferably hardware nodes. The typical model would be to get Crowbar running on your admin network, and start booting up your gear. Crowbar will discover and inventory your gear automatically. You can then use Chef Provisioning to tell Crowbar to do all those things it's good at: install the OS you want, configure the BIOS, RAID, networking, and manage the power states of the gear. You can keep using Chef-Provisioning and Crowbar when you want to power down or re-image those nodes.
|
14
|
+
|
15
|
+
Crowbar manages nodes in groups of "deployments." All nodes start and spend their lives in the foundational "system" deployment. They're then added to deployments you create to effect change on them. By creating a new deployment and adding nodes to it, all the roles you defined as belonging to that deployment get run on the gear.
|
16
|
+
|
17
|
+
When you write a recipe with Chef Provisioning and Crowbar, Chef Provisioning requests from Crowbar to "allocate_machine." Crowbar then looks in its inventory for a node that is "alive" according to Crowbar (Crowbar can power it on and ssh into it,) but not used in any other deployments than "system." Once Crowbar finds an appropriate node, it adds the node to the "ready" deployment (default name, and will create it if necessary.) This will start Crowbar configuring the hardware, OS, network, etc. Chef Provisioning waits patiently for all this to get finished, so it can consider the node "ready."
|
18
|
+
|
19
|
+
A Chef Provisioning "ready" machine is a Crowbar node which has completed the tasks in the proper deployment ("ready"), and Crowbar has given up managing it until further notice. In Crowbar terms, that means that the milestone noderole for the deployment is 'active', and the node is marked in Crowbar as node["available"]:false so the annealer will not manage the node's noderoles.
|
20
|
+
|
21
|
+
## Example
|
22
|
+
|
23
|
+
Chef Provisioning with Crowbar will treat your gear like a cloud!
|
24
|
+
|
25
|
+
Example Session:
|
26
|
+
|
27
|
+
See the nodes available from Crowbar:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
bash-4.1# crowbar deployments nodes "system" | grep '"alias"'
|
31
|
+
"alias": "d52-54-32-f9-00-00",
|
32
|
+
"alias": "d52-54-32-f8-00-00",
|
33
|
+
"alias": "d52-54-32-f5-00-00",
|
34
|
+
"alias": "d52-54-32-f6-00-00",
|
35
|
+
"alias": "d52-54-32-f7-00-00",
|
36
|
+
"alias": "be727e682d0d",
|
37
|
+
bash-4.1#
|
38
|
+
```
|
39
|
+
|
40
|
+
And you can see them with `knife` against the chef server in Crowbar:
|
41
|
+
|
42
|
+
```bash
|
43
|
+
$ knife node list -s http://192.168.124.10
|
44
|
+
be727e682d0d.crowbar.org
|
45
|
+
d52-54-32-f5-00-00.crowbar.org
|
46
|
+
d52-54-32-f6-00-00.crowbar.org
|
47
|
+
d52-54-32-f7-00-00.crowbar.org
|
48
|
+
d52-54-32-f8-00-00.crowbar.org
|
49
|
+
d52-54-32-f9-00-00.crowbar.org
|
50
|
+
```
|
51
|
+
|
52
|
+
So, to provision one of these, run the example recipe:
|
53
|
+
|
54
|
+
Example Recipe:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
require 'chef/provisioning'
|
58
|
+
with_driver 'crowbar'
|
59
|
+
|
60
|
+
# Here I indicate the chef server running inside Crowbar. If you like, use your own Chef Server, or just
|
61
|
+
# use Chef Zero by calling chef-client -z <recipe_name>.
|
62
|
+
|
63
|
+
with_chef_server 'https://192.168.124.10',
|
64
|
+
:client_name => 'metal',
|
65
|
+
:signing_key_filename => '/home/metal/.chef/metal.pem'
|
66
|
+
|
67
|
+
machine "chef-provisioning-#{rand(1000)}" do
|
68
|
+
machine_options :crowbar_options => { 'provisioner-target_os' => 'centos-7.0' }
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
Example chef-client Invocation:
|
73
|
+
|
74
|
+
```bash
|
75
|
+
$ chef-client ./crowbar_test.rb
|
76
|
+
```
|
77
|
+
|
78
|
+
and you will see the new node being allocated and made ready. Here's a few nodes in the "ready"
|
79
|
+
deployment.
|
80
|
+
|
81
|
+
```bash
|
82
|
+
# crowbar deployments nodes "ready" | grep '"alias"'
|
83
|
+
"alias": "chef-provisioning-another-brother-42",
|
84
|
+
"alias": "chef-provisioning-another-brother-97",
|
85
|
+
"alias": "chef-provisioning-example-56",
|
86
|
+
"alias": "chef-provisioning-another-brother-80",
|
87
|
+
```
|
88
|
+
|
89
|
+
|
90
|
+
Real world use would have you put a run-list in the `machine` resource, so chef can use the nodes to actually do things.
|
91
|
+
|
92
|
+
## Setup
|
93
|
+
|
94
|
+
### Networking
|
95
|
+
|
96
|
+
Before you start, your chef-provisioning box must be on the crowbar administration network, so it can ssh into the slave nodes and do its cheffy thing. In our typical development environments, that's simply a matter of setting your host OSs network as follows:
|
97
|
+
|
98
|
+
```bash
|
99
|
+
$ sudo ip a add 192.168.124.2/24 dev docker0
|
100
|
+
```
|
101
|
+
|
102
|
+
### Chef
|
103
|
+
|
104
|
+
#### Chef-DK
|
105
|
+
|
106
|
+
I use the chef-dk, which now has Chef Provisioning built in. Get your `knife` and `chef-client` all setup for development work.
|
107
|
+
|
108
|
+
#### Gems, no Chef-DK
|
109
|
+
|
110
|
+
Or you can use Chef gems.
|
111
|
+
|
112
|
+
*Gem dependencies* You also need the HTTParty, Chef and Chef-Metal gems: `sudo gem install httparty chef chef-metal`
|
113
|
+
|
114
|
+
## Example gem build script and test run
|
115
|
+
|
116
|
+
This is an example file to run and build chef provisioning crowbar.
|
117
|
+
|
118
|
+
All the exciting stuff is happening in chef-provisioning-crowbar/cookbooks/app/recipes/crowbar-test.rb
|
119
|
+
|
120
|
+
/$HOME/build_and_test_chef-provisioning-crowbar.sh
|
121
|
+
|
122
|
+
```bash
|
123
|
+
# source the chef-dk env
|
124
|
+
. $HOME/.bash_profile
|
125
|
+
CPC_REPO_PATH=<path to chef-provisioning-crowbar git repo>
|
126
|
+
cd ${CPC_REPO_PATH}
|
127
|
+
gem build chef-provisioning-crowbar.gemspec
|
128
|
+
gem install --ignore-dependencies --no-ri --no-rdoc chef-provisioning-crowbar-0.0.2.gem
|
129
|
+
cd ${CPC_REPO_PATH}/cookbooks/app/recipes/
|
130
|
+
# run with debugging..
|
131
|
+
#chef-client -l debug -z crowbar_test.rb
|
132
|
+
# or not
|
133
|
+
#chef-client -z crowbar_test.rb
|
134
|
+
# or omit -z to use a chef server indicated elsewhere
|
135
|
+
chef-client ./crowbar_test.rb
|
136
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
# Copyright 2014, Rob Hirschfeld
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'bundler'
|
17
|
+
require 'bundler'
|
18
|
+
require 'bundler/gem_tasks'
|
19
|
+
|
20
|
+
task :spec do
|
21
|
+
require File.expand_path('spec/run')
|
22
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
# Copyright 2014, Rob Hirschfeld
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
#require 'chef/mixin/shell_out'
|
16
|
+
require 'chef/provisioning/driver'
|
17
|
+
require 'chef/provisioning/machine/unix_machine'
|
18
|
+
require 'chef/provisioning/machine_spec'
|
19
|
+
require 'chef/provisioning/convergence_strategy/install_cached'
|
20
|
+
require 'chef/provisioning/transport/ssh'
|
21
|
+
require 'chef/provisioning/crowbar_driver/version'
|
22
|
+
require 'etc'
|
23
|
+
require 'time'
|
24
|
+
#require 'cheffish/merged_config'
|
25
|
+
require 'crowbar/core'
|
26
|
+
|
27
|
+
class Chef
|
28
|
+
module Provisioning
|
29
|
+
module CrowbarDriver
|
30
|
+
|
31
|
+
class Driver < Chef::Provisioning::Driver
|
32
|
+
|
33
|
+
|
34
|
+
ALLOCATE_DEPLOYMENT = 'system'
|
35
|
+
READY_DEPLOYMENT = 'ready'
|
36
|
+
TARGET_NODE_ROLE = "crowbar-installed-node"
|
37
|
+
API_BASE = "/api/v2"
|
38
|
+
|
39
|
+
def initialize(driver_url, config)
|
40
|
+
super(driver_url, config)
|
41
|
+
@crowbar = Crowbar.new
|
42
|
+
#config[:private_key_paths] = [ "$HOME/.ssh/id_rsa" ]
|
43
|
+
#config[:log_level] = :debug
|
44
|
+
end
|
45
|
+
|
46
|
+
# Passed in a driver_url, and a config in the format of Driver.config.
|
47
|
+
def self.from_url(driver_url, config)
|
48
|
+
Driver.new(driver_url, config)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.canonicalize_url(driver_url, config)
|
52
|
+
[ driver_url, config ]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Acquire a machine, generally by provisioning it. Returns a Machine
|
56
|
+
# object pointing at the machine, allowing useful actions like setup,
|
57
|
+
# converge, execute, file and directory.
|
58
|
+
|
59
|
+
def allocate_machine(action_handler, machine_spec, machine_options)
|
60
|
+
|
61
|
+
@crowbar.log_level(config[:log_level])
|
62
|
+
|
63
|
+
if machine_spec.location
|
64
|
+
if !@crowbar.node_exists?(machine_spec.location['server_id'])
|
65
|
+
# It doesn't really exist
|
66
|
+
action_handler.perform_action "Machine #{machine_spec.location['server_id']} does not really exist. Recreating ..." do
|
67
|
+
machine_spec.location = nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if !machine_spec.location
|
73
|
+
action_handler.perform_action "Crowbar: #{@crowbar} Creating server #{machine_spec.name} with options #{machine_options}" do
|
74
|
+
# TODO: Make sure SSH keys are found locally here? Or in allocate_node?
|
75
|
+
# get ssh pubkey from current user
|
76
|
+
#result = shell_out("cat ~/.ssh/id_rsa.pub", :cwd => '$HOME')
|
77
|
+
#sshkey = result.stdout
|
78
|
+
#action_handler.report_progress "sshpubkey on admin server #{sshkey}\n"
|
79
|
+
# put it on the crowbar server provisioner
|
80
|
+
#@crowbar.add_sshkey(result.stdout)
|
81
|
+
|
82
|
+
server = allocate_node(machine_spec.name, machine_options, action_handler)
|
83
|
+
server_id = server["id"]
|
84
|
+
debug "allocate server_id = #{server_id}"
|
85
|
+
machine_spec.location = {
|
86
|
+
'driver_url' => driver_url,
|
87
|
+
'driver_version' => Chef::Provisioning::CrowbarDriver::VERSION,
|
88
|
+
'server_id' => server_id,
|
89
|
+
'node_role_id' => server["node_role_id"]
|
90
|
+
# 'bootstrap_key' => sshkey
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Ready a machine moves the machine from the System deployment
|
97
|
+
# to the Ready deployment. Default Ready deployment is named 'ready'
|
98
|
+
# but will pick up machine_configs that match
|
99
|
+
def ready_machine(action_handler, machine_spec, machine_options)
|
100
|
+
debug machine_spec.location
|
101
|
+
|
102
|
+
server_id = machine_spec.location['server_id']
|
103
|
+
unless @crowbar.node_alive?(server_id)
|
104
|
+
action_handler.perform_action "Powering up machine #{server_id}" do
|
105
|
+
@crowbar.power(server_id, "on")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
nr_id = machine_spec.location['node_role_id']
|
110
|
+
|
111
|
+
action_handler.report_progress "Awaiting ready machine..."
|
112
|
+
action_handler.perform_action "done waiting for machine id: #{server_id}" do
|
113
|
+
loop do
|
114
|
+
break if @crowbar.node_ready(server_id,nr_id)
|
115
|
+
sleep 5
|
116
|
+
end
|
117
|
+
action_handler.report_progress "waited for machine - machine is ready. machine id: #{server_id}"
|
118
|
+
end
|
119
|
+
|
120
|
+
# set the machine to "reserved" to take control away from Crowbar
|
121
|
+
node = @crowbar.node(server_id)
|
122
|
+
node['available'] = false
|
123
|
+
@crowbar.set_node(server_id, node)
|
124
|
+
|
125
|
+
# Return the Machine object
|
126
|
+
machine_for(machine_spec, machine_options)
|
127
|
+
end
|
128
|
+
|
129
|
+
def machine_for(machine_spec, machine_options)
|
130
|
+
ssh_options = {
|
131
|
+
:auth_methods => ['publickey'],
|
132
|
+
}
|
133
|
+
server_id = machine_spec.location['server_id']
|
134
|
+
node_admin_addresses = @crowbar.node_attrib(server_id, 'network-admin_addresses')
|
135
|
+
node_ipv4_admin_net_ip = node_admin_addresses['value'][0].split('/')[0]
|
136
|
+
node_ipv4_admin_net_ip = node_admin_addresses['value'][0].split('/')[0]
|
137
|
+
#node_ipv6_admin_net_ip = node_admin_addresses['value'][1]
|
138
|
+
|
139
|
+
transport = Chef::Provisioning::Transport::SSH.new(node_ipv4_admin_net_ip, 'root', ssh_options, {}, config)
|
140
|
+
convergence_strategy = Chef::Provisioning::ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
|
141
|
+
Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport, convergence_strategy)
|
142
|
+
end
|
143
|
+
|
144
|
+
def create_ssh_transport(machine_spec)
|
145
|
+
crowbar_ssh_config = crowbar_ssh_config_for(machine_spec)
|
146
|
+
hostname = crowbar_ssh_config['HostName']
|
147
|
+
username = crowbar_ssh_config['User']
|
148
|
+
ssh_options = {
|
149
|
+
:port => '22',
|
150
|
+
:auth_methods => ['publickey'],
|
151
|
+
#:user_known_hosts_file => crowbar_ssh_config['UserKnownHostsFile'],
|
152
|
+
:paranoid => false, #yes_or_no(vagrant_ssh_config['StrictHostKeyChecking']),
|
153
|
+
:keys => [ '$HOME/.ssh/id_rsa' ],
|
154
|
+
:keys_only => true
|
155
|
+
}
|
156
|
+
Chef::Provisioning::Transport::SSH.new(hostname, username, ssh_options, options, config) end
|
157
|
+
|
158
|
+
def ensure_deployment(to_deployment)
|
159
|
+
unless @crowbar.deployment_exists?(to_deployment)
|
160
|
+
@crowbar.deployment_create(to_deployment)
|
161
|
+
debug("Crowbar deployment '#{to_deployment}' does not exist... creating...")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def get_non_admin_nodes(names=[])
|
166
|
+
# get available nodes
|
167
|
+
from_deployment = ALLOCATE_DEPLOYMENT
|
168
|
+
pool = @crowbar.non_admin_nodes_in_deployment(from_deployment)
|
169
|
+
raise "No available non-admin nodes in pool '#{from_deployment}'" if !pool || pool.size == 0
|
170
|
+
#action_handler.report_progress "Pool size: #{pool.size}"
|
171
|
+
# make sure node name isn't taken
|
172
|
+
good_nodes = []
|
173
|
+
names.each do |name|
|
174
|
+
pool.each do |node|
|
175
|
+
if node['alias'] == name
|
176
|
+
debug "Node #{name} already exists, skipping."
|
177
|
+
break
|
178
|
+
end
|
179
|
+
good_nodes << node
|
180
|
+
end
|
181
|
+
end
|
182
|
+
return good_nodes
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
def set_node_and_bind_noderole(node,name,role,to_deployment,crowbar_options={})
|
187
|
+
debug("set_node_foundling #{node}")
|
188
|
+
node["alias"] = name
|
189
|
+
node["deployment"] = to_deployment
|
190
|
+
raise "Setting node #{node["alias"]} to deployment \"#{to_deployment}\" failed." unless @crowbar.set_node(node["id"], node)
|
191
|
+
#action_handler.report_progress "Crowbar node\'s deployment attirbute set to \"#{to_deployment}\"\n"
|
192
|
+
|
193
|
+
|
194
|
+
# bind the NodeRole if missing (eventually set the OS property)
|
195
|
+
bind = {:node=>node["id"], :role=>role, :deployment=>to_deployment}
|
196
|
+
# blindly add node role > we need to make this smarter and skip if unneeded
|
197
|
+
# query node for all its noderoles and skip if noderole is in finished state
|
198
|
+
node["node_role_id"] = @crowbar.bind_node_role(bind)
|
199
|
+
#action_handler.report_progress "Crowbar node #{node["id"]} noderole bound to #{TARGET_NODE_ROLE} deployment #{to_deployment} as noderole #{node["node_role_id"]}\n"
|
200
|
+
|
201
|
+
# set crowbar_options. they're attribs in crowbar
|
202
|
+
crowbar_options.each do |attrib, value|
|
203
|
+
#attribs = { "provisioner-target_os" => machine_options[:crowbar_options]['provisioner-target_os'] }
|
204
|
+
@crowbar.set_node_attrib( node["id"], attrib, value )
|
205
|
+
end
|
206
|
+
node
|
207
|
+
end
|
208
|
+
|
209
|
+
# debug messages
|
210
|
+
def debug(msg)
|
211
|
+
Chef::Log.debug msg
|
212
|
+
end
|
213
|
+
|
214
|
+
# Allocate many machines simultaneously
|
215
|
+
def allocate_machines(action_handler, specs_and_options, parallelizer)
|
216
|
+
to_deployment = READY_DEPLOYMENT
|
217
|
+
|
218
|
+
# check for ready deployment
|
219
|
+
ensure_deployment(to_deployment)
|
220
|
+
# can the deployment be set to proposed?
|
221
|
+
@crowbar.propose_deployment(to_deployment)
|
222
|
+
|
223
|
+
#private_key = get_private_key('bootstrapkey')
|
224
|
+
servers = []
|
225
|
+
server_names = []
|
226
|
+
specs_and_options.each do |machine_spec, machine_options|
|
227
|
+
if !machine_spec.location
|
228
|
+
servers << [ machine_spec.name, machine_options ]
|
229
|
+
server_names << machine_spec.name
|
230
|
+
# skip name collisions
|
231
|
+
# add nodes to ready deployment
|
232
|
+
# Tell the cloud API to spin them all up at once
|
233
|
+
action_handler.perform_action "Allocating servers #{server_names.join(',')} from the cloud" do
|
234
|
+
role = TARGET_NODE_ROLE
|
235
|
+
node = get_non_admin_nodes([machine_spec.name])[0]
|
236
|
+
server = set_node_and_bind_noderole(node,machine_spec.name,role,to_deployment,machine_options[:crowbar_options])
|
237
|
+
server_id = server["id"]
|
238
|
+
debug "allocate server_id = #{server_id}"
|
239
|
+
machine_spec.location = {
|
240
|
+
'driver_url' => driver_url,
|
241
|
+
'driver_version' => Chef::Provisioning::CrowbarDriver::VERSION,
|
242
|
+
'server_id' => server_id,
|
243
|
+
'node_role_id' => server["node_role_id"]
|
244
|
+
}
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
# commit deployment
|
251
|
+
@crowbar.commit_deployment(to_deployment)
|
252
|
+
end
|
253
|
+
|
254
|
+
# follow getready process to allocate nodes
|
255
|
+
def allocate_node(name, machine_options, action_handler)
|
256
|
+
|
257
|
+
role = TARGET_NODE_ROLE
|
258
|
+
to_deployment = READY_DEPLOYMENT
|
259
|
+
|
260
|
+
ensure_deployment(to_deployment)
|
261
|
+
|
262
|
+
@crowbar.propose_deployment(to_deployment)
|
263
|
+
|
264
|
+
my_node = get_non_admin_nodes([name])[0]
|
265
|
+
#puts(my_node)
|
266
|
+
set_node_and_bind_noderole(my_node,name,role,to_deployment,machine_options[:crowbar_options])
|
267
|
+
|
268
|
+
@crowbar.commit_deployment(to_deployment)
|
269
|
+
|
270
|
+
# at this point Crowbar will bring up the node in the background
|
271
|
+
# we can return the node handle to the user
|
272
|
+
my_node
|
273
|
+
|
274
|
+
end
|
275
|
+
private
|
276
|
+
|
277
|
+
end # Class
|
278
|
+
end
|
279
|
+
end # Module
|
280
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright 2014, Rob Hirschfeld
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
class Chef
|
16
|
+
module Provisioning
|
17
|
+
module CrowbarDriver
|
18
|
+
VERSION = '0.1.0'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Copyright 2014, Rob Hirschfeld
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'chef/provisioning/crowbar_driver/driver'
|
16
|
+
|
17
|
+
Chef::Provisioning.register_driver_class("crowbar", Chef::Provisioning::CrowbarDriver::Driver)
|
data/lib/crowbar/core.rb
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
# Copyright 2014, Rob Hirschfeld
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require 'rubygems'
|
16
|
+
require 'httparty'
|
17
|
+
require 'json'
|
18
|
+
|
19
|
+
|
20
|
+
class Crowbar
|
21
|
+
include HTTParty
|
22
|
+
|
23
|
+
API_BASE = '/api/v2'
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
def initialize(url = "http://127.0.0.1:3000", u = "crowbar", p = "crowbar")
|
28
|
+
@url = url + API_BASE
|
29
|
+
self.class.digest_auth u, p
|
30
|
+
self.class.base_uri @url
|
31
|
+
debug "initialize #{@url}"
|
32
|
+
end
|
33
|
+
|
34
|
+
@llevel = :info
|
35
|
+
def log_level(level)
|
36
|
+
@llevel = level
|
37
|
+
debug("incoming #{level} instance #{@llevel}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def debug(msg)
|
41
|
+
puts "\nCROWBAR #{@llevel}: #{msg}" if @llevel == :debug
|
42
|
+
end
|
43
|
+
|
44
|
+
# debug_output $stderr
|
45
|
+
format :json
|
46
|
+
|
47
|
+
#http://192.168.222.6:3000/api/v2/nodes/1/attribs/provisioner-access_keys
|
48
|
+
def add_sshkey(sshkey)
|
49
|
+
res = self.class.get("/nodes/1/attribs/provisioner-access_keys")
|
50
|
+
if res.code != 200
|
51
|
+
raise("Could not get sshkey on admin node #{res.code} #{res.message}")
|
52
|
+
end
|
53
|
+
debug res
|
54
|
+
res = self.class.put("/nodes/1/attribs/provisioner-access_keys", :body => sshkey)
|
55
|
+
if res.code != 200
|
56
|
+
raise("Could not put sshkey on admin node #{res.code} #{res.message}")
|
57
|
+
end
|
58
|
+
return res
|
59
|
+
end
|
60
|
+
|
61
|
+
def deployment_create(name, parent_id=1)
|
62
|
+
data = { :name => name, :parent_id => parent_id }
|
63
|
+
res = self.class.post("/deployments", :body => data)
|
64
|
+
if res.code != 200
|
65
|
+
raise("Could not create deployment #{name}. #{res.code} #{res.message}")
|
66
|
+
end
|
67
|
+
res
|
68
|
+
end
|
69
|
+
|
70
|
+
def commit_deployment(name)
|
71
|
+
deployment_set(name,"commit")
|
72
|
+
end
|
73
|
+
|
74
|
+
def propose_deployment(name)
|
75
|
+
deployment_set(name,"propose")
|
76
|
+
end
|
77
|
+
|
78
|
+
def deployment_set(name,state)
|
79
|
+
res = self.class.put("/deployments/#{name}/#{state}")
|
80
|
+
if res.code != 200
|
81
|
+
raise("Could not set deployment #{name} to #{state}. #{res.code} #{res.message}")
|
82
|
+
end
|
83
|
+
return res
|
84
|
+
end
|
85
|
+
|
86
|
+
def deployment_exists?(name)
|
87
|
+
res = self.class.get("/deployments/#{name}")
|
88
|
+
debug("res code deployment exists #{res.code}")
|
89
|
+
return false unless res.code == 200
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
def node_exists?(name)
|
94
|
+
res = self.class.get("/nodes/#{name}")
|
95
|
+
debug("res code node exists #{res.code}")
|
96
|
+
return false unless res.code == 200
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
def non_admin_nodes_in_deployment(name, attrs={})
|
101
|
+
#attrs = {'x-return-attributes' => '["admin"]' }
|
102
|
+
n = nodes_in_deployment(name,attrs)
|
103
|
+
n.reject{ |e| e["admin"] == true } || []
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def find_node_in_deployment(node,deployment,attrs=[])
|
108
|
+
res = self.class.get("/deployments/#{deployment}/nodes", :headers => {'x-return-attributes' => "#{attrs.to_json}" } )
|
109
|
+
res.index{|e|e["name"] == node || e["id"] == node}
|
110
|
+
end
|
111
|
+
|
112
|
+
def nodes_in_deployment(name,attrs={})
|
113
|
+
self.class.get("/deployments/#{name}/nodes", :headers => attrs )
|
114
|
+
end
|
115
|
+
|
116
|
+
def set_deployment_to_proposed(name)
|
117
|
+
self.class.put("/deployments/#{name}/propose")
|
118
|
+
end
|
119
|
+
|
120
|
+
def ssh_private_key(name)
|
121
|
+
res = self.class.get("nodes/#{name}/attribs/#{attrib}")
|
122
|
+
if res.code != 200
|
123
|
+
raise("Could not get node \"#{name}\" ssh keys")
|
124
|
+
end
|
125
|
+
res
|
126
|
+
end
|
127
|
+
|
128
|
+
def node_status(id)
|
129
|
+
res = self.class.get("http://127.0.0.1:3000/api/status/nodes/#{id}" )
|
130
|
+
if res.code != 200
|
131
|
+
raise("Could not get node status #{res.code} #{res.message}")
|
132
|
+
end
|
133
|
+
res
|
134
|
+
end
|
135
|
+
|
136
|
+
def node_alive?(node_id)
|
137
|
+
n = node(node_id, ['alive'])
|
138
|
+
n["alive"]
|
139
|
+
end
|
140
|
+
|
141
|
+
def node_ready(node_id,node_role_id)
|
142
|
+
# get noderole state == 0 and runcount >= 1
|
143
|
+
# get node alive = true
|
144
|
+
nr = self.class.get("/node_roles/#{node_role_id}")
|
145
|
+
if nr["state"] == 0 && nr["run_count"] >= 1 && node_alive?(node_id)
|
146
|
+
return true
|
147
|
+
else
|
148
|
+
return false
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def set_node(id, data)
|
153
|
+
res = self.class.put("/nodes/#{id}", :body => data)
|
154
|
+
if res.code != 200
|
155
|
+
raise("Could not update node #{res.code} #{res.message}")
|
156
|
+
end
|
157
|
+
return res
|
158
|
+
end
|
159
|
+
|
160
|
+
def node(id,attrs=[])
|
161
|
+
my_head = {}
|
162
|
+
attrs.size > 0 && my_head = { :headers => {'x-return-attributes' => attrs.to_json } }
|
163
|
+
res = self.class.get("/nodes/#{id}", my_head )
|
164
|
+
if res.code != 200
|
165
|
+
raise("Could not get node \"#{id}\" #{res.code} #{res.message}")
|
166
|
+
end
|
167
|
+
res
|
168
|
+
end
|
169
|
+
|
170
|
+
def node_attrib(id,attrib)
|
171
|
+
res = self.class.get("/nodes/#{id}/attribs/#{attrib}")
|
172
|
+
if res.code != 200
|
173
|
+
raise("Could not get node \"#{id}\" attrib #{attrib} - #{res.code} #{res.message}")
|
174
|
+
end
|
175
|
+
return res
|
176
|
+
end
|
177
|
+
|
178
|
+
def deployment(id,attrs={})
|
179
|
+
res = self.class.get("/deployments/#{id}", :headers => attrs )
|
180
|
+
if res.code != 200
|
181
|
+
raise("Could not get deployment \"#{id}\" #{res.code} #{res.message}")
|
182
|
+
end
|
183
|
+
return res
|
184
|
+
end
|
185
|
+
|
186
|
+
def power(name,action)
|
187
|
+
res = self.class.put("/nodes/#{name}/power?poweraction=#{action}")
|
188
|
+
if res.code != 200
|
189
|
+
raise("Could not power #{action} node #{name}")
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def set_node_role_attrib(nr_id, attrib, value)
|
194
|
+
res = self.class.put("/node_roles/#{nr_id}/attribs/#{attrib}", :body => { :value => "#{value}" } )
|
195
|
+
if res.code != 200
|
196
|
+
raise("Could not set node_role #{nr_id} attrib #{attrib} to value #{value}")
|
197
|
+
end
|
198
|
+
res
|
199
|
+
end
|
200
|
+
|
201
|
+
def set_node_attrib(n_id, attrib, value)
|
202
|
+
res = self.class.put("/nodes/#{n_id}/attribs/#{attrib}", :body => { :value => "#{value}" } )
|
203
|
+
if res.code != 200
|
204
|
+
raise("Could not set node #{n_id} attrib #{attrib} to value #{value}: code #{res.code} #{res.message}")
|
205
|
+
end
|
206
|
+
res
|
207
|
+
end
|
208
|
+
|
209
|
+
def bind_node_role(data)
|
210
|
+
raise("Count not bind role to node. #{data}") unless
|
211
|
+
res = self.class.post("/node_roles", :body => data)
|
212
|
+
res['id']
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
private
|
217
|
+
|
218
|
+
|
219
|
+
# # connect to the Crowbar API
|
220
|
+
# # this currently AUTHS every call, we need to optimize that so that we can reuse the auth tokens
|
221
|
+
# def authenticate(req,uri,data=nil)
|
222
|
+
#
|
223
|
+
# # build request
|
224
|
+
# request_headers={
|
225
|
+
# "Accept" => "application/json",
|
226
|
+
# "Content-Type" => "application/json"}
|
227
|
+
# #request_headers['x-return-attributes']=$attributes if $attributes
|
228
|
+
# # build URL
|
229
|
+
# uri = URI.parse(@url)
|
230
|
+
# uri.user= @username
|
231
|
+
# uri.password= @password
|
232
|
+
# # starting HTTP session
|
233
|
+
# res=nil
|
234
|
+
# Net::HTTP.start(uri.host, uri.port) {|http|
|
235
|
+
# http.read_timeout = 500
|
236
|
+
# r = http.new(uri.request_uri,request_headers)
|
237
|
+
# r.body = data if data
|
238
|
+
# res = http.request r
|
239
|
+
# debug "(a) return code: #{res.code}"
|
240
|
+
# debug "(a) return body: #{res.body}"
|
241
|
+
# debug "(a) return headers:"
|
242
|
+
# res.each_header do |h, v|
|
243
|
+
# debug "#{h}: #{v}"
|
244
|
+
# end
|
245
|
+
#
|
246
|
+
# if res['www-authenticate']
|
247
|
+
# debug "(a) uri: #{uri}"
|
248
|
+
# debug "(a) www-authenticate: #{res['www-authenticate']}"
|
249
|
+
# debug "(a) req-method: #{req::METHOD}"
|
250
|
+
# auth=Net::HTTP::DigestAuth.new.auth_header(uri,
|
251
|
+
# res['www-authenticate'],
|
252
|
+
# req::METHOD)
|
253
|
+
# r.add_field 'Authorization', auth
|
254
|
+
# res = http.request r
|
255
|
+
# end
|
256
|
+
# }
|
257
|
+
# res
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
# # Common data and debug handling.
|
261
|
+
# def go(verb,path,data=nil)
|
262
|
+
# uri = URI.parse(@url + API_BASE + path)
|
263
|
+
# # We want to give valid JSON to the API, so if we were
|
264
|
+
# # handed an array or a hash as the data to be messed with,
|
265
|
+
# # turn it into a blob of JSON.
|
266
|
+
# data = data.to_json if data.is_a?(Array) || data.is_a?(Hash)
|
267
|
+
# res = authenticate(verb,uri,data)
|
268
|
+
# debug "(#{verb}) hostname: #{uri.host}:#{uri.port}"
|
269
|
+
# debug "(#{verb}) request: #{uri.path}"
|
270
|
+
# debug "(#{verb}) data: #{data}"
|
271
|
+
# debug "(#{verb}) return code: #{res.code}"
|
272
|
+
# debug "(#{verb}) return body: #{res.body}"
|
273
|
+
# [ JSON.parse(res.body), res.code.to_i ]
|
274
|
+
# end
|
275
|
+
#
|
276
|
+
#
|
277
|
+
|
278
|
+
|
279
|
+
end
|
280
|
+
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chef-provisioning-crowbar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Judd Maltin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: chef
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '11.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '11.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: chef-provisioning
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.15'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.15'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: httparty
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Crowbar is an open-source, multi-purpose node deployment tool.
|
70
|
+
email: judd@newgoliath.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files:
|
74
|
+
- README.md
|
75
|
+
- LICENSE
|
76
|
+
files:
|
77
|
+
- LICENSE
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- lib/chef/provisioning/crowbar_driver/driver.rb
|
81
|
+
- lib/chef/provisioning/crowbar_driver/version.rb
|
82
|
+
- lib/chef/provisioning/driver_init/crowbar.rb
|
83
|
+
- lib/crowbar/core.rb
|
84
|
+
homepage: https://github.com/newgoliath/chef-provisioning-crowbar
|
85
|
+
licenses:
|
86
|
+
- Apache 2
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.4.1
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: Driver for creating Crowbar servers in Chef Provisioning.
|
108
|
+
test_files: []
|
109
|
+
has_rdoc:
|