vcloud-box-spinner 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|