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.
- 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
|