beaker-google 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,37 +1,52 @@
1
1
  require 'time'
2
2
 
3
3
  module Beaker
4
-
5
4
  # Beaker support for the Google Compute Engine.
6
5
  class GoogleCompute < Beaker::Hypervisor
7
-
8
6
  SLEEPWAIT = 5
9
7
 
10
8
  # Hours before an instance is considered a zombie
11
9
  ZOMBIE = 3
12
10
 
13
11
  # Do some reasonable sleuthing on the SSH public key for GCE
14
- def find_google_ssh_public_key
15
- keyfile = ENV.fetch('BEAKER_gce_ssh_public_key', File.join(ENV['HOME'], '.ssh', 'google_compute_engine.pub'))
16
12
 
17
- if @options[:gce_ssh_public_key] && !File.exist?(keyfile)
18
- keyfile = @options[:gce_ssh_public_key]
19
- end
20
-
21
- raise("Could not find GCE Public SSH Key at '#{keyfile}'") unless File.exist?(keyfile)
13
+ ##
14
+ # Try to find the private ssh key file
15
+ #
16
+ # @return [String] The file path for the private key file
17
+ #
18
+ # @raise [Error] if the private key can not be found
19
+ def find_google_ssh_private_key
20
+ private_keyfile = ENV.fetch('BEAKER_gce_ssh_public_key',
21
+ File.join(ENV.fetch('HOME', nil), '.ssh', 'google_compute_engine'))
22
+ private_keyfile = @options[:gce_ssh_private_key] if @options[:gce_ssh_private_key] && !File.exist?(private_keyfile)
23
+ raise("Could not find GCE Private SSH key at '#{keyfile}'") unless File.exist?(private_keyfile)
24
+ @options[:gce_ssh_private_key] = private_keyfile
25
+ private_keyfile
26
+ end
22
27
 
23
- return keyfile
28
+ ##
29
+ # Try to find the public key file based on the location of the private key or provided data
30
+ #
31
+ # @return [String] The file path for the public key file
32
+ #
33
+ # @raise [Error] if the public key can not be found
34
+ def find_google_ssh_public_key
35
+ private_keyfile = find_google_ssh_private_key
36
+ public_keyfile = private_keyfile << '.pub'
37
+ public_keyfile = @options[:gce_ssh_public_key] if @options[:gce_ssh_public_key] && !File.exist?(public_keyfile)
38
+ raise("Could not find GCE Public SSH key at '#{keyfile}'") unless File.exist?(public_keyfile)
39
+ @options[:gce_ssh_public_key] = public_keyfile
40
+ public_keyfile
24
41
  end
25
42
 
26
- # Create the array of metaData, each member being a hash with a :key and a
27
- # :value. Sets :department, :project and :jenkins_build_url.
28
- def format_metadata
29
- [ {:key => :department, :value => @options[:department]},
30
- {:key => :project, :value => @options[:project]},
31
- {:key => :jenkins_build_url, :value => @options[:jenkins_build_url]},
32
- {:key => :sshKeys, :value => "google_compute:#{File.read(find_google_ssh_public_key).strip}" }
33
- ].delete_if { |member| member[:value].nil? or member[:value].empty?}
43
+ # IP is the only way we can be sure to connect
44
+ # TODO: This isn't being called
45
+ # rubocop:disable Lint/UnusedMethodArgument
46
+ def connection_preference(host)
47
+ [:ip]
34
48
  end
49
+ # rubocop:enable Lint/UnusedMethodArgument
35
50
 
36
51
  # Create a new instance of the Google Compute Engine hypervisor object
37
52
  #
@@ -60,6 +75,7 @@ module Beaker
60
75
  def initialize(google_hosts, options)
61
76
  require 'beaker/hypervisor/google_compute_helper'
62
77
 
78
+ super
63
79
  @options = options
64
80
  @logger = options[:logger]
65
81
  @hosts = google_hosts
