Class: Beaker::GoogleComputeHelper

Inherits:
Object
  • Object
show all
Defined in:
lib/beaker/hypervisor/google_compute_helper.rb

Overview

Beaker helper module for doing API level Google Compute Engine interaction.

Defined Under Namespace

Classes: GoogleComputeError

Constant Summary collapse

SLEEPWAIT =
5
AUTH_URL =
'https://www.googleapis.com/auth/compute'
API_VERSION =
'v1'
BASE_URL =
"https://www.googleapis.com/compute/#{API_VERSION}/projects/"
CENTOS_PROJECT =
'centos-cloud'
DEBIAN_PROJECT =
'debian-cloud'
DEFAULT_ZONE_NAME =
'us-central1-a'
DEFAULT_MACHINE_TYPE =
'n1-highmem-2'
DEFAULT_DISK_SIZE =
25

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ GoogleComputeHelper

Create a new instance of the Google Compute Engine helper object

Options Hash (options):

  • :gce_project (String)

    The Google Compute Project name to connect to

  • :gce_keyfile (String)

    The location of the Google Compute service account keyfile

  • :gce_password (String)

    The password for the Google Compute service account key

  • :gce_email (String)

    The email address for the Google Compute service account

  • :gce_machine_type (String)

    A Google Compute machine type used to create instances, defaults to n1-highmem-2

  • :timeout (Integer)

    The amount of time to attempt execution before quiting and exiting with failure



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 32

def initialize(options)
  @options = options
  @logger = options[:logger]
  try = 1
  attempts = @options[:timeout].to_i / SLEEPWAIT
  start = Time.now

  set_client(Beaker::Version::STRING)
  set_compute_api(API_VERSION, start, attempts)

  raise 'You must specify a gce_project for Google Compute Engine instances!' unless @options[:gce_project]
  raise 'You must specify a gce_keyfile for Google Compute Engine instances!' unless @options[:gce_keyfile]
  raise 'You must specify a gce_password for Google Compute Engine instances!' unless @options[:gce_password]
  raise 'You must specify a gce_email for Google Compute Engine instances!' unless @options[:gce_email]

  authenticate(@options[:gce_keyfile], @options[:gce_password], @options[:gce_email], start, attempts)
end

Instance Method Details

#authenticate(keyfile, password, email, start, attempts) ⇒ Object

Creates an authenticated connection to the Google Compute Engine API

Raises:

  • (Exception)

    Raised if we fail to create an authenticated connection to the Google Compute API, either through errors or running out of attempts



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 113

def authenticate(keyfile, password, email, start, attempts)
  # OAuth authentication, using the service account
  key = Google::APIClient::PKCS12.load_key(keyfile, password)
   = Google::APIClient::JWTAsserter.new(
      email,
      AUTH_URL,
      key)
  try = (Time.now - start) / SLEEPWAIT
  while try <= attempts
    begin
      @client.authorization = .authorize
      @logger.debug("Authorized to use Google Compute")
      return
    rescue => e
      @logger.debug("Failed to authorize to use Google Compute")
      if try >= attempts
        raise e
      end
    end
    try += 1
  end
end

#create_disk(name, img, start, attempts) ⇒ Object

Create a Google Compute disk on the current connection

Raises:

  • (Exception)

    Raised if we fail create the disk, either through errors or running out of attempts



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 270

def create_disk(name, img, start, attempts)
  #create a new boot disk for this instance
  disk = execute( disk_insert_req( name, img['selfLink'] ), start, attempts )

  status = ''
  try = (Time.now - start) / SLEEPWAIT
  while status !~ /READY/ and try <= attempts
    begin
      disk = execute( disk_get_req( name ), start, attempts )
      status = disk['status']
    rescue GoogleComputeError => e
      @logger.debug("Waiting for #{name} disk creation")
      sleep(SLEEPWAIT)
    end
    try += 1
  end
  if status == ''
    raise "Unable to create disk #{name}"
  end
  disk
end

#create_firewall(name, network, start, attempts) ⇒ Object

Create a Google Compute firewall on the current connection

Raises:

  • (Exception)

    Raised if we fail create the firewall, either through errors or running out of attempts



259
260
261
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 259

def create_firewall(name, network, start, attempts)
  execute( firewall_insert_req( name, network['selfLink'] ), start, attempts )
end

#create_instance(name, img, machineType, disk, start, attempts) ⇒ Object

Create a Google Compute instance on the current connection

Raises:

  • (Exception)

    Raised if we fail create the instance, either through errors or running out of attempts



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 301

