beaker-google 0.1.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.
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems' unless defined?(Gem)
4
+ require 'beaker-google'
5
+
6
+ VERSION_STRING =
7
+ "
8
+ _ .--.
9
+ ( ` )
10
+ beaker-google .-' `--,
11
+ _..----.. ( )`-.
12
+ .'_|` _|` _|( .__, )
13
+ /_| _| _| _( (_, .-'
14
+ ;| _| _| _| '-'__,--'`--'
15
+ | _| _| _| _| |
16
+ _ || _| _| _| _| %s
17
+ _( `--.\\_| _| _| _|/
18
+ .-' )--,| _| _|.`
19
+ (__, (_ ) )_| _| /
20
+ `-.__.\\ _,--'\\|__|__/
21
+ ;____;
22
+ \\YT/
23
+ ||
24
+ |\"\"|
25
+ '=='
26
+ "
27
+
28
+
29
+
30
+ puts BeakerGoogle::VERSION
31
+
32
+ exit 0
@@ -0,0 +1,41 @@
1
+ This option provisions instances from pre-existing Google Compute Engine images. Currently only supports debian-7 and centos-6 platforms (<a href = "https://developers.google.com/compute/docs/images#availableimages">Google Compute Engine available images</a>)
2
+
3
+ Pre-requisites:
4
+
5
+ * A Google Compute Engine project.
6
+ * An active service account to your Google Compute Engine project (<a href = "https://developers.google.com/drive/service-accounts">Service Accounts Documentation</a>), along with the following information:
7
+ * The service account private key file (named xxx-privatekey.p12).
8
+ * The service account email address (named xxx@developer.gserviceaccount.com).
9
+ * The service account password.
10
+ * A passwordless ssh keypair (<a href = "http://www.linuxproblem.org/art_9.html">SSH login without password</a>, <a href = "https://developers.google.com/compute/docs/console#sshkeys">Setting up Google Compute sshKeys metadata</a>).
11
+ * Name the pair `google-compute`
12
+ * Place the public key in your Google Compute Engine project metadata
13
+ * `key`: `sshKeys`
14
+ * `value` is the contents of your google_compute.pub with "google_compute:" prepended, eg:
15
+ <pre>
16
+ google_compute:ssh-rsaAAAABCCCCCCCCCCDDDDDeeeeeeeeFFFFFFFGGGGGGGGGGGGGGGGHHHHHHHHiiiiiiiiiiiJJJJJJJJKKKKKKKKKlllllllllllllllllllMNOppppppppppppppppppQRSTUV123456789101010101101010101011010101010110/ABCDEFGHIJKLMNOP+AB user@machine.local </pre>
17
+
18
+ ### example GCE hosts file###
19
+
20
+ HOSTS:
21
+ debian-7-master:
22
+ roles:
23
+ - master
24
+ - agent
25
+ - database
26
+ platform: debian-7-wheezy-xxx
27
+ hypervisor: google
28
+ centos-6-agent:
29
+ roles:
30
+ - agent
31
+ platform: centos-6-xxx
32
+ hypervisor: google
33
+ CONFIG:
34
+ nfs_server: none
35
+ consoleport: 443
36
+ gce_project: google-compute-project-name
37
+ gce_keyfile: /path/to/*****-privatekey.p12
38
+ gce_password: notasecret
39
+ gce_email: *********@developer.gserviceaccount.com
40
+
41
+ Google Compute cloud instances and disks are deleted after test runs, but it is up to the owner of the Google Compute Engine project to ensure that any zombie instances/disks are properly removed should Beaker fail during cleanup.
@@ -0,0 +1,3 @@
1
+ module BeakerGoogle
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'beaker/hypervisor/google_compute'
2
+
3
+ class Google < GoogleCompute
4
+ end
@@ -0,0 +1,164 @@
1
+ require 'time'
2
+
3
+ module Beaker
4
+ #Beaker support for the Google Compute Engine.
5
+ class GoogleCompute < Beaker::Hypervisor
6
+
7
+ SLEEPWAIT = 5
8
+ #number of hours before an instance is considered a zombie
9
+ ZOMBIE = 3
10
+
11
+ #Create the array of metaData, each member being a hash with a :key and a :value. Sets
12
+ #:department, :project and :jenkins_build_url.
13
+ def format_metadata
14
+ [ {:key => :department, :value => @options[:department]},
15
+ {:key => :project, :value => @options[:project]},
16
+ {:key => :jenkins_build_url, :value => @options[:jenkins_build_url]} ].delete_if { |member| member[:value].nil? or member[:value].empty?}
17
+ end
18
+
19
+ #Create a new instance of the Google Compute Engine hypervisor object
20
+ #@param [<Host>] google_hosts The array of google hosts to provision, may ONLY be of platforms /centos-6-.*/ and
21
+ # /debian-7-.*/. We currently only support the Google Compute provided templates.
22
+ #@param [Hash{Symbol=>String}] options The options hash containing configuration values
23
+ #@option options [String] :gce_project The Google Compute Project name to connect to
24
+ #@option options [String] :gce_keyfile The location of the Google Compute service account keyfile
25
+ #@option options [String] :gce_password The password for the Google Compute service account key
26
+ #@option options [String] :gce_email The email address for the Google Compute service account
27
+ #@option options [String] :gce_machine_type A Google Compute machine type used to create instances, defaults to n1-highmem-2
28
+ #@option options [Integer] :timeout The amount of time to attempt execution before quiting and exiting with failure
29
+ def initialize(google_hosts, options)
30
+ require 'beaker/hypervisor/google_compute_helper'
31
+ @options = options
32
+ @logger = options[:logger]
33
+ @hosts = google_hosts
34
+ @firewall = ''
35
+ @gce_helper = GoogleComputeHelper.new(options)
36
+ end
37
+
38
+ #Create and configure virtual machines in the Google Compute Engine, including their associated disks and firewall rules
39
+ #Currently ONLY supports Google Compute provided templates of CENTOS-6 and DEBIAN-7
40
+ def provision
41
+ try = 1
42
+ attempts = @options[:timeout].to_i / SLEEPWAIT
43
+ start = Time.now
44
+
45
+ #get machineType resource, used by all instances
46
+ machineType = @gce_helper.get_machineType(start, attempts)
47
+
48
+ #set firewall to open pe ports
49
+ network = @gce_helper.get_network(start, attempts)
50
+ @firewall = generate_host_name
51
+ @gce_helper.create_firewall(@firewall, network, start, attempts)
52
+
53
+ @logger.debug("Created Google Compute firewall #{@firewall}")
54
+
55
+
56
+ @hosts.each do |host|
57
+ gplatform = Platform.new(host[:image] || host[:platform])
58
+ img = @gce_helper.get_latest_image(gplatform, start, attempts)
59
+ host['diskname'] = generate_host_name
60
+ disk = @gce_helper.create_disk(host['diskname'], img, start, attempts)
61
+ @logger.debug("Created Google Compute disk for #{host.name}: #{host['diskname']}")
62
+
63
+ #create new host name
64
+ host['vmhostname'] = generate_host_name
65
+ #add a new instance of the image
66
+ instance = @gce_helper.create_instance(host['vmhostname'], img, machineType, disk, start, attempts)
67
+ @logger.debug("Created Google Compute instance for #{host.name}: #{host['vmhostname']}")
68
+
69
+ #add metadata to instance, if there is any to set
70
+ mdata = format_metadata
71
+ if not mdata.empty?
72
+ @gce_helper.setMetadata_on_instance(host['vmhostname'], instance['metadata']['fingerprint'],
73
+ mdata,
74
+ start, attempts)
75
+ @logger.debug("Added tags to Google Compute instance #{host.name}: #{host['vmhostname']}")
76
+ end
77
+
78
+ #get ip for this host
79
+ host['ip'] = instance['networkInterfaces'][0]['accessConfigs'][0]['natIP']
80
+
81
+ #configure ssh
82
+ default_user = host['user']
83
+ host['user'] = 'google_compute'
84
+
85
+ disable_se_linux(host, @options)
86
+ copy_ssh_to_root(host, @options)
87
+ enable_root_login(host, @options)
88
+ host['user'] = default_user
89
+
90
+ #shut down connection, will reconnect on next exec
91
+ host.close
92
+
93
+ @logger.debug("Instance ready: #{host['vmhostname']} for #{host.name}}")
94
+ end
95
+ end
96
+
97
+ #Shutdown and destroy virtual machines in the Google Compute Engine, including their associated disks and firewall rules
98
+ def cleanup()
99
+ attempts = @options[:timeout].to_i / SLEEPWAIT
100
+ start = Time.now
101
+
102
+ @gce_helper.delete_firewall(@firewall, start, attempts)
103
+
104
+ @hosts.each do |host|
105
+ @gce_helper.delete_instance(host['vmhostname'], start, attempts)
106
+ @logger.debug("Deleted Google Compute instance #{host['vmhostname']} for #{host.name}")
107
+ @gce_helper.delete_disk(host['diskname'], start, attempts)
108
+ @logger.debug("Deleted Google Compute disk #{host['diskname']} for #{host.name}")
109
+ end
110
+
111
+ end
112
+
113
+ #Shutdown and destroy Google Compute instances (including their associated disks and firewall rules)
114
+ #that have been alive longer than ZOMBIE hours.
115
+ def kill_zombies(max_age = ZOMBIE)
116
+ now = start = Time.now
117
+ attempts = @options[:timeout].to_i / SLEEPWAIT
118
+
119
+ #get rid of old instances
120
+ instances = @gce_helper.list_instances(start, attempts)
121
+ if instances
122
+ instances.each do |instance|
123
+ created = Time.parse(instance['creationTimestamp'])
124
+ alive = (now - created ) /60 /60
125
+ if alive >= max_age
126
+ #kill it with fire!
127
+ @logger.debug("Deleting zombie instance #{instance['name']}")
128
+ @gce_helper.delete_instance( instance['name'], start, attempts )
129
+ end
130
+ end
131
+ else
132
+ @logger.debug("No zombie instances found")
133
+ end
134
+ #get rid of old disks
135
+ disks = @gce_helper.list_disks(start, attempts)
136
+ if disks
137
+ disks.each do |disk|
138
+ created = Time.parse(disk['creationTimestamp'])
139
+ alive = (now - created ) /60 /60
140
+ if alive >= max_age
141
+ #kill it with fire!
142
+ @logger.debug("Deleting zombie disk #{disk['name']}")
143
+ @gce_helper.delete_disk( disk['name'], start, attempts )
144
+ end
145
+ end
146
+ else
147
+ @logger.debug("No zombie disks found")
148
+ end
149
+ #get rid of non-default firewalls
150
+ firewalls = @gce_helper.list_firewalls( start, attempts)
151
+
152
+ if firewalls and not firewalls.empty?
153
+ firewalls.each do |firewall|
154
+ @logger.debug("Deleting non-default firewall #{firewall['name']}")
155
+ @gce_helper.delete_firewall( firewall['name'], start, attempts )
156
+ end
157
+ else
158
+ @logger.debug("No zombie firewalls found")
159
+ end
160
+
161
+ end
162
+
163
+ end
164
+ end
@@ -0,0 +1,577 @@
1
+ require 'google/api_client'
2
+ require 'json'
3
+ require 'time'
4
+ require 'ostruct'
5
+
6
+ module Beaker
7
+ #Beaker helper module for doing API level Google Compute Engine interaction.
8
+ class GoogleComputeHelper
9
+
10
+ class GoogleComputeError < StandardError
11
+ end
12
+
13
+ SLEEPWAIT = 5
14
+
15
+ AUTH_URL = 'https://www.googleapis.com/auth/compute'
16
+ API_VERSION = 'v1'
17
+ BASE_URL = "https://www.googleapis.com/compute/#{API_VERSION}/projects/"
18
+ CENTOS_PROJECT = 'centos-cloud'
19
+ DEBIAN_PROJECT = 'debian-cloud'
20
+ DEFAULT_ZONE_NAME = 'us-central1-a'
21
+ DEFAULT_MACHINE_TYPE = 'n1-highmem-2'
22
+ DEFAULT_DISK_SIZE = 25
23
+
24
+ #Create a new instance of the Google Compute Engine helper object
25
+ #@param [Hash{Symbol=>String}] options The options hash containing configuration values
26
+ #@option options [String] :gce_project The Google Compute Project name to connect to
27
+ #@option options [String] :gce_keyfile The location of the Google Compute service account keyfile
28
+ #@option options [String] :gce_password The password for the Google Compute service account key
29
+ #@option options [String] :gce_email The email address for the Google Compute service account
30
+ #@option options [String] :gce_machine_type A Google Compute machine type used to create instances, defaults to n1-highmem-2
31
+ #@option options [Integer] :timeout The amount of time to attempt execution before quiting and exiting with failure
32
+ def initialize(options)
33
+ @options = options
34
+ @logger = options[:logger]
35
+ try = 1
36
+ attempts = @options[:timeout].to_i / SLEEPWAIT
37
+ start = Time.now
38
+
39
+ set_client(Beaker::Version::STRING)
40
+ set_compute_api(API_VERSION, start, attempts)
41
+
42
+ raise 'You must specify a gce_project for Google Compute Engine instances!' unless @options[:gce_project]
43
+ raise 'You must specify a gce_keyfile for Google Compute Engine instances!' unless @options[:gce_keyfile]
44
+ raise 'You must specify a gce_password for Google Compute Engine instances!' unless @options[:gce_password]
45
+ raise 'You must specify a gce_email for Google Compute Engine instances!' unless @options[:gce_email]
46
+
47
+ authenticate(@options[:gce_keyfile], @options[:gce_password], @options[:gce_email], start, attempts)
48
+ end
49
+
50
+ #Determines the default Google Compute zone based upon options and defaults
51
+ #@return The full URL to the default zone in which Google Compute requests will be sent
52
+ def default_zone
53
+ BASE_URL + @options[:gce_project] + '/global/zones/' + DEFAULT_ZONE_NAME
54
+ end
55
+
56
+ #Determines the default Google Compute network based upon defaults and options
57
+ #@return The full URL to the default network in which Google Compute instances will operate
58
+ def default_network
59
+ BASE_URL + @options[:gce_project] + '/global/networks/default'
60
+ end
61
+
62
+ #Determines the Google Compute project which contains bases instances of type name
63
+ #@param [String] name The platform type to search for
64
+ #@return The Google Compute project name
65
+ #@raise [Exception] If the provided platform type name is unsupported
66
+ def get_platform_project(name)
67
+ if name =~ /debian/
68
+ return DEBIAN_PROJECT
69
+ elsif name =~ /centos/
70
+ return CENTOS_PROJECT
71
+ else
72
+ raise "Unsupported platform for Google Compute Engine: #{name}"
73
+ end
74
+ end
75
+
76
+ #Create the Google APIClient object which will be used for accessing the Google Compute API
77
+ #@param version The version number of Beaker currently running
78
+ def set_client(version)
79
+ @client = Google::APIClient.new({:application_name => "Beaker", :application_version => version})
80
+ end
81
+
82
+ #Discover the currently active Google Compute API
83
+ #@param [String] version The version of the Google Compute API to discover
84
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
85
+ # further code execution attempts remain
86
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
87
+ #@raise [Exception] Raised if we fail to discover the Google Compute API, either through errors or running out of attempts
88
+ def set_compute_api version, start, attempts
89
+ try = (Time.now - start) / SLEEPWAIT
90
+ while try <= attempts
91
+ begin
92
+ @compute = @client.discovered_api('compute', version)
93
+ @logger.debug("Google Compute API discovered")
94
+ return
95
+ rescue => e
96
+ @logger.debug("Failed to discover Google Compute API")
97
+ if try >= attempts
98
+ raise e
99
+ end
100
+ end
101
+ try += 1
102
+ end
103
+ end
104
+
105
+ #Creates an authenticated connection to the Google Compute Engine API
106
+ #@param [String] keyfile The location of the Google Compute Service Account keyfile to use for authentication
107
+ #@param [String] password The password for the provided Google Compute Service Account key
108
+ #@param [String] email The email address of the Google Compute Service Account we are using to connect
109
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
110
+ # further code execution attempts remain
111
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
112
+ #@raise [Exception] Raised if we fail to create an authenticated connection to the Google Compute API, either through errors or running out of attempts
113
+ def authenticate(keyfile, password, email, start, attempts)
114
+ # OAuth authentication, using the service account
115
+ key = Google::APIClient::PKCS12.load_key(keyfile, password)
116
+ service_account = Google::APIClient::JWTAsserter.new(
117
+ email,
118
+ AUTH_URL,
119
+ key)
120
+ try = (Time.now - start) / SLEEPWAIT
121
+ while try <= attempts
122
+ begin
123
+ @client.authorization = service_account.authorize
124
+ @logger.debug("Authorized to use Google Compute")
125
+ return
126
+ rescue => e
127
+ @logger.debug("Failed to authorize to use Google Compute")
128
+ if try >= attempts
129
+ raise e
130
+ end
131
+ end
132
+ try += 1
133
+ end
134
+ end
135
+
136
+ #Executes a provided Google Compute request using a previously configured and authenticated Google Compute client connection
137
+ #@param [Hash] req A correctly formatted Google Compute request object
138
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
139
+ # further code execution attempts remain
140
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
141
+ #@raise [Exception] Raised if we fail to execute the request, either through errors or running out of attempts
142
+ def execute req, start, attempts
143
+ last_error = parsed = nil
144
+ try = (Time.now - start) / SLEEPWAIT
145
+ while try <= attempts
146
+ begin
147
+ result = @client.execute(req)
148
+ parsed = JSON.parse(result.body)
149
+ if not result.success?
150
+ error_code = parsed["error"] ? parsed["error"]["code"] : 0
151
+ if error_code == 404
152
+ raise GoogleComputeError, "Resource Not Found: #{result.body}"
153
+ elsif error_code == 400
154
+ raise GoogleComputeError, "Bad Request: #{result.body}"
155
+ else
156
+ raise GoogleComputeError, "Error attempting Google Compute API execute: #{result.body}"
157
+ end
158
+ end
159
+ return parsed
160
+ #retry errors
161
+ rescue Faraday::Error::ConnectionFailed => e
162
+ @logger.debug "ConnectionFailed attempting Google Compute execute command"
163
+ try += 1
164
+ last_error = e
165
+ end
166
+ end
167
+ #we only get down here if we've used up all our tries
168
+ raise last_error
169
+ end
170
+
171
+ #Determines the latest image available for the provided platform name. We currently only support debian-7 and centos-6 platforms.
172
+ #@param [String] platform The platform type to search for an instance of, must be one of /debian-7-.*/ or /centos-6-.*/
173
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
174
+ # further code execution attempts remain
175
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
176
+ #@return [Hash] The image hash of the latest, non-deprecated image for the provided platform
177
+ #@raise [Exception] Raised if we fail to execute the request, either through errors or running out of attempts
178
+ def get_latest_image(platform, start, attempts)
179
+ #use the platform version numbers instead of codenames
180
+ platform = platform.with_version_number
181
+ #break up my platform for information
182
+ platform_name, platform_version, platform_extra_info = platform.split('-', 3)
183
+ #find latest image to use
184
+ result = execute( image_list_req(get_platform_project(platform_name)), start, attempts )
185
+ images = result["items"]
186
+ #reject images of the wrong version of the given platform
187
+ images.delete_if { |image| image['name'] !~ /^#{platform_name}-#{platform_version}/}
188
+ #reject deprecated images
189
+ images.delete_if { |image| image['deprecated']}
190
+ #find a match based upon platform type
191
+ if images.length != 1
192
+ raise "Unable to find a single matching image for #{platform}, found #{images}"
193
+ end
194
+ images[0]
195
+ end
196
+
197
+ #Determines the Google Compute machineType object based upon the selected gce_machine_type option
198
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
199
+ # further code execution attempts remain
200
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
201
+ #@return [Hash] The machineType hash
202
+ #@raise [Exception] Raised if we fail get the machineType, either through errors or running out of attempts
203
+ def get_machineType(start, attempts)
204
+ execute( machineType_get_req, start, attempts )
205
+ end
206
+
207
+ #Determines the Google Compute network object in use for the current connection
208
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
209
+ # further code execution attempts remain
210
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
211
+ #@return [Hash] The network hash
212
+ #@raise [Exception] Raised if we fail get the network, either through errors or running out of attempts
213
+ def get_network(start, attempts)
214
+ execute( network_get_req, start, attempts)
215
+ end
216
+
217
+ #Determines a list of existing Google Compute instances
218
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
219
+ # further code execution attempts remain
220
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
221
+ #@return [Array[Hash]] The instances array of hashes
222
+ #@raise [Exception] Raised if we fail determine the list of existing instances, either through errors or running out of attempts
223
+ def list_instances(start, attempts)
224
+ instances = execute( instance_list_req(), start, attempts )
225
+ instances["items"]
226
+ end
227
+
228
+ #Determines a list of existing Google Compute disks
229
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
230
+ # further code execution attempts remain
231
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
232
+ #@return [Array[Hash]] The disks array of hashes
233
+ #@raise [Exception] Raised if we fail determine the list of existing disks, either through errors or running out of attempts
234
+ def list_disks(start, attempts)
235
+ disks = execute( disk_list_req(), start, attempts )
236
+ disks["items"]
237
+ end
238
+
239
+ #Determines a list of existing Google Compute firewalls
240
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
241
+ # further code execution attempts remain
242
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
243
+ #@return [Array[Hash]] The firewalls array of hashes
244
+ #@raise [Exception] Raised if we fail determine the list of existing firewalls, either through errors or running out of attempts
245
+ def list_firewalls(start, attempts)
246
+ result = execute( firewall_list_req(), start, attempts )
247
+ firewalls = result["items"]
248
+ firewalls.delete_if{|f| f['name'] =~ /default-allow-internal|default-ssh/}
249
+ firewalls
250
+ end
251
+
252
+ #Create a Google Compute firewall on the current connection
253
+ #@param [String] name The name of the firewall to create
254
+ #@param [Hash] network The Google Compute network hash in which to create the firewall
255
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
256
+ # further code execution attempts remain
257
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
258
+ #@raise [Exception] Raised if we fail create the firewall, either through errors or running out of attempts
259
+ def create_firewall(name, network, start, attempts)
260
+ execute( firewall_insert_req( name, network['selfLink'] ), start, attempts )
261
+ end
262
+
263
+ #Create a Google Compute disk on the current connection
264
+ #@param [String] name The name of the disk to create
265
+ #@param [Hash] img The Google Compute image to use for instance creation
266
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
267
+ # further code execution attempts remain
268
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
269
+ #@raise [Exception] Raised if we fail create the disk, either through errors or running out of attempts
270
+ def create_disk(name, img, start, attempts)
271
+ #create a new boot disk for this instance
272
+ disk = execute( disk_insert_req( name, img['selfLink'] ), start, attempts )
273
+
274
+ status = ''
275
+ try = (Time.now - start) / SLEEPWAIT
276
+ while status !~ /READY/ and try <= attempts
277
+ begin
278
+ disk = execute( disk_get_req( name ), start, attempts )
279
+ status = disk['status']
280
+ rescue GoogleComputeError => e
281
+ @logger.debug("Waiting for #{name} disk creation")
282
+ sleep(SLEEPWAIT)
283
+ end
284
+ try += 1
285
+ end
286
+ if status == ''
287
+ raise "Unable to create disk #{name}"
288
+ end
289
+ disk
290
+ end
291
+
292
+ #Create a Google Compute instance on the current connection
293
+ #@param [String] name The name of the instance to create
294
+ #@param [Hash] img The Google Compute image to use for instance creation
295
+ #@param [Hash] machineType The Google Compute machineType
296
+ #@param [Hash] disk The Google Compute disk to attach to the newly created instance
297
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
298
+ # further code execution attempts remain
299
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
300
+ #@raise [Exception] Raised if we fail create the instance, either through errors or running out of attempts
301
+ def create_instance(name, img, machineType, disk, start, attempts)
302
+ #add a new instance of the image
303
+ instance = execute( instance_insert_req( name, img['selfLink'], machineType['selfLink'], disk['selfLink'] ), start, attempts)
304
+ status = ''
305
+ try = (Time.now - start) / SLEEPWAIT
306
+ while status !~ /RUNNING/ and try <= attempts
307
+ begin
308
+ instance = execute( instance_get_req( name ), start, attempts )
309
+ status = instance['status']
310
+ rescue GoogleComputeError => e
311
+ @logger.debug("Waiting for #{name} instance creation")
312
+ sleep(SLEEPWAIT)
313
+ end
314
+ try += 1
315
+ end
316
+ if status == ''
317
+ raise "Unable to create instance #{name}"
318
+ end
319
+ instance
320
+ end
321
+
322
+ #Add key/value pairs to a Google Compute instance on the current connection
323
+ #@param [String] name The name of the instance to add metadata to
324
+ #@param [String] fingerprint A hash of the metadata's contents of the given instance
325
+ #@param [Array<Hash>] data An array of hashes. Each hash should have a key and a value.
326
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
327
+ # further code execution attempts remain
328
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
329
+ #@raise [Exception] Raised if we fail to add metadata, either through errors or running out of attempts
330
+ def setMetadata_on_instance(name, fingerprint, data, start, attempts)
331
+ zone_operation = execute( instance_setMetadata_req( name, fingerprint, data), start, attempts )
332
+ status = ''
333
+ try = (Time.now - start) / SLEEPWAIT
334
+ while status !~ /DONE/ and try <= attempts
335
+ begin
336
+ operation = execute( operation_get_req( zone_operation['name'] ), start, attempts )
337
+ status = operation['status']
338
+ rescue GoogleComputeError => e
339
+ @logger.debug("Waiting for tags to be added to #{name}")
340
+ sleep(SLEEPWAIT)
341
+ end
342
+ try += 1
343
+ end
344
+ if status == ''
345
+ raise "Unable to set metaData (#{tags.to_s}) on #{name}"
346
+ end
347
+ zone_operation
348
+ end
349
+
350
+ #Delete a Google Compute instance on the current connection
351
+ #@param [String] name The name of the instance to delete
352
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
353
+ # further code execution attempts remain
354
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
355
+ #@raise [Exception] Raised if we fail delete the instance, either through errors or running out of attempts
356
+ def delete_instance(name, start, attempts)
357
+ result = execute( instance_delete_req( name ), start, attempts )
358
+ #ensure deletion of instance
359
+ try = (Time.now - start) / SLEEPWAIT
360
+ while try <= attempts
361
+ begin
362
+ result = execute( instance_get_req( name ), start, attempts )
363
+ @logger.debug("Waiting for #{name} instance deletion")
364
+ sleep(SLEEPWAIT)
365
+ rescue GoogleComputeError => e
366
+ @logger.debug("#{name} instance deleted!")
367
+ return
368
+ end
369
+ try += 1
370
+ end
371
+ @logger.debug("#{name} instance was not removed before timeout, may still exist")
372
+ end
373
+
374
+ #Delete a Google Compute disk on the current connection
375
+ #@param [String] name The name of the disk to delete
376
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
377
+ # further code execution attempts remain
378
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
379
+ #@raise [Exception] Raised if we fail delete the disk, either through errors or running out of attempts
380
+ def delete_disk(name, start, attempts)
381
+ result = execute( disk_delete_req( name ), start, attempts )
382
+ #ensure deletion of disk
383
+ try = (Time.now - start) / SLEEPWAIT
384
+ while try <= attempts
385
+ begin
386
+ disk = execute( disk_get_req( name ), start, attempts )
387
+ @logger.debug("Waiting for #{name} disk deletion")
388
+ sleep(SLEEPWAIT)
389
+ rescue GoogleComputeError => e
390
+ @logger.debug("#{name} disk deleted!")
391
+ return
392
+ end
393
+ try += 1
394
+ end
395
+ @logger.debug("#{name} disk was not removed before timeout, may still exist")
396
+ end
397
+
398
+ #Delete a Google Compute firewall on the current connection
399
+ #@param [String] name The name of the firewall to delete
400
+ #@param [Integer] start The time when we started code execution, it is compared to Time.now to determine how many
401
+ # further code execution attempts remain
402
+ #@param [Integer] attempts The total amount of attempts to execute that we are willing to allow
403
+ #@raise [Exception] Raised if we fail delete the firewall, either through errors or running out of attempts
404
+ def delete_firewall(name, start, attempts)
405
+ result = execute( firewall_delete_req( name ), start, attempts )
406
+ #ensure deletion of disk
407
+ try = (Time.now - start) / SLEEPWAIT
408
+ while try <= attempts
409
+ begin
410
+ firewall = execute( firewall_get_req( name ), start, attempts )
411
+ @logger.debug("Waiting for #{name} firewall deletion")
412
+ sleep(SLEEPWAIT)
413
+ rescue GoogleComputeError => e
414
+ @logger.debug("#{name} firewall deleted!")
415
+ return
416
+ end
417
+ try += 1
418
+ end
419
+ @logger.debug("#{name} firewall was not removed before timeout, may still exist")
420
+ end
421
+
422
+
423
+ #Create a Google Compute list all images request
424
+ #@param [String] name The Google Compute project name to query
425
+ #@return [Hash] A correctly formatted Google Compute request hash
426
+ def image_list_req(name)
427
+ { :api_method => @compute.images.list,
428
+ :parameters => { 'project' => name } }
429
+ end
430
+
431
+ #Create a Google Compute list all disks request
432
+ #@return [Hash] A correctly formatted Google Compute request hash
433
+ def disk_list_req
434
+ { :api_method => @compute.disks.list,
435
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME } }
436
+ end
437
+
438
+ #Create a Google Compute get disk request
439
+ #@param [String] name The name of the disk to query for
440
+ #@return [Hash] A correctly formatted Google Compute request hash
441
+ def disk_get_req(name)
442
+ { :api_method => @compute.disks.get,
443
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'disk' => name } }
444
+ end
445
+
446
+ #Create a Google Compute disk delete request
447
+ #@param [String] name The name of the disk delete
448
+ #@return [Hash] A correctly formatted Google Compute request hash
449
+ def disk_delete_req(name)
450
+ { :api_method => @compute.disks.delete,
451
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'disk' => name } }
452
+ end
453
+
454
+ #Create a Google Compute disk create request
455
+ #@param [String] name The name of the disk to create
456
+ #@param [String] source The link to a Google Compute image to base the disk creation on
457
+ #@return [Hash] A correctly formatted Google Compute request hash
458
+ def disk_insert_req(name, source)
459
+ { :api_method => @compute.disks.insert,
460
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'sourceImage' => source },
461
+ :body_object => { 'name' => name, 'sizeGb' => DEFAULT_DISK_SIZE } }
462
+ end
463
+
464
+ #Create a Google Compute get firewall request
465
+ #@param [String] name The name of the firewall to query fo
466
+ #@return [Hash] A correctly formatted Google Compute request hash
467
+ def firewall_get_req(name)
468
+ { :api_method => @compute.firewalls.get,
469
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'firewall' => name } }
470
+ end
471
+
472
+ #Create a Google Compute insert firewall request, open ports 443, 8140 and 61613
473
+ #@param [String] name The name of the firewall to create
474
+ #@param [String] network The link to the Google Compute network to attach this firewall to
475
+ #@return [Hash] A correctly formatted Google Compute request hash
476
+ def firewall_insert_req(name, network)
477
+ { :api_method => @compute.firewalls.insert,
478
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME },
479
+ :body_object => { 'name' => name,
480
+ 'allowed'=> [ { 'IPProtocol' => 'tcp', "ports" => [ '443', '8140', '61613', '8080', '8081' ]} ],
481
+ 'network'=> network,
482
+ 'sourceRanges' => [ "0.0.0.0/0" ] } }
483
+ end
484
+
485
+ #Create a Google Compute delete firewall request
486
+ #@param [String] name The name of the firewall to delete
487
+ #@return [Hash] A correctly formatted Google Compute request hash
488
+ def firewall_delete_req(name)
489
+ { :api_method => @compute.firewalls.delete,
490
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'firewall' => name } }
491
+ end
492
+
493
+ #Create a Google Compute list firewall request
494
+ #@return [Hash] A correctly formatted Google Compute request hash
495
+ def firewall_list_req()
496
+ { :api_method => @compute.firewalls.list,
497
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME } }
498
+ end
499
+
500
+ #Create a Google Compute get network request
501
+ #@param [String] name (default) The name of the network to access information about
502
+ #@return [Hash] A correctly formatted Google Compute request hash
503
+ def network_get_req(name = 'default')
504
+ { :api_method => @compute.networks.get,
505
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'network' => name } }
506
+ end
507
+
508
+ #Create a Google Compute zone operation request
509
+ #@return [Hash] A correctly formatted Google Compute request hash
510
+ def operation_get_req(name)
511
+ { :api_method => @compute.zone_operations.get,
512
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'operation' => name } }
513
+ end
514
+
515
+ #Set tags on a Google Compute instance
516
+ #@param [Array<String>] data An array of tags to be added to an instance
517
+ #@return [Hash] A correctly formatted Google Compute request hash
518
+ def instance_setMetadata_req(name, fingerprint, data)
519
+ { :api_method => @compute.instances.set_metadata,
520
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'instance' => name },
521
+ :body_object => { 'kind' => 'compute#metadata',
522
+ 'fingerprint' => fingerprint,
523
+ 'items' => data }
524
+ }
525
+ end
526
+
527
+ #Create a Google Compute list instance request
528
+ #@return [Hash] A correctly formatted Google Compute request hash
529
+ def instance_list_req
530
+ { :api_method => @compute.instances.list,
531
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME } }
532
+ end
533
+
534
+ #Create a Google Compute get instance request
535
+ #@param [String] name The name of the instance to query for
536
+ #@return [Hash] A correctly formatted Google Compute request hash
537
+ def instance_get_req(name)
538
+ { :api_method => @compute.instances.get,
539
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'instance' => name } }
540
+ end
541
+
542
+ #Create a Google Compute instance delete request
543
+ #@param [String] name The name of the instance to delete
544
+ #@return [Hash] A correctly formatted Google Compute request hash
545
+ def instance_delete_req(name)
546
+ { :api_method => @compute.instances.delete,
547
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'instance' => name } }
548
+ end
549
+
550
+ #Create a Google Compute instance create request
551
+ #@param [String] name The name of the instance to create
552
+ #@param [String] image The link to the image to use for instance create
553
+ #@param [String] machineType The link to the type of Google Compute instance to create (indicates cpus and memory size)
554
+ #@param [String] disk The link to the disk to be used by the newly created instance
555
+ #@return [Hash] A correctly formatted Google Compute request hash
556
+ def instance_insert_req(name, image, machineType, disk)
557
+ { :api_method => @compute.instances.insert,
558
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME },
559
+ :body_object => { 'name' => name,
560
+ 'image' => image,
561
+ 'zone' => default_zone,
562
+ 'machineType' => machineType,
563
+ 'disks' => [ { 'source' => disk,
564
+ 'type' => 'PERSISTENT', 'boot' => 'true'} ],
565
+ 'networkInterfaces' => [ { 'accessConfigs' => [{ 'type' => 'ONE_TO_ONE_NAT', 'name' => 'External NAT' }],
566
+ 'network' => default_network } ] } }
567
+ end
568
+
569
+ #Create a Google Compute machineType get request
570
+ #@return [Hash] A correctly formatted Google Compute request hash
571
+ def machineType_get_req()
572
+ { :api_method => @compute.machine_types.get,
573
+ :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'machineType' => @options[:gce_machine_type] || DEFAULT_MACHINE_TYPE } }
574
+ end
575
+
576
+ end
577
+ end