@@ -70,67 +86,147 @@ module Beaker
70
86
  # Create and configure virtual machines in the Google Compute Engine,
71
87
  # including their associated disks and firewall rules
72
88
  def provision
73
- attempts = @options[:timeout].to_i / SLEEPWAIT
74
89
  start = Time.now
75
-
76
90
  test_group_identifier = "beaker-#{start.to_i}-"
77
91
 
78
- # get machineType resource, used by all instances
79
- machineType = @gce_helper.get_machineType(start, attempts)
80
-
81
92
  # set firewall to open pe ports
82
- network = @gce_helper.get_network(start, attempts)
93
+ network = @gce_helper.get_network
94
+
83
95
  @firewall = test_group_identifier + generate_host_name
84
- @gce_helper.create_firewall(@firewall, network, start, attempts)
85
96
 
86
- @logger.debug("Created Google Compute firewall #{@firewall}")
97
+ @gce_helper.create_firewall(@firewall, network)
87
98
 
99
+ @logger.debug("Created Google Compute firewall #{@firewall}")
88
100
 
89
101
  @hosts.each do |host|
102
+
103
+ machine_type_name = ENV.fetch('BEAKER_gce_machine_type', host['gce_machine_type'])
104
+ raise "Must provide a machine type name in 'gce_machine_type'." if machine_type_name.nil?
105
+ # Get the GCE machine type object for this host
106
+ machine_type = @gce_helper.get_machine_type(machine_type_name)
107
+ raise "Unable to find machine type named #{machine_type_name} in region #{@compute.default_zone}" if machine_type.nil?
108
+
109
+ # Find the image to use to create the new VM.
110
+ # Either `image` or `family` must be set in the configuration. Accepted formats
111
+ # for the image and family:
112
+ # - {project}/{image}
113
+ # - {project}/{family}
114
+ # - {image}
115
+ # - {family}
116
+ #
117
+ # If a {project} is not specified, default to the project provided in the
118
+ # BEAKER_gce_project environment variable
90
119
  if host[:image]
91
- gplatform = host[:image]
92
- elsif host[:platform]
93
- gplatform = Platform.new(host[:platform])
120
+ image_selector = host[:image]
121
+ # Do we have a project name?
122
+ if %r{/}.match?(image_selector)
123
+ image_project, image_name = image_selector.split('/')[0..1]
124
+ else
125
+ image_project = @gce_helper.options[:gce_project]
126
+ image_name = image_selector
127
+ end
128
+ img = @gce_helper.get_image(image_project, image_name)
129
+ raise "Unable to find image #{image_name} from project #{image_project}" if img.nil?
130
+ elsif host[:family]
131
+ image_selector = host[:family]
132
+ # Do we have a project name?
133
+ if %r{/}.match?(image_selector)
134
+ image_project, family_name = image_selector.split('/')
135
+ else
136
+ image_project = @gce_helper.options[:gce_project]
137
+ family_name = image_selector
138
+ end
139
+ img = @gce_helper.get_latest_image_from_family(image_project, family_name)
140
+ raise "Unable to find image in family #{family_name} from project #{image_project}" if img.nil?
94
141
  else
95
- raise('You must specify either :image or :platform, or both as necessary')
142
+ raise('You must specify either :image or :family')
96
143
  end
97
144
 
98
- img = @gce_helper.get_latest_image(gplatform, start, attempts)
99
-
100
145
  unique_host_id = test_group_identifier + generate_host_name
101
146
 
102
- host['diskname'] = unique_host_id
103
- disk = @gce_helper.create_disk(host['diskname'], img, start, attempts)
104
- @logger.debug("Created Google Compute disk for #{host.name}: #{host['diskname']}")
147
+ boot_size = host['volume_size'] || img.disk_size_gb
148
+
149
+ # The boot disk is created as part of the instance creation
150
+ # TODO: Allow creation of other disks
151
+ # disk = @gce_helper.create_disk(host["diskname"], img, size)
152
+ # @logger.debug("Created Google Compute disk for #{host.name}: #{host["diskname"]}")
105
153
 