def create_instance(name, img, machineType, disk, start, attempts)
  #add a new instance of the image
  instance = execute( instance_insert_req( name, img['selfLink'], machineType['selfLink'], disk['selfLink'] ), start, attempts)
  status = ''
  try = (Time.now - start) / SLEEPWAIT
  while status !~ /RUNNING/ and try <= attempts
    begin
      instance = execute( instance_get_req( name ), start, attempts )
      status = instance['status']
    rescue GoogleComputeError => e
      @logger.debug("Waiting for #{name} instance creation")
      sleep(SLEEPWAIT)
    end
    try += 1
  end
  if status == ''
    raise "Unable to create instance #{name}"
  end
  instance
end

#default_networkObject

Determines the default Google Compute network based upon defaults and options



58
59
60
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 58

def default_network
  BASE_URL + @options[:gce_project] + '/global/networks/default'
end

#default_zoneObject

Determines the default Google Compute zone based upon options and defaults



52
53
54
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 52

def default_zone
  BASE_URL + @options[:gce_project] + '/global/zones/' + DEFAULT_ZONE_NAME
end

#delete_disk(name, start, attempts) ⇒ Object

Delete a Google Compute disk on the current connection

Raises:

  • (Exception)

    Raised if we fail delete the disk, either through errors or running out of attempts



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 380

def delete_disk(name, start, attempts)
  result = execute( disk_delete_req( name ), start, attempts )
  #ensure deletion of disk
  try = (Time.now - start) / SLEEPWAIT
  while try <= attempts
    begin
      disk = execute( disk_get_req( name ), start, attempts )
      @logger.debug("Waiting for #{name} disk deletion")
      sleep(SLEEPWAIT)
    rescue GoogleComputeError => e
      @logger.debug("#{name} disk deleted!")
      return
    end
    try += 1
  end
  @logger.debug("#{name} disk was not removed before timeout, may still exist")
end

#delete_firewall(name, start, attempts) ⇒ Object

Delete a Google Compute firewall on the current connection

Raises:

  • (Exception)

    Raised if we fail delete the firewall, either through errors or running out of attempts



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 404

def delete_firewall(name, start, attempts)
  result = execute( firewall_delete_req( name ), start, attempts )
  #ensure deletion of disk
  try = (Time.now - start) / SLEEPWAIT
  while try <= attempts
    begin
      firewall = execute( firewall_get_req( name ), start, attempts )
      @logger.debug("Waiting for #{name} firewall deletion")
      sleep(SLEEPWAIT)
    rescue GoogleComputeError => e
      @logger.debug("#{name} firewall deleted!")
      return
    end
    try += 1
  end
  @logger.debug("#{name} firewall was not removed before timeout, may still exist")
end

#delete_instance(name, start, attempts) ⇒ Object

Delete a Google Compute instance on the current connection

Raises:

  • (Exception)

    Raised if we fail delete the instance, either through errors or running out of attempts



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 356

def delete_instance(name, start, attempts)
  result = execute( instance_delete_req( name ), start, attempts )
  #ensure deletion of instance
  try = (Time.now - start) / SLEEPWAIT
  while try <= attempts
    begin
      result = execute( instance_get_req( name ), start, attempts )
      @logger.debug("Waiting for #{name} instance deletion")
      sleep(SLEEPWAIT)
    rescue GoogleComputeError => e
      @logger.debug("#{name} instance deleted!")
      return
    end
    try += 1
  end
  @logger.debug("#{name} instance was not removed before timeout, may still exist")
end

#disk_delete_req(name) ⇒ Hash

Create a Google Compute disk delete request



449
450
451
452
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 449

def disk_delete_req(name)
  { :api_method  => @compute.disks.delete,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'disk' => name } }
end

#disk_get_req(name) ⇒ Hash

Create a Google Compute get disk request



441
442
443
444
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 441

def disk_get_req(name)
  { :api_method  => @compute.disks.get,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'disk' => name } }
end

#disk_insert_req(name, source) ⇒ Hash

Create a Google Compute disk create request



458
459
460
461
462
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 458

def disk_insert_req(name, source)
  { :api_method  => @compute.disks.insert,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'sourceImage' => source },
    :body_object => { 'name' => name, 'sizeGb' => DEFAULT_DISK_SIZE } }
end

#disk_list_reqHash

Create a Google Compute list all disks request



433
434
435
436
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 433

def disk_list_req
  { :api_method  => @compute.disks.list,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME } }
end

#execute(req, start, attempts) ⇒ Object

Executes a provided Google Compute request using a previously configured and authenticated Google Compute client connection

Raises:

  • (Exception)

    Raised if we fail to execute the request, either through errors or running out of attempts



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 142

