mirror of
https://github.com/puppetlabs/vmpooler-provider-ec2.git
synced 2026-01-26 02:28:40 -05:00
begin disk and snapshop, but we dont have perms
This commit is contained in:
parent
2783adc32e
commit
f125c08b2e
1 changed files with 124 additions and 156 deletions
|
|
@ -153,22 +153,15 @@ module Vmpooler
|
||||||
def get_vm(pool_name, vm_name)
|
def get_vm(pool_name, vm_name)
|
||||||
debug_logger('get_vm')
|
debug_logger('get_vm')
|
||||||
vm_hash = nil
|
vm_hash = nil
|
||||||
begin
|
|
||||||
filters = [{
|
filters = [{
|
||||||
name: "tag:vm_name",
|
name: "tag:vm_name",
|
||||||
values: [vm_name],
|
values: [vm_name],
|
||||||
}]
|
}]
|
||||||
instances = connection.instances(filters: filters)
|
instances = connection.instances(filters: filters).first
|
||||||
rescue ::Aws::EC2::ClientError => e
|
return vm_hash if instances.nil?
|
||||||
raise e unless e.status_code == 404
|
|
||||||
|
|
||||||
# swallow the ClientError error 404 and return nil when the VM was not found
|
vm_hash = generate_vm_hash(instances, pool_name)
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return vm_hash if instances.size.nil? || instances.size == 0
|
|
||||||
|
|
||||||
vm_hash = generate_vm_hash(instances.first, pool_name)
|
|
||||||
debug_logger("vm_hash #{vm_hash}")
|
debug_logger("vm_hash #{vm_hash}")
|
||||||
vm_hash
|
vm_hash
|
||||||
end
|
end
|
||||||
|
|
@ -248,7 +241,7 @@ module Vmpooler
|
||||||
debug_logger('trigger insert_instance')
|
debug_logger('trigger insert_instance')
|
||||||
batch_instance = connection.create_instances(config)
|
batch_instance = connection.create_instances(config)
|
||||||
instance_id = batch_instance.first.instance_id
|
instance_id = batch_instance.first.instance_id
|
||||||
connection.client.wait_until(:instance_running, {instance_ids: [instance_id]}, {max_attempts: 10})
|
connection.client.wait_until(:instance_running, {instance_ids: [instance_id]})
|
||||||
created_instance = get_vm(pool_name, new_vmname)
|
created_instance = get_vm(pool_name, new_vmname)
|
||||||
created_instance
|
created_instance
|
||||||
end
|
end
|
||||||
|
|
@ -288,7 +281,7 @@ module Vmpooler
|
||||||
# #{vm_name}-disk1 == additional disk added via create_disk
|
# #{vm_name}-disk1 == additional disk added via create_disk
|
||||||
# #{vm_name}-disk2 == additional disk added via create_disk if run a second time etc
|
# #{vm_name}-disk2 == additional disk added via create_disk if run a second time etc
|
||||||
# the new disk has labels added for vm and pool
|
# the new disk has labels added for vm and pool
|
||||||
# The GCE lifecycle is to create a new disk (lives independently of the instance) then to attach
|
# The AWS lifecycle is to create a new disk (lives independently of the instance) then to attach
|
||||||
# it to the existing instance.
|
# it to the existing instance.
|
||||||
# inputs
|
# inputs
|
||||||
# [String] pool_name : Name of the pool
|
# [String] pool_name : Name of the pool
|
||||||
|
|
@ -301,38 +294,54 @@ module Vmpooler
|
||||||
pool = pool_config(pool_name)
|
pool = pool_config(pool_name)
|
||||||
raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil?
|
raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil?
|
||||||
|
|
||||||
begin
|
filters = [{
|
||||||
vm_object = connection.get_instance(project, zone(pool_name), vm_name)
|
name: "tag:vm_name",
|
||||||
rescue ::Google::Apis::ClientError => e
|
values: [vm_name],
|
||||||
raise e unless e.status_code == 404
|
}]
|
||||||
|
instances = connection.instances(filters: filters).first
|
||||||
|
raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if instances.nil?
|
||||||
|
|
||||||
# if it does not exist
|
|
||||||
raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}")
|
|
||||||
end
|
|
||||||
# this number should start at 1 when there is only the boot disk,
|
# this number should start at 1 when there is only the boot disk,
|
||||||
# eg the new disk will be named spicy-proton-disk1
|
# eg the new disk will be named spicy-proton-disk1
|
||||||
number_disk = vm_object.disks.length
|
number_disk = instances.block_device_mappings.length
|
||||||
|
|
||||||
disk_name = "#{vm_name}-disk#{number_disk}"
|
disk_name = "#{vm_name}-disk#{number_disk}"
|
||||||
disk = Google::Apis::ComputeV1::Disk.new(
|
disk = {
|
||||||
name: disk_name,
|
availability_zone: zone(pool_name),
|
||||||
size_gb: disk_size,
|
size: disk_size,
|
||||||
labels: { 'pool' => pool_name, 'vm' => vm_name }
|
tag_specifications: [
|
||||||
)
|
{
|
||||||
|
resource_type: "volume",
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
key: "pool",
|
||||||
|
value: pool_name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "vm",
|
||||||
|
value: vm_name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "disk_name",
|
||||||
|
value: disk_name,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
debug_logger("trigger insert_disk #{disk_name} for #{vm_name}")
|
debug_logger("trigger insert_disk #{disk_name} for #{vm_name}")
|
||||||
result = connection.insert_disk(project, zone(pool_name), disk)
|
volume = connection.create_volume(disk)
|
||||||
wait_for_operation(project, pool_name, result)
|
# Aws::EC2::Errors::UnauthorizedOperation:
|
||||||
debug_logger("trigger get_disk #{disk_name} for #{vm_name}")
|
# You are not authorized to perform this operation.
|
||||||
new_disk = connection.get_disk(project, zone(pool_name), disk_name)
|
connection.client.wait_until(:volume_available, {volume_ids: [volume.id]})
|
||||||
|
|
||||||
attached_disk = Google::Apis::ComputeV1::AttachedDisk.new(
|
|
||||||
auto_delete: true,
|
|
||||||
boot: false,
|
|
||||||
source: new_disk.self_link
|
|
||||||
)
|
|
||||||
debug_logger("trigger attach_disk #{disk_name} for #{vm_name}")
|
debug_logger("trigger attach_disk #{disk_name} for #{vm_name}")
|
||||||
result = connection.attach_disk(project, zone(pool_name), vm_object.name, attached_disk)
|
volume = instances.attach_volume(
|
||||||
wait_for_operation(project, pool_name, result)
|
{
|
||||||
|
device: "/dev/xvdb",
|
||||||
|
volume_id: volume.id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
connection.client.wait_until(:volume_in_use, {volume_ids: [volume.id]})
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -341,7 +350,7 @@ module Vmpooler
|
||||||
# since the snapshot resource needs a unique name in the gce project,
|
# since the snapshot resource needs a unique name in the gce project,
|
||||||
# we create a unique name by concatenating {new_snapshot_name}-#{disk.name}
|
# we create a unique name by concatenating {new_snapshot_name}-#{disk.name}
|
||||||
# the disk name is based on vm_name which makes it unique.
|
# the disk name is based on vm_name which makes it unique.
|
||||||
# The snapshot is added labels snapshot_name, vm, pool, diskname and boot
|
# The snapshot is added tags snapshot_name, vm, pool, diskname and boot
|
||||||
# inputs
|
# inputs
|
||||||
# [String] pool_name : Name of the pool
|
# [String] pool_name : Name of the pool
|
||||||
# [String] vm_name : Name of the existing VM
|
# [String] vm_name : Name of the existing VM
|
||||||
|
|
@ -353,52 +362,58 @@ module Vmpooler
|
||||||
# RuntimeError if the snapshot_name already exists for this VM
|
# RuntimeError if the snapshot_name already exists for this VM
|
||||||
def create_snapshot(pool_name, vm_name, new_snapshot_name)
|
def create_snapshot(pool_name, vm_name, new_snapshot_name)
|
||||||
debug_logger('create_snapshot')
|
debug_logger('create_snapshot')
|
||||||
begin
|
filters = [{
|
||||||
vm_object = connection.get_instance(project, zone(pool_name), vm_name)
|
name: "tag:vm_name",
|
||||||
rescue ::Google::Apis::ClientError => e
|
values: [vm_name],
|
||||||
raise e unless e.status_code == 404
|
}]
|
||||||
|
instances = connection.instances(filters: filters).first
|
||||||
# if it does not exist
|
raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if instances.nil?
|
||||||
raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}")
|
|
||||||
end
|
|
||||||
|
|
||||||
old_snap = find_snapshot(vm_name, new_snapshot_name)
|
old_snap = find_snapshot(vm_name, new_snapshot_name)
|
||||||
raise("Snapshot #{new_snapshot_name} for VM #{vm_name} in pool #{pool_name} already exists for the provider #{name}") unless old_snap.nil?
|
raise("Snapshot #{new_snapshot_name} for VM #{vm_name} in pool #{pool_name} already exists for the provider #{name}") unless old_snap.first.nil?
|
||||||
|
|
||||||
result_list = []
|
result_list = []
|
||||||
vm_object.disks.each do |attached_disk|
|
instances.block_device_mappings.each do |attached_disk|
|
||||||
disk_name = disk_name_from_source(attached_disk)
|
volume_id = attached_disk.ebs.volume_id
|
||||||
snapshot_obj = ::Google::Apis::ComputeV1::Snapshot.new(
|
|
||||||
name: "#{new_snapshot_name}-#{disk_name}",
|
snapshot = connection.create_snapshot({
|
||||||
labels: {
|
description: new_snapshot_name,
|
||||||
'snapshot_name' => new_snapshot_name,
|
volume_id: volume_id,
|
||||||
'vm' => vm_name,
|
tag_specifications: [
|
||||||
'pool' => pool_name,
|
{
|
||||||
'diskname' => disk_name,
|
resource_type: "snapshot", # accepts capacity-reservation, client-vpn-endpoint, customer-gateway, carrier-gateway, dedicated-host, dhcp-options, egress-only-internet-gateway, elastic-ip, elastic-gpu, export-image-task, export-instance-task, fleet, fpga-image, host-reservation, image, import-image-task, import-snapshot-task, instance, instance-event-window, internet-gateway, ipam, ipam-pool, ipam-scope, ipv4pool-ec2, ipv6pool-ec2, key-pair, launch-template, local-gateway, local-gateway-route-table, local-gateway-virtual-interface, local-gateway-virtual-interface-group, local-gateway-route-table-vpc-association, local-gateway-route-table-virtual-interface-group-association, natgateway, network-acl, network-interface, network-insights-analysis, network-insights-path, network-insights-access-scope, network-insights-access-scope-analysis, placement-group, prefix-list, replace-root-volume-task, reserved-instances, route-table, security-group, security-group-rule, snapshot, spot-fleet-request, spot-instances-request, subnet, subnet-cidr-reservation, traffic-mirror-filter, traffic-mirror-session, traffic-mirror-target, transit-gateway, transit-gateway-attachment, transit-gateway-connect-peer, transit-gateway-multicast-domain, transit-gateway-route-table, volume, vpc, vpc-endpoint, vpc-endpoint-service, vpc-peering-connection, vpn-connection, vpn-gateway, vpc-flow-log
|
||||||
'boot' => attached_disk.boot.to_s
|
tags: [
|
||||||
}
|
{
|
||||||
)
|
key: "vm_name",
|
||||||
debug_logger("trigger async create_disk_snapshot #{vm_name}: #{new_snapshot_name}-#{disk_name}")
|
value: vm_name,
|
||||||
result = connection.create_disk_snapshot(project, zone(pool_name), disk_name, snapshot_obj)
|
},
|
||||||
# do them all async, keep a list, check later
|
{
|
||||||
result_list << result
|
key: "pool_name",
|
||||||
end
|
value: pool_name,
|
||||||
# now check they are done
|
},
|
||||||
result_list.each do |result|
|
{
|
||||||
wait_for_operation(project, pool_name, result)
|
key: "new_snapshot_name",
|
||||||
|
value: new_snapshot_name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
# Aws::EC2::Errors::UnauthorizedOperation:
|
||||||
|
# You are not authorized to perform this operation.
|
||||||
end
|
end
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
# revert_snapshot reverts an existing VM's disks to an existing snapshot_name
|
# revert_snapshot reverts an existing VM's disks to an existing snapshot_name
|
||||||
# reverting in gce entails
|
# reverting in aws entails
|
||||||
# 1. shutting down the VM,
|
# 1. shutting down the VM,
|
||||||
# 2. detaching and deleting the drives,
|
# 2. detaching and deleting the drives,
|
||||||
# 3. creating new disks with the same name from the snapshot for each disk
|
# 3. creating new disks with the same name from the snapshot for each disk
|
||||||
# 4. attach disks and start instance
|
# 4. attach disks and start instance
|
||||||
# for one vm, there might be multiple snapshots in time. We select the ones referred to by the
|
# for one vm, there might be multiple snapshots in time. We select the ones referred to by the
|
||||||
# snapshot_name, but that may be multiple snapshots, one for each disks
|
# snapshot_name, but that may be multiple snapshots, one for each disks
|
||||||
# The new disk is added labels vm and pool
|
# The new disk is added tags vm and pool
|
||||||
# inputs
|
# inputs
|
||||||
# [String] pool_name : Name of the pool
|
# [String] pool_name : Name of the pool
|
||||||
# [String] vm_name : Name of the existing VM
|
# [String] vm_name : Name of the existing VM
|
||||||
|
|
@ -420,7 +435,7 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
|
|
||||||
snapshot_object = find_snapshot(vm_name, snapshot_name)
|
snapshot_object = find_snapshot(vm_name, snapshot_name)
|
||||||
raise("Snapshot #{snapshot_name} for VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if snapshot_object.nil?
|
raise("Snapshot #{snapshot_name} for VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if snapshot_object.first.nil?
|
||||||
|
|
||||||
# Shutdown instance
|
# Shutdown instance
|
||||||
debug_logger("trigger stop_instance #{vm_name}")
|
debug_logger("trigger stop_instance #{vm_name}")
|
||||||
|
|
@ -480,60 +495,39 @@ module Vmpooler
|
||||||
def destroy_vm(pool_name, vm_name)
|
def destroy_vm(pool_name, vm_name)
|
||||||
debug_logger('destroy_vm')
|
debug_logger('destroy_vm')
|
||||||
deleted = false
|
deleted = false
|
||||||
begin
|
|
||||||
connection.get_instance(project, zone(pool_name), vm_name)
|
|
||||||
rescue ::Google::Apis::ClientError => e
|
|
||||||
raise e unless e.status_code == 404
|
|
||||||
|
|
||||||
# If a VM doesn't exist then it is effectively deleted
|
filters = [{
|
||||||
deleted = true
|
name: "tag:vm_name",
|
||||||
debug_logger("instance #{vm_name} already deleted")
|
values: [vm_name],
|
||||||
end
|
}]
|
||||||
|
instances = connection.instances(filters: filters).first
|
||||||
|
return true if instances.nil?
|
||||||
|
|
||||||
unless deleted
|
|
||||||
debug_logger("trigger delete_instance #{vm_name}")
|
debug_logger("trigger delete_instance #{vm_name}")
|
||||||
vm_hash = get_vm(pool_name, vm_name)
|
# vm_hash = get_vm(pool_name, vm_name)
|
||||||
result = connection.delete_instance(project, zone(pool_name), vm_name)
|
instances.terminate
|
||||||
wait_for_operation(project, pool_name, result, 10)
|
begin
|
||||||
dns_teardown(vm_hash)
|
connection.client.wait_until(:instance_terminated, {instance_ids: [instances.id]})
|
||||||
|
deleted = true
|
||||||
|
rescue ::Aws::Waiters::Errors => error
|
||||||
|
debug_logger("failed waiting for instance terminated #{vm_name}")
|
||||||
end
|
end
|
||||||
|
|
||||||
# list and delete any leftover disk, for instance if they were detached from the instance
|
return deleted
|
||||||
filter = "(labels.vm = #{vm_name})"
|
|
||||||
disk_list = connection.list_disks(project, zone(pool_name), filter: filter)
|
|
||||||
result_list = []
|
|
||||||
disk_list.items&.each do |disk|
|
|
||||||
debug_logger("trigger delete_disk #{disk.name}")
|
|
||||||
result = connection.delete_disk(project, zone(pool_name), disk.name)
|
|
||||||
# do them all async, keep a list, check later
|
|
||||||
result_list << result
|
|
||||||
end
|
|
||||||
# now check they are done
|
|
||||||
result_list.each do |r|
|
|
||||||
wait_for_operation(project, pool_name, r)
|
|
||||||
end
|
|
||||||
|
|
||||||
# list and delete leftover snapshots, this could happen if snapshots were taken,
|
|
||||||
# as they are not removed when the original disk is deleted or the instance is detroyed
|
|
||||||
snapshot_list = find_all_snapshots(vm_name)
|
|
||||||
result_list = []
|
|
||||||
snapshot_list&.each do |snapshot|
|
|
||||||
debug_logger("trigger delete_snapshot #{snapshot.name}")
|
|
||||||
result = connection.delete_snapshot(project, snapshot.name)
|
|
||||||
# do them all async, keep a list, check later
|
|
||||||
result_list << result
|
|
||||||
end
|
|
||||||
# now check they are done
|
|
||||||
result_list.each do |r|
|
|
||||||
wait_for_operation(project, pool_name, r)
|
|
||||||
end
|
|
||||||
true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# check if a vm is ready by opening a socket on port 22
|
||||||
|
# if a domain is set, it will use vn_name.domain,
|
||||||
|
# if not then it will use the ip directly (AWS workaround)
|
||||||
def vm_ready?(_pool_name, vm_name)
|
def vm_ready?(_pool_name, vm_name)
|
||||||
begin
|
begin
|
||||||
# TODO: we could use a healthcheck resource attached to instance
|
# TODO: we could use a healthcheck resource attached to instance
|
||||||
open_socket(vm_name, domain || global_config[:config]['domain'])
|
domain_set = domain || global_config[:config]['domain']
|
||||||
|
if domain_set.nil?
|
||||||
|
vm_ip = get_vm(_pool_name, vm_name)['private_ip_address']
|
||||||
|
vm_name = vm_ip unless vm_ip.nil?
|
||||||
|
end
|
||||||
|
open_socket(vm_name, domain_set)
|
||||||
rescue StandardError => _e
|
rescue StandardError => _e
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
@ -694,40 +688,6 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Compute resource wait for operation to be DONE (synchronous operation)
|
|
||||||
def wait_for_zone_operation(project, zone, result, retries = 5)
|
|
||||||
while result.status != 'DONE'
|
|
||||||
result = connection.wait_zone_operation(project, zone, result.name)
|
|
||||||
debug_logger(" -> wait_for_zone_operation status #{result.status} (#{result.name})")
|
|
||||||
end
|
|
||||||
if result.error # unsure what kind of error can be stored here
|
|
||||||
error_message = ''
|
|
||||||
# array of errors, combine them all
|
|
||||||
result.error.errors.each do |error|
|
|
||||||
error_message = "#{error_message} #{error.code}:#{error.message}"
|
|
||||||
end
|
|
||||||
raise "Operation: #{result.description} failed with error: #{error_message}"
|
|
||||||
end
|
|
||||||
result
|
|
||||||
rescue Google::Apis::TransmissionError => e
|
|
||||||
# Error returned once timeout reached, each retry typically about 1 minute.
|
|
||||||
if retries.positive?
|
|
||||||
retries -= 1
|
|
||||||
retry
|
|
||||||
end
|
|
||||||
raise
|
|
||||||
rescue Google::Apis::ClientError => e
|
|
||||||
raise e unless e.status_code == 404
|
|
||||||
|
|
||||||
# if the operation is not found, and we are 'waiting' on it, it might be because it
|
|
||||||
# is already finished
|
|
||||||
puts "waited on #{result.name} but was not found, so skipping"
|
|
||||||
end
|
|
||||||
|
|
||||||
def wait_for_operation(project, pool_name, result, retries = 5)
|
|
||||||
wait_for_zone_operation(project, zone(pool_name), result, retries)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Return a hash of VM data
|
# Return a hash of VM data
|
||||||
# Provides name, template, poolname, boottime, status, image_size, private_ip_address
|
# Provides name, template, poolname, boottime, status, image_size, private_ip_address
|
||||||
def generate_vm_hash(vm_object, pool_name)
|
def generate_vm_hash(vm_object, pool_name)
|
||||||
|
|
@ -792,9 +752,17 @@ module Vmpooler
|
||||||
# this is used because for one vm, with the same snapshot name there could be multiple snapshots,
|
# this is used because for one vm, with the same snapshot name there could be multiple snapshots,
|
||||||
# one for each disk
|
# one for each disk
|
||||||
def find_snapshot(vm_name, snapshotname)
|
def find_snapshot(vm_name, snapshotname)
|
||||||
filter = "(labels.vm = #{vm_name}) AND (labels.snapshot_name = #{snapshotname})"
|
filters = [
|
||||||
snapshot_list = connection.list_snapshots(project, filter: filter)
|
{
|
||||||
snapshot_list.items # array of snapshot objects
|
name: "tag:vm_name",
|
||||||
|
values: [vm_name],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag:snapshot_name",
|
||||||
|
values: [snapshotname],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
snapshot_list = connection.snapshots({filters: filters})
|
||||||
end
|
end
|
||||||
|
|
||||||
# find all snapshots ever created for one vm,
|
# find all snapshots ever created for one vm,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue