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 ADDED
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ pkg/*
4
+ vendor/*
5
+ *.swp
6
+ *~
7
+ \#*
8
+ /Gemfile.lock
data/CHANGELOG ADDED
@@ -0,0 +1,8 @@
1
+ 0.2.0 2013-08-09
2
+
3
+ - Adding delete vApp feature
4
+ - Changed the syntax for the command line interface
5
+
6
+ 0.1.0 2013-08-07
7
+
8
+ - Introducing vcloud-box-spinner gem (Initial Version)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in vcloud-box-spinner.gemspec
4
+ gemspec
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
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + '/../lib'
4
+ require 'provisioner/cli'
5
+
6
+ $:.unshift File.dirname(__FILE__) + '/../lib'
7
+
8
+ Excon.defaults[:ssl_verify_peer] = false
9
+ EXCON_DEBUG = true
10
+
11
+ Provisioner::CLI.new(ARGV).execute
data/docs/hacking.md ADDED
@@ -0,0 +1,7 @@
1
+ # Contribute to this project
2
+
3
+ * To run tests
4
+
5
+ bundle exec rake spec
6
+
7
+ * In case you adding major changes bump up the version file `lib/provisioner/version.rb`
@@ -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,5 @@
1
+ #!/bin/bash -x
2
+ set -e
3
+ bundle install --path "${HOME}/bundles/${JOB_NAME}"
4
+ bundle exec rake spec
5
+ bundle exec rake publish_gem --trace
@@ -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,5 @@
1
+ module Provisioner
2
+ class BlankProvisioner < Provisioner
3
+ include ComputeNode
4
+ end
5
+ 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