def execute req, start, attempts
  last_error = parsed = nil
  try = (Time.now - start) / SLEEPWAIT
  while try <= attempts
    begin
      result = @client.execute(req)
      parsed = JSON.parse(result.body)
      if not result.success?
        error_code = parsed["error"] ? parsed["error"]["code"] : 0
        if error_code == 404
          raise GoogleComputeError, "Resource Not Found: #{result.body}"
        elsif error_code == 400
          raise GoogleComputeError, "Bad Request: #{result.body}"
        else
          raise GoogleComputeError, "Error attempting Google Compute API execute: #{result.body}"
        end
      end
      return parsed
    #retry errors
    rescue Faraday::Error::ConnectionFailed => e
      @logger.debug "ConnectionFailed attempting Google Compute execute command"
      try += 1
      last_error = e
    end
  end
  #we only get down here if we've used up all our tries
  raise last_error
end

#firewall_delete_req(name) ⇒ Hash

Create a Google Compute delete firewall request



488
489
490
491
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 488

def firewall_delete_req(name)
  { :api_method  => @compute.firewalls.delete,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'firewall' => name } }
end

#firewall_get_req(name) ⇒ Hash

Create a Google Compute get firewall request



467
468
469
470
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 467

def firewall_get_req(name)
  { :api_method  => @compute.firewalls.get,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'firewall' => name } }
end

#firewall_insert_req(name, network) ⇒ Hash

Create a Google Compute insert firewall request, open ports 443, 8140 and 61613



476
477
478
479
480
481
482
483
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 476

def firewall_insert_req(name, network)
  { :api_method  => @compute.firewalls.insert,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME },
    :body_object => { 'name' => name,
                      'allowed'=> [ { 'IPProtocol' => 'tcp', "ports" =>  [ '443', '8140', '61613', '8080', '8081' ]} ],
                      'network'=> network,
                      'sourceRanges' => [ "0.0.0.0/0" ] } }
end

#firewall_list_reqHash

Create a Google Compute list firewall request



495
496
497
498
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 495

def firewall_list_req()
  { :api_method  => @compute.firewalls.list,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME } }
end

#get_latest_image(platform, start, attempts) ⇒ Hash

Determines the latest image available for the provided platform name. We currently only support debian-7 and centos-6 platforms.

Raises:

  • (Exception)

    Raised if we fail to execute the request, either through errors or running out of attempts



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 178

def get_latest_image(platform, start, attempts)
  #use the platform version numbers instead of codenames
  platform = platform.with_version_number
  #break up my platform for information
  platform_name, platform_version, platform_extra_info = platform.split('-', 3)
  #find latest image to use
  result = execute( image_list_req(get_platform_project(platform_name)), start, attempts )
  images = result["items"]
  #reject images of the wrong version of the given platform
  images.delete_if { |image| image['name'] !~ /^#{platform_name}-#{platform_version}/}
  #reject deprecated images
  images.delete_if { |image| image['deprecated']}
  #find a match based upon platform type
  if images.length != 1
    raise "Unable to find a single matching image for #{platform}, found #{images}"
  end
  images[0]
end

#get_machineType(start, attempts) ⇒ Hash

Determines the Google Compute machineType object based upon the selected gce_machine_type option

Raises:

  • (Exception)

    Raised if we fail get the machineType, either through errors or running out of attempts



203
204
205
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 203

def get_machineType(start, attempts)
  execute( machineType_get_req, start, attempts )
end

#get_network(start, attempts) ⇒ Hash

Determines the Google Compute network object in use for the current connection

Raises:

  • (Exception)

    Raised if we fail get the network, either through errors or running out of attempts



213
214
215
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 213

def get_network(start, attempts)
  execute( network_get_req, start, attempts)
end

#get_platform_project(name) ⇒ Object

Determines the Google Compute project which contains bases instances of type name

Raises:

  • (Exception)

    If the provided platform type name is unsupported



66
67
68
69
70
71
72
73
74
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 66

def get_platform_project(name)
  if name =~ /debian/
    return DEBIAN_PROJECT
  elsif name =~ /centos/
    return CENTOS_PROJECT
  else
    raise "Unsupported platform for Google Compute Engine: #{name}"
  end
end

#image_list_req(name) ⇒ Hash

Create a Google Compute list all images request



426
427
428
429
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 426

def image_list_req(name)
  { :api_method  => @compute.images.list,
    :parameters  => { 'project' => name } }
end

#instance_delete_req(name) ⇒ Hash

Create a Google Compute instance delete request



545
546
547
548
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 545

def instance_delete_req(name)
  { :api_method  => @compute.instances.delete,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'instance' => name } }
end

#instance_get_req(name) ⇒ Hash

Create a Google Compute get instance request



537
538
539
540
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 537

def instance_get_req(name)
  { :api_method  => @compute.instances.get,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'instance' => name } }
end

#instance_insert_req(name, image, machineType, disk) ⇒ Hash

Create a Google Compute instance create request



556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 556

