beaker-google 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +25 -0
- data/.simplecov +9 -0
- data/Gemfile +27 -0
- data/LICENSE +202 -0
- data/README.md +22 -0
- data/Rakefile +107 -0
- data/beaker-google.gemspec +38 -0
- data/bin/beaker-google +32 -0
- data/google_compute_engine.md +41 -0
- data/lib/beaker-google/version.rb +3 -0
- data/lib/beaker/hypervisor/google.rb +4 -0
- data/lib/beaker/hypervisor/google_compute.rb +164 -0
- data/lib/beaker/hypervisor/google_compute_helper.rb +577 -0
- metadata +212 -0
data/bin/beaker-google
ADDED
@@ -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,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
|