106
154
  # create new host name
107
155
  host['vmhostname'] = unique_host_id
108
- #add a new instance of the image
109
- instance = @gce_helper.create_instance(host['vmhostname'], img, machineType, disk, start, attempts)
156
+
157
+ # add a new instance of the image
158
+ operation = @gce_helper.create_instance(host['vmhostname'], img, machine_type, boot_size)
159
+ unless operation.error.nil?
160
+ raise "Unable to create Google Compute Instance #{host.name}: [#{operation.error.errors[0].code}] #{operation.error.errors[0].message}"
161
+ end
110
162
  @logger.debug("Created Google Compute instance for #{host.name}: #{host['vmhostname']}")
163
+ instance = @gce_helper.get_instance(host['vmhostname'])
164
+
165
+ # Make sure we have a non root/Adminsitor user to log in as
166
+ if host['user'] == "root" || host['user'] == "Administrator" || host['user'].empty?
167
+ initial_user = 'google_compute'
168
+ else
169
+ initial_user = host['user']
170
+ end
111
171
 
112
172
  # add metadata to instance, if there is any to set
113
- mdata = format_metadata
173
+ # mdata = format_metadata
174
+ # TODO: Set a configuration option for this to allow disabeling oslogin
175
+ mdata = [
176
+ {
177
+ key: 'ssh-keys',
178
+ value: "#{initial_user}:#{File.read(find_google_ssh_public_key).strip}"
179
+ },
180
+ # For now oslogin needs to be disabled as there's no way to log in as root and it would
181
+ # take too much work on beaker to add sudo support to everything
182
+ {
183
+ key: 'enable-oslogin',
184
+ value: 'FALSE'
185
+ },
186
+ ]
187
+
188
+ # Check for google's default windows images and turn on ssh if found
189
+ if image_project == "windows-cloud" || image_project == "windows-sql-cloud"
190
+ # Turn on SSH on GCP's default windows images
191
+ mdata << {
192
+ key: 'enable-windows-ssh',
193
+ value: 'TRUE',
194
+ }
195
+ mdata << {
196
+ key: 'sysprep-specialize-script-cmd',
197
+ value: 'googet -noconfirm=true update && googet -noconfirm=true install google-compute-engine-ssh',
198
+ }
199
+ # Some versions of windows don't seem to add the OpenSSH directory to the path which prevents scp from working
200
+ mdata << {
201
+ key: 'sysprep-specialize-script-ps1',
202
+ value: '[Environment]::SetEnvironmentVariable( "PATH", "$ENV:PATH;C:\Program Files\OpenSSH", [EnvironmentVariableTarget]::Machine )',
203
+ }
204
+ end
114
205
  unless mdata.empty?
115
- @gce_helper.setMetadata_on_instance(host['vmhostname'], instance['metadata']['fingerprint'],
116
- mdata,
117
- start, attempts)
206
+ # Add the metadata to the host
207
+ @gce_helper.set_metadata_on_instance(host['vmhostname'], mdata)
118
208
  @logger.debug("Added tags to Google Compute instance #{host.name}: #{host['vmhostname']}")
119
209
  end
120
210
 
121
- # get ip for this host
122
- host['ip'] = instance['networkInterfaces'][0]['accessConfigs'][0]['natIP']
123
-
124
- # configure ssh
125
- default_user = host['user']
126
- host['user'] = 'google_compute'
211
+ host['ip'] = instance.network_interfaces[0].access_configs[0].nat_ip
127
212
 
128
- copy_ssh_to_root(host, @options)
129
- enable_root_login(host, @options)
130
- host['user'] = default_user
213
+ # Add the new host to the firewall
214
+ @gce_helper.add_firewall_tag(@firewall, host['vmhostname'])
131
215
 