def instance_insert_req(name, image, machineType, disk)
  { :api_method  => @compute.instances.insert,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME },
    :body_object => { 'name' => name,
                      'image' => image,
                      'zone' => default_zone,
                      'machineType' => machineType,
                      'disks' => [ { 'source' => disk,
                                     'type' => 'PERSISTENT', 'boot' => 'true'} ],
                                     'networkInterfaces' => [ { 'accessConfigs' => [{ 'type' => 'ONE_TO_ONE_NAT', 'name' => 'External NAT' }],
                                                                'network' => default_network } ] } }
end

#instance_list_reqHash

Create a Google Compute list instance request



529
530
531
532
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 529

def instance_list_req
  { :api_method  => @compute.instances.list,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME } }
end

#instance_setMetadata_req(name, fingerprint, data) ⇒ Hash

Set tags on a Google Compute instance



518
519
520
521
522
523
524
525
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 518

def (name, fingerprint, data)
  { :api_method => @compute.instances.,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'instance' => name },
    :body_object => { 'kind' => 'compute#metadata',
                      'fingerprint'  => fingerprint,
                      'items' => data }
  }
end

#list_disks(start, attempts) ⇒ Array[Hash]

Determines a list of existing Google Compute disks

Raises:

  • (Exception)

    Raised if we fail determine the list of existing disks, either through errors or running out of attempts



234
235
236
237
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 234

def list_disks(start, attempts)
  disks = execute( disk_list_req(), start, attempts )
  disks["items"]
end

#list_firewalls(start, attempts) ⇒ Array[Hash]

Determines a list of existing Google Compute firewalls

Raises:

  • (Exception)

    Raised if we fail determine the list of existing firewalls, either through errors or running out of attempts



245
246
247
248
249
250
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 245

def list_firewalls(start, attempts)
  result = execute( firewall_list_req(), start, attempts )
  firewalls = result["items"]
  firewalls.delete_if{|f| f['name'] =~ /default-allow-internal|default-ssh/}
  firewalls
end

#list_instances(start, attempts) ⇒ Array[Hash]

Determines a list of existing Google Compute instances

Raises:

  • (Exception)

    Raised if we fail determine the list of existing instances, either through errors or running out of attempts



223
224
225
226
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 223

def list_instances(start, attempts)
  instances = execute( instance_list_req(), start, attempts )
  instances["items"]
end

#machineType_get_reqHash

Create a Google Compute machineType get request



571
572
573
574
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 571

def machineType_get_req()
  { :api_method => @compute.machine_types.get,
    :parameters => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'machineType' => @options[:gce_machine_type] || DEFAULT_MACHINE_TYPE } }
end

#network_get_req(name = 'default') ⇒ Hash

Create a Google Compute get network request



503
504
505
506
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 503

def network_get_req(name = 'default')
  { :api_method  => @compute.networks.get,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'network' => name } }
end

#operation_get_req(name) ⇒ Hash

Create a Google Compute zone operation request



510
511
512
513
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 510

def operation_get_req(name)
  { :api_method  => @compute.zone_operations.get,
    :parameters  => { 'project' => @options[:gce_project], 'zone' => DEFAULT_ZONE_NAME, 'operation' => name } }
end

#set_client(version) ⇒ Object

Create the Google APIClient object which will be used for accessing the Google Compute API



78
79
80
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 78

def set_client(version)
  @client = Google::APIClient.new({:application_name => "Beaker", :application_version => version})
end

#set_compute_api(version, start, attempts) ⇒ Object

Discover the currently active Google Compute API

Raises:

  • (Exception)

    Raised if we fail to discover the Google Compute API, either through errors or running out of attempts



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 88

def set_compute_api version, start, attempts
  try = (Time.now - start) / SLEEPWAIT
  while try <= attempts
    begin
      @compute = @client.discovered_api('compute', version)
      @logger.debug("Google Compute API discovered")
      return
    rescue => e
      @logger.debug("Failed to discover Google Compute API")
      if try >= attempts
        raise e
      end
    end
    try += 1
  end
end

#setMetadata_on_instance(name, fingerprint, data, start, attempts) ⇒ Object

Add key/value pairs to a Google Compute instance on the current connection

Raises:

  • (Exception)

    Raised if we fail to add metadata, either through errors or running out of attempts



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/beaker/hypervisor/google_compute_helper.rb', line 330

def (name, fingerprint, data, start, attempts)
  zone_operation = execute( ( name, fingerprint, data), start, attempts )
  status = ''
  try = (Time.now - start) / SLEEPWAIT
  while status !~ /DONE/ and try <= attempts
    begin
      operation = execute( operation_get_req( zone_operation['name'] ), start, attempts )
      status = operation['status']
    rescue GoogleComputeError => e
      @logger.debug("Waiting for tags to be added to #{name}")
      sleep(SLEEPWAIT)
    end
    try += 1
  end
  if status == ''
    raise "Unable to set metaData (#{tags.to_s}) on #{name}"
  end
  zone_operation
end