beaker-google 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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