vcloud-box-spinner 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/CHANGELOG +8 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +79 -0
- data/Rakefile +16 -0
- data/bin/vcloud-box-spinner +11 -0
- data/docs/hacking.md +7 -0
- data/docs/json_formats.md +65 -0
- data/docs/uuids.md +41 -0
- data/jenkins.sh +5 -0
- data/lib/fog/vcloud/compute/server_ready.rb +16 -0
- data/lib/fog/vcloud/compute/shared.rb +30 -0
- data/lib/provisioner/blank_provisioner.rb +5 -0
- data/lib/provisioner/cli.rb +151 -0
- data/lib/provisioner/compute_action/create.rb +176 -0
- data/lib/provisioner/compute_action/delete.rb +29 -0
- data/lib/provisioner/compute_node.rb +14 -0
- data/lib/provisioner/errors.rb +6 -0
- data/lib/provisioner/provisioner.rb +134 -0
- data/lib/provisioner/version.rb +3 -0
- data/lib/vcloud_box_provisioner.rb +31 -0
- data/spec/fog/vcloud/compute/shared_spec.rb +64 -0
- data/spec/provisioner/cli_spec.rb +170 -0
- data/spec/provisioner/compute_node_spec.rb +19 -0
- data/spec/provisioner/provisioner_spec.rb +37 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/test_data/machine.json +5 -0
- data/spec/test_data/org.json +18 -0
- data/vcloud-box-spinner.gemspec +34 -0
- metadata +213 -0
data/.gitignore
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
Copyright (c) 2013 singhgarima
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
12
|
+
copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
18
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
19
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
20
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# vCloud Provisioner
|
2
|
+
|
3
|
+
This is a wrapper around the vCloud director API that should allow for easy
|
4
|
+
provisioning of VMs.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
gem install vcloud-box-spinner
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
You should be able to do `vcloud-box-spinner --help`
|
13
|
+
|
14
|
+
Usage: vcloud-box-spinner [options] <org_config> <machine_config>
|
15
|
+
|
16
|
+
Provision a machine described by the JSON template `machine_config` in the vCloud organisation
|
17
|
+
described in the JSON config file `org_config`
|
18
|
+
|
19
|
+
e.g. vcloud-box-spinner -u username orgs/staging.json machines/frontend-1.json
|
20
|
+
|
21
|
+
-u, --user=USERNAME vCloud username
|
22
|
+
-p, --password=PASSWORD vCloud password
|
23
|
+
-F, --ssh-config=FILENAME SSH config file(s) to use (can be specified multiple times)
|
24
|
+
-s, --setup-script=SETUP-SCRIPT path to setup script that should run after machine is brought up
|
25
|
+
-d, --debug Enable debugging output
|
26
|
+
-v, --verbose Enable verbose output
|
27
|
+
-h, --help Show usage instructions
|
28
|
+
|
29
|
+
To provision a machine you will need to specify at least two JSON files:
|
30
|
+
|
31
|
+
1. A JSON config file which tells the provisioner about the vCloud
|
32
|
+
organisation into which it is to provision a vApp
|
33
|
+
2. A JSON config file which defines the machine-specific setup
|
34
|
+
|
35
|
+
Options:
|
36
|
+
|
37
|
+
- `user` is the username on your "vmware vcloud director" page
|
38
|
+
(usually in the top right corner).
|
39
|
+
- `setup-script` allows you to pass a script file path (shell), which
|
40
|
+
would be loaded as guest customization script. The purpose of
|
41
|
+
providing this option, is to let user do some basic bootstraping.
|
42
|
+
The script is not for the purpose of encouraging configuration
|
43
|
+
management and that should be done separately. A particular example
|
44
|
+
of how you can use the script is - You can set ssh configuration for
|
45
|
+
a user(eg ci), which can ssh in the system later and run the config
|
46
|
+
management script/tool.
|
47
|
+
On how to write this script please refer the following links:
|
48
|
+
|
49
|
+
- [Understand Guest OS Customisation](http://pubs.vmware.com/vcd-51/index.jsp?topic=%2Fcom.vmware.vcloud.users.doc_51%2FGUID-BB682E4D-DCD7-4936-A665-0B0FBD6F0EB5.html)
|
50
|
+
- [Example of scripts](http://pubs.vmware.com/vcd-51/index.jsp?topic=%2Fcom.vmware.vcloud.users.doc_51%2FGUID-724EB7B5-5C97-4A2F-897F-B27F1D4226C7.html)
|
51
|
+
|
52
|
+
The best way to understand the formats of the json files, read the docs
|
53
|
+
[here](/docs/json_formats.md)
|
54
|
+
|
55
|
+
Once you have an org and machine config, you can invoke the provisioner as
|
56
|
+
follows:
|
57
|
+
|
58
|
+
vcloud-box-spinner -u username -p password org_config.json machine_config.json
|
59
|
+
|
60
|
+
## Hacking
|
61
|
+
|
62
|
+
refer [here](/docs/hacking.md)
|
63
|
+
|
64
|
+
### Testing
|
65
|
+
|
66
|
+
You can run the tests with:
|
67
|
+
|
68
|
+
bundle exec rake
|
69
|
+
|
70
|
+
## Known Issues
|
71
|
+
|
72
|
+
- We are using fog ruby gem, as a wrapper around vCloud API. The fog gem currently hasn't released fix for "case
|
73
|
+
insensitivity for Set-Cookie headers"
|
74
|
+
[here](https://github.com/singhgarima/fog/commit/b7f8db97b15f1dc48541f5f2781f70fb2b743267). The fix till then is,
|
75
|
+
in your Gemfile, add the following lines
|
76
|
+
|
77
|
+
gem 'fog',
|
78
|
+
:git => 'git://github.com/fog/fog.git',
|
79
|
+
:ref => '3f034ccce030cff32e867f57482387d249b4da90'
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'gem_publisher'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |task|
|
7
|
+
task.pattern = FileList['spec/**/*_spec.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :spec
|
11
|
+
|
12
|
+
desc "Publish gem to RubyGems.org"
|
13
|
+
task :publish_gem do |t|
|
14
|
+
gem = GemPublisher.publish_if_updated("vcloud-box-spinner.gemspec", :rubygems)
|
15
|
+
puts "Published #{gem}" if gem
|
16
|
+
end
|
data/docs/hacking.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Organisation & Machine JSON files
|
2
|
+
|
3
|
+
## Tree structure
|
4
|
+
|
5
|
+
.
|
6
|
+
├── machines
|
7
|
+
│ └── tester
|
8
|
+
│ ├── machine-1.json
|
9
|
+
│ └── machine-2.json
|
10
|
+
├── orgs
|
11
|
+
├── org1.json
|
12
|
+
└── org2.json
|
13
|
+
|
14
|
+
## Organisation JSON file format
|
15
|
+
|
16
|
+
Each organisation JSON file contains key value pair to represent meta data for
|
17
|
+
the same. An example is as follows
|
18
|
+
|
19
|
+
{
|
20
|
+
"default": {
|
21
|
+
"template_name": "<catalog-item-name>",
|
22
|
+
"host": "<api-vendor-endpoint>",
|
23
|
+
"platform": "<platform-name>",
|
24
|
+
"organisation": "<org-name>",
|
25
|
+
"catalog_items": {
|
26
|
+
"<catalog-item-name": "https://api.vcd.portal.skyscapecloud.com/api/catalogItem/<catalog-item-uuid>"
|
27
|
+
}
|
28
|
+
},
|
29
|
+
"<vdc-ref-name/zone>": {
|
30
|
+
"default_vdc": "https://api.vcd.portal.skyscapecloud.com/api/vdc/<vdc-uuid>",
|
31
|
+
"network_name": "<network-name>",
|
32
|
+
"network_uri": "https://api.vcd.portal.skyscapecloud.com/api/network/<network-uuid>",
|
33
|
+
"vdc_id": "<vdc-uuid>"
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
* catalog is a terminology used by vcloud to represent templates used as base
|
38
|
+
images
|
39
|
+
* org-name is the name of organisation on which you would be bringing up the
|
40
|
+
machines.
|
41
|
+
* vdc-ref-name/zone, is an non vcloud specific term, which we use to map the
|
42
|
+
machines to a particular network. You would see the reference to the value
|
43
|
+
in machine JSON file.
|
44
|
+
* default_vdc, is the herf to vdc network.
|
45
|
+
* network-name, can be found out from vcloud UI `Adminstration -> Your VDC -> Org VDC Networks -> the network you would use it for`
|
46
|
+
|
47
|
+
To find various uuids, please refer [here](/docs/uuids.md)
|
48
|
+
|
49
|
+
Note: We would be removing platform from the settings, as it is currently only
|
50
|
+
used to set facter variables on the newly set up machine.
|
51
|
+
|
52
|
+
## Machine JSON file format
|
53
|
+
|
54
|
+
Each machine JSON file contain key value pair to represent machine specific
|
55
|
+
meta data
|
56
|
+
|
57
|
+
{
|
58
|
+
"zone": "<vdc-ref-name/zone>",
|
59
|
+
"vm_name": "vm-machine-name",
|
60
|
+
"ip": "<internal-ip-addr>"
|
61
|
+
}
|
62
|
+
|
63
|
+
* zone, is the reference to <vdc-ref-name/zone> used in organisation json
|
64
|
+
* vm-machine-name, is the name of vApp
|
65
|
+
* ip is the internal ip addr that would be used.
|
data/docs/uuids.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Finding various UUIDs needed by the JSON files
|
2
|
+
|
3
|
+
## Catalog Item UUID
|
4
|
+
|
5
|
+
* Authorization:
|
6
|
+
|
7
|
+
curl -v -X POST -d '' -H "Accept: application/*+xml;version=5.1" -u "username@org-name:password" "https://vendor-api-endpoint/api/sessions"
|
8
|
+
|
9
|
+
- The above returns x-vcloud-authorization token
|
10
|
+
- The abpve request returns xml, which contains the reference to the organisation details url, similar to below
|
11
|
+
|
12
|
+
<Link rel="down" type="application/vnd.vmware.vcloud.org+xml" name="org-name" href="https://vendor-api-endpoint/api/org/{org-uuid}"/>
|
13
|
+
|
14
|
+
* Get org details:
|
15
|
+
|
16
|
+
curl -v --insecure -H "x-vcloud-authorization: <token>" -H "Accept: application/*+xml;version=5.1" https://vendor-api-endpoint/api/org/{org-uuid}
|
17
|
+
|
18
|
+
- The above returns catalog details. (In vcloud you have a catalog which is a group pf catalogItems i.e templates)
|
19
|
+
|
20
|
+
<Link rel="down" type="application/vnd.vmware.vcloud.catalog+xml" name="Default" href="https://vendor-api-endpoint/api/catalog/{catalog-uuid}"/>
|
21
|
+
|
22
|
+
* Get catalog items
|
23
|
+
|
24
|
+
curl -v --insecure -H "x-vcloud-authorization: {token}" -H "Accept: application/*+xml;version=5.1" https://vendor-api-endpoint/api/catalog/{catalog-uuid}
|
25
|
+
|
26
|
+
- The above returns all the catalogItems in the catalog, you can choose the desired one
|
27
|
+
|
28
|
+
<CatalogItem type="application/vnd.vmware.vcloud.catalogItem+xml" name="{catalog-item-name}" href="https://vendor-api-endpoint/api/catalogItem/{catalog-item-uuid}"/>
|
29
|
+
|
30
|
+
|
31
|
+
## Network UUID
|
32
|
+
|
33
|
+
* Authorization: (same as describe in above steps)
|
34
|
+
|
35
|
+
* Get org details:
|
36
|
+
|
37
|
+
curl -v --insecure -H "x-vcloud-authorization: <token>" -H "Accept: application/*+xml;version=5.1" https://vendor-api-endpoint/api/org/{org-uuid}
|
38
|
+
|
39
|
+
- The above returns network details, choose the one you need on basis of name
|
40
|
+
|
41
|
+
<Link rel="down" type="application/vnd.vmware.vcloud.orgNetwork+xml" name="{network-name}" href="https://vendor-api-endpoint/api/network/{network-uuid}"/>
|
data/jenkins.sh
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'fog/vcloud/models/compute/server'
|
2
|
+
module Fog
|
3
|
+
module Vcloud
|
4
|
+
class Compute
|
5
|
+
class Server < Fog::Vcloud::Model
|
6
|
+
def ready?
|
7
|
+
reload_status
|
8
|
+
running_tasks = tasks && tasks.flatten.any? do |task|
|
9
|
+
task.kind_of?(Hash) && (task[:status] == 'running' && task[:Progress] != '100')
|
10
|
+
end
|
11
|
+
status != '0' && !running_tasks
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'fog/vcloud/compute'
|
3
|
+
require 'fog/vcloud/requests/compute/instantiate_vapp_template'
|
4
|
+
|
5
|
+
module Fog::Vcloud::Compute::Shared
|
6
|
+
private
|
7
|
+
def generate_instantiate_vapp_template_request(options)
|
8
|
+
xml = ::Builder::XmlMarkup.new
|
9
|
+
xml.InstantiateVAppTemplateParams(xmlns.merge!(:name => options[:name], :"xml:lang" => "en")) {
|
10
|
+
xml.Description(options[:description])
|
11
|
+
xml.InstantiationParams {
|
12
|
+
if options[:network_uri]
|
13
|
+
# TODO - implement properly
|
14
|
+
xml.NetworkConfigSection {
|
15
|
+
xml.ovf :Info
|
16
|
+
xml.NetworkConfig(:networkName => options[:network_name]) {
|
17
|
+
xml.Configuration {
|
18
|
+
xml.ParentNetwork(:href => options[:network_uri])
|
19
|
+
xml.FenceMode 'bridged'
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
}
|
25
|
+
# The template
|
26
|
+
xml.Source(:href => options[:template_uri])
|
27
|
+
xml.AllEULAsAccepted("true")
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'optparse'
|
3
|
+
require 'provisioner/errors'
|
4
|
+
require 'highline/import'
|
5
|
+
require 'vcloud_box_provisioner'
|
6
|
+
|
7
|
+
module Provisioner
|
8
|
+
class CLI
|
9
|
+
def self.defaults
|
10
|
+
{
|
11
|
+
:debug => false,
|
12
|
+
:log_level => 5,
|
13
|
+
:memory => 4096,
|
14
|
+
:num_cores => 2,
|
15
|
+
:num_servers => 1,
|
16
|
+
:platform => "production",
|
17
|
+
:ssh_config => true, # if not specified, use system defaults
|
18
|
+
}.freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.process(options = {})
|
22
|
+
# The template must specify a zone so we know where to look in the
|
23
|
+
# organisation config
|
24
|
+
begin
|
25
|
+
zone = options[:machine_metadata].fetch(:zone)
|
26
|
+
rescue KeyError
|
27
|
+
raise ConfigurationError, "The machine configuration doesn't specify " +
|
28
|
+
"a zone (Maybe you've put machine metadata and org config in the wrong order?)"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Internal defaults
|
32
|
+
res = self.defaults.dup
|
33
|
+
# vCloud config defaults
|
34
|
+
org_config = options.delete(:org_config)
|
35
|
+
res.merge!(org_config.fetch(:default, {}))
|
36
|
+
# vCloud zone defaults
|
37
|
+
res.merge!(org_config.fetch(zone.to_sym, {}))
|
38
|
+
# Machine metadata options
|
39
|
+
res.merge!(options.delete(:machine_metadata))
|
40
|
+
# Command line options
|
41
|
+
res.merge!(options)
|
42
|
+
|
43
|
+
unless res.include? :catalog_id
|
44
|
+
begin
|
45
|
+
template_name = res.fetch(:template_name)
|
46
|
+
catalog_id = res.fetch(:catalog_items).fetch(template_name.to_sym)
|
47
|
+
rescue KeyError
|
48
|
+
raise ConfigurationError, 'You must specify catalog_id OR (catalog_items AND template_name)'
|
49
|
+
end
|
50
|
+
res[:catalog_id] = catalog_id
|
51
|
+
end
|
52
|
+
|
53
|
+
res
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def initialize( args )
|
58
|
+
@args = args
|
59
|
+
end
|
60
|
+
|
61
|
+
def execute
|
62
|
+
options = {}
|
63
|
+
|
64
|
+
optparser = OptionParser.new do |o|
|
65
|
+
|
66
|
+
o.banner = "Usage: #{File.basename($0)} [options] <action>"
|
67
|
+
|
68
|
+
o.separator ""
|
69
|
+
o.separator "Provision a machine described by the JSON template `machine_metadata` in the vCloud organisation"
|
70
|
+
o.separator "described in the JSON config file `org_config`"
|
71
|
+
o.separator ""
|
72
|
+
o.separator "e.g. vcloud-box-spinner -u johndoe -o orgs/staging.json -m machines/frontend-1.json create"
|
73
|
+
o.separator ""
|
74
|
+
o.separator "[Available actions]:"
|
75
|
+
o.separator " #{Provisioner::AVAILABLE_ACTIONS.join(', ')}"
|
76
|
+
o.separator ""
|
77
|
+
o.separator "[Available options]:"
|
78
|
+
|
79
|
+
o.on("-u", "--user", "=USERNAME", "vCloud username") do |v|
|
80
|
+
options[:user] = v
|
81
|
+
end
|
82
|
+
|
83
|
+
o.on("-p", "--password", "=PASSWORD", "vCloud password") do |v|
|
84
|
+
options[:password] = v
|
85
|
+
end
|
86
|
+
|
87
|
+
o.on("-F", "--ssh-config", "=FILENAME", "SSH config file(s) to use (can be specified multiple times)") do |v|
|
88
|
+
options[:ssh_config] ||= []
|
89
|
+
options[:ssh_config].push(v)
|
90
|
+
end
|
91
|
+
|
92
|
+
options[:org_config] = {}
|
93
|
+
o.on("-o", "--org-config", "=ORG-CONFIG-JSON",
|
94
|
+
"The organisation configuration json file path") do |v|
|
95
|
+
options[:org_config] = JSON.parse(File.read(v), :symbolize_names => true)
|
96
|
+
end
|
97
|
+
|
98
|
+
options[:machine_metadata] = {}
|
99
|
+
o.on("-m", "--machine-config", "=METADATA",
|
100
|
+
"The machine configuration json file path") do |v|
|
101
|
+
options[:machine_metadata] = JSON.parse(File.read(v), :symbolize_names => true)
|
102
|
+
end
|
103
|
+
|
104
|
+
o.on('-s', '--setup-script', "=SETUP-SCRIPT", "path to setup script that should run after machine is brought up") do |v|
|
105
|
+
options[:setup_script] = v
|
106
|
+
end
|
107
|
+
|
108
|
+
o.on("-d", "--debug", "Enable debugging output") do
|
109
|
+
options[:debug] = true
|
110
|
+
end
|
111
|
+
|
112
|
+
o.on("-v", "--verbose", "Enable verbose output") do
|
113
|
+
options[:log_level] = 0
|
114
|
+
end
|
115
|
+
|
116
|
+
o.on_tail("-h", "--help", "Show usage instructions") do
|
117
|
+
puts o
|
118
|
+
exit
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
begin
|
123
|
+
optparser.parse!(@args)
|
124
|
+
|
125
|
+
if @args.length != 1
|
126
|
+
raise ConfigurationError, "#{File.basename($0)} takes one argument"
|
127
|
+
end
|
128
|
+
|
129
|
+
action = @args[0]
|
130
|
+
|
131
|
+
if options[:user].nil? then
|
132
|
+
options[:user] = ask("vCloud username: ")
|
133
|
+
end
|
134
|
+
|
135
|
+
if options[:password].nil? then
|
136
|
+
options[:password] = ask("vCloud password: ") { |q| q.echo = false }
|
137
|
+
end
|
138
|
+
|
139
|
+
provisioner_opts = self.class.process(options)
|
140
|
+
|
141
|
+
provisioner = VcloudBoxProvisioner.build provisioner_opts
|
142
|
+
provisioner.execute(action)
|
143
|
+
rescue OptionParser::InvalidArgument, ConfigurationError => e
|
144
|
+
$stderr.puts "Error: #{e}"
|
145
|
+
$stderr.puts
|
146
|
+
$stderr.puts optparser
|
147
|
+
exit 1
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module Provisioner
|
2
|
+
module ComputeAction
|
3
|
+
module Create
|
4
|
+
def user_data_files
|
5
|
+
[File.expand_path(options[:setup_script])]
|
6
|
+
end
|
7
|
+
private :user_data_files
|
8
|
+
|
9
|
+
def user_data
|
10
|
+
user_data_files.map { |f|
|
11
|
+
File.read(f)
|
12
|
+
}.join("\n")
|
13
|
+
end
|
14
|
+
private :user_data
|
15
|
+
|
16
|
+
def provision(name, options)
|
17
|
+
logger.debug "User data for #{name}: #{user_data}"
|
18
|
+
server = compute.servers.create(
|
19
|
+
:vdc_uri => options[:vdc_id],
|
20
|
+
:catalog_item_uri => options[:catalog_id],
|
21
|
+
:name => options[:vm_name],
|
22
|
+
:network_uri => options[:network_uri],
|
23
|
+
:network_name => options[:network_name],
|
24
|
+
:user_data => user_data,
|
25
|
+
:connection_options => {
|
26
|
+
:ssl_verify_peer => false,
|
27
|
+
:omit_default_port => true
|
28
|
+
}
|
29
|
+
)
|
30
|
+
notify "Waiting for server to come up", name
|
31
|
+
server.wait_for { server.ready? }
|
32
|
+
server
|
33
|
+
end
|
34
|
+
private :provision
|
35
|
+
|
36
|
+
def modify_xml(url, mime_type)
|
37
|
+
connection = compute.servers.service
|
38
|
+
xml = Nokogiri::XML(connection.request(:uri => url).body)
|
39
|
+
yield xml
|
40
|
+
connection.request(:uri => url,
|
41
|
+
:expects => 202,
|
42
|
+
:method => 'PUT',
|
43
|
+
:body => xml.to_s,
|
44
|
+
:headers => {'Content-Type' => mime_type})
|
45
|
+
end
|
46
|
+
private :modify_xml
|
47
|
+
|
48
|
+
def update_guest_customization_options(server, options)
|
49
|
+
logger.debug "server attributes: #{server.attributes}"
|
50
|
+
customization_options = compute.servers.service.get_customization_options(server.attributes[:children][:href]).body
|
51
|
+
guest_customization_section = customization_options[:GuestCustomizationSection]
|
52
|
+
|
53
|
+
response = modify_xml(guest_customization_section[:href], guest_customization_section[:type]) do |xml|
|
54
|
+
xml.at_css('ComputerName').content = options[:vm_name]
|
55
|
+
if xml.at_css('CustomizationScript').nil?
|
56
|
+
xml.at_css('ComputerName').before("<CustomizationScript>#{CGI.escapeHTML(user_data)}</CustomizationScript>\n")
|
57
|
+
else
|
58
|
+
xml.at_css('CustomizationScript').content = user_data
|
59
|
+
end
|
60
|
+
logger.debug(xml.to_xml)
|
61
|
+
end
|
62
|
+
|
63
|
+
wait_for_task(server, extract_task_uri(response))
|
64
|
+
server.wait_for { server.ready? }
|
65
|
+
end
|
66
|
+
private :update_guest_customization_options
|
67
|
+
|
68
|
+
def update_machine_resources(server, options)
|
69
|
+
update(server, "cpu", options[:num_cores])
|
70
|
+
update(server, "memory", options[:memory])
|
71
|
+
|
72
|
+
server.wait_for { server.ready? }
|
73
|
+
end
|
74
|
+
private :update_machine_resources
|
75
|
+
|
76
|
+
def update(server, resource_type, value)
|
77
|
+
virtual_hardware_section_links = server.attributes[:children]['ovf:VirtualHardwareSection'.to_sym][:Link]
|
78
|
+
edit_link = virtual_hardware_section_links.select { |item| item[:href].include?(resource_type) && item[:rel] == "edit" }.first
|
79
|
+
|
80
|
+
response = modify_xml(edit_link[:href], edit_link[:type]) do |xml|
|
81
|
+
xml.at_xpath('//rasd:VirtualQuantity').content = value
|
82
|
+
end
|
83
|
+
|
84
|
+
wait_for_task(server, extract_task_uri(response))
|
85
|
+
end
|
86
|
+
private :update
|
87
|
+
|
88
|
+
def extract_task_uri(response)
|
89
|
+
response_xml = Nokogiri::XML(response.body)
|
90
|
+
response_xml.at_css("Task").attributes['href'].value
|
91
|
+
end
|
92
|
+
private :extract_task_uri
|
93
|
+
|
94
|
+
def wait_for_task(server, task_uri)
|
95
|
+
connection = compute.servers.service
|
96
|
+
|
97
|
+
server.wait_for do
|
98
|
+
puts "... "
|
99
|
+
task = Nokogiri::XML(connection.request(:uri => task_uri, :expects => 200).body)
|
100
|
+
task.at_css("Task").attributes['status'].value == 'success'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
private :wait_for_task
|
104
|
+
|
105
|
+
def update_network_connection_options(server, options)
|
106
|
+
network_connection_section = server.attributes[:children][:NetworkConnectionSection][:Link]
|
107
|
+
logger.debug "server attributes: #{server.attributes}"
|
108
|
+
logger.debug "NetworkConnectionSection #{network_connection_section}"
|
109
|
+
|
110
|
+
response = modify_xml(network_connection_section[:href], network_connection_section[:type]) do |xml|
|
111
|
+
if options[:ip]
|
112
|
+
if xml.at_css('IpAddress').nil?
|
113
|
+
xml.at_css('IsConnected').before("<IpAddress>#{options[:ip]}</IpAddress>\n")
|
114
|
+
else
|
115
|
+
xml.at_css('IpAddress').content = options[:ip]
|
116
|
+
end
|
117
|
+
xml.at_css('IpAddressAllocationMode').content = 'MANUAL'
|
118
|
+
else
|
119
|
+
xml.at_css('IpAddressAllocationMode').content = 'POOL'
|
120
|
+
end
|
121
|
+
xml.at_css('NetworkConnection')[:network] = options[:network_name]
|
122
|
+
xml.at_css('IsConnected').content = 'true'
|
123
|
+
end
|
124
|
+
|
125
|
+
wait_for_task(server, extract_task_uri(response))
|
126
|
+
server.wait_for { server.ready? }
|
127
|
+
end
|
128
|
+
private :update_network_connection_options
|
129
|
+
|
130
|
+
def power_on(server)
|
131
|
+
connection = compute.servers.service
|
132
|
+
power_on_uri = server.links.find {|link| link[:rel] == 'power:powerOn' }[:href]
|
133
|
+
connection.request(:uri => power_on_uri, :method => "POST", :expects => 202)
|
134
|
+
end
|
135
|
+
private :power_on
|
136
|
+
|
137
|
+
def wait_for_vms_to_appear(server, options)
|
138
|
+
100.times.each do |x|
|
139
|
+
logger.debug("waiting for vm to spin up...")
|
140
|
+
if server.attributes[:children] && server.attributes[:children][:href]
|
141
|
+
return
|
142
|
+
end
|
143
|
+
sleep 1
|
144
|
+
end
|
145
|
+
|
146
|
+
if !server.attributes[:children] || !server.attributes[:children][:href]
|
147
|
+
abort "vm didn't properly spin up"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
private :wait_for_vms_to_appear
|
151
|
+
|
152
|
+
def launch_server name
|
153
|
+
super
|
154
|
+
server = provision(name, options)
|
155
|
+
|
156
|
+
wait_for_vms_to_appear(server, options)
|
157
|
+
|
158
|
+
update_guest_customization_options(server, options)
|
159
|
+
update_network_connection_options(server, options)
|
160
|
+
update_machine_resources(server, options)
|
161
|
+
|
162
|
+
power_on(server)
|
163
|
+
|
164
|
+
server
|
165
|
+
end
|
166
|
+
|
167
|
+
def launch_servers
|
168
|
+
super
|
169
|
+
end
|
170
|
+
|
171
|
+
def prepare_run
|
172
|
+
super
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|