132
- # shut down connection, will reconnect on next exec
133
- host.close
216
+ if host['disable_root_ssh'] == true
217
+ @logger.info('Not enabling root ssh as disable_root_ssh is true')
218
+ else
219
+ real_user = host['user']
220
+ host['user'] = initial_user
221
+ # Set the ssh private key we need to use
222
+ host.options['ssh']['keys'] = [find_google_ssh_private_key]
223
+
224
+ copy_ssh_to_root(host, @options)
225
+ enable_root_login(host, @options)
226
+ host['user'] = real_user
227
+ # shut down connection, will reconnect on next exec
228
+ host.close
229
+ end
134
230
 
135
231
  @logger.debug("Instance ready: #{host['vmhostname']} for #{host.name}}")
136
232
  end
@@ -138,70 +234,13 @@ module Beaker
138
234
 
139
235
  # Shutdown and destroy virtual machines in the Google Compute Engine,
140
236
  # including their associated disks and firewall rules
141
- def cleanup()
142
- attempts = @options[:timeout].to_i / SLEEPWAIT
143
- start = Time.now
144
-
145
- @gce_helper.delete_firewall(@firewall, start, attempts)
237
+ def cleanup
238
+ @gce_helper.delete_firewall(@firewall)
146
239
 
147
240
  @hosts.each do |host|
148
- @gce_helper.delete_instance(host['vmhostname'], start, attempts)
241
+ # TODO: Delete any other disks attached during the instance creation
242
+ @gce_helper.delete_instance(host['vmhostname'])
149
243
  @logger.debug("Deleted Google Compute instance #{host['vmhostname']} for #{host.name}")
150
- @gce_helper.delete_disk(host['diskname'], start, attempts)
151
- @logger.debug("Deleted Google Compute disk #{host['diskname']} for #{host.name}")
152
- end
153
-
154
- end
155
-
156
- # Shutdown and destroy Google Compute instances (including their associated
157
- # disks and firewall rules) that have been alive longer than ZOMBIE hours.
158
- def kill_zombies(max_age = ZOMBIE)
159
- now = start = Time.now
160
- attempts = @options[:timeout].to_i / SLEEPWAIT
161
-
162
- # get rid of old instances
163
- instances = @gce_helper.list_instances(start, attempts)
164
- if instances
165
- instances.each do |instance|
166
- created = Time.parse(instance['creationTimestamp'])
167
- alive = (now - created )/60/60
168
- if alive >= max_age
169
- #kill it with fire!
170
- @logger.debug("Deleting zombie instance #{instance['name']}")
171
- @gce_helper.delete_instance( instance['name'], start, attempts )
172
- end
173
- end
174
- else
175
- @logger.debug("No zombie instances found")
176
- end
177
-
178
- # get rid of old disks
179
- disks = @gce_helper.list_disks(start, attempts)
180
- if disks
181
- disks.each do |disk|
182
- created = Time.parse(disk['creationTimestamp'])
183
- alive = (now - created )/60/60
184
- if alive >= max_age
185
-
186
- # kill it with fire!
187
- @logger.debug("Deleting zombie disk #{disk['name']}")
188
- @gce_helper.delete_disk( disk['name'], start, attempts )
189
- end
190
- end
191
- else
192
- @logger.debug("No zombie disks found")
193
- end
194
-
195
- # get rid of non-default firewalls
196
- firewalls = @gce_helper.list_firewalls( start, attempts)
197
-
198
- if firewalls && !firewalls.empty?
199
- firewalls.each do |firewall|
200
- @logger.debug("Deleting non-default firewall #{firewall['name']}")
201
- @gce_helper.delete_firewall( firewall['name'], start, attempts )
202
- end
203
- else
204
- @logger.debug("No zombie firewalls found")
205
244
  end
206
245
  end
207
246
  end