beaker-google 0.3.0 → 0.5.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.
@@ -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