lib/azure/armrest/virtual_machine_service.rb in azure-armrest-0.3.11 vs lib/azure/armrest/virtual_machine_service.rb in azure-armrest-0.3.12

- old
+ new

@@ -119,22 +119,192 @@ # def stop(vmname, group = configuration.resource_group) vm_operate('powerOff', vmname, group) end + # Delete the VM and associated resources. By default, this will + # delete the VM, its NIC, the associated IP address, and the + # image files (.vhd and .status) for the VM. + # + # If you want to delete other associated resources, such as any + # attached disks, the VM's underlying storage account, or associated + # network security groups you must explicitly specify them as an option. + # + # An attempt to delete a resource that cannot be deleted because it's + # still associated with some other resource will be logged and skipped. + # + # If the :verbose option is set to true, then additional messages are + # sent to your configuration log, or stdout if no log was specified. + # + # Note that if all of your related resources are in a self-contained + # resource group, you do not necessarily need this method. You could + # just delete the resource group itself, which would automatically + # delete all of its resources. + # + def delete_associated_resources(vmname, vmgroup, options = {}) + options = { + :network_interfaces => true, + :ip_addresses => true, + :os_disk => true, + :network_security_groups => false, + :storage_account => false, + :verbose => false + }.merge(options) + + Azure::Armrest::Configuration.log ||= STDOUT if options[:verbose] + + vm = get(vmname, vmgroup) + + delete_and_wait(self, vmname, vmgroup, options) + + # Must delete network interfaces first if you want to delete + # IP addresses or network security groups. + if options[:network_interfaces] || options[:ip_addresses] || options[:network_security_groups] + delete_associated_nics(vm, options) + end + + if options[:os_disk] || options[:storage_account] + delete_associated_disk(vm, options) + end + end + def model_class VirtualMachineModel end private + # Deletes any NIC's associated with the VM, and optionally any public IP addresses + # and network security groups. + # + def delete_associated_nics(vm, options) + nis = Azure::Armrest::Network::NetworkInterfaceService.new(configuration) + nics = vm.properties.network_profile.network_interfaces.map(&:id) + + if options[:ip_addresses] + ips = Azure::Armrest::Network::IpAddressService.new(configuration) + end + + if options[:network_security_groups] + nsgs = Azure::Armrest::Network::NetworkSecurityGroupService.new(configuration) + end + + nics.each do |nic_id_string| + nic = get_associated_resource(nic_id_string) + delete_and_wait(nis, nic.name, nic.resource_group, options) + + if options[:ip_addresses] + nic.properties.ip_configurations.each do |ip| + ip = get_associated_resource(ip.properties.public_ip_address.id) + delete_and_wait(ips, ip.name, ip.resource_group, options) + end + end + + if options[:network_security_groups] + nic.properties.network_security_group + nsg = get_associated_resource(nic.properties.network_security_group.id) + delete_and_wait(nsgs, nsg.name, nsg.resource_group, options) + end + end + end + + # This deletes the OS disk from the storage account that's backing the + # virtual machine, along with the .status file. This does NOT delete + # copies of the disk. + # + # If the option to delete the entire storage account was selected, then + # it will not bother with deleting invidual files from the storage + # account first. + # + def delete_associated_disk(vm, options) + sas = Azure::Armrest::StorageAccountService.new(configuration) + + storage_account = sas.get_from_vm(vm) + + # Deleting the storage account does not require deleting the disks + # first, so skip that if deletion of the storage account was requested. + if options[:storage_account] + delete_and_wait(sas, storage_account.name, storage_account.resource_group, options) + else + keys = sas.list_account_keys(storage_account.name, storage_account.resource_group) + key = keys['key1'] || keys['key2'] + disk = sas.get_os_disk(vm) + + # There's a short delay between deleting the VM and unlocking the underlying + # .vhd file by Azure. Therefore we sleep up to two minutes while checking. + if disk.x_ms_lease_status.casecmp('unlocked') != 0 + sleep_time = 0 + + while sleep_time < 120 + sleep 10 + sleep_time += 10 + disk = sas.get_os_disk(vm) + break if disk.x_ms_lease_status.casecmp('unlocked') != 0 + end + + # In the unlikely event it did not unlock, just log and skip. + if disk.x_ms_lease_status.casecmp('unlocked') != 0 + log_message("Unable to delete disk #{disk.container}/#{disk.name}", 'warn') + return + end + end + + storage_account.delete_blob(disk.container, disk.name, key) + log_message("Deleted blob #{disk.container}/#{disk.name}") if options[:verbose] + + begin + status_file = File.basename(disk.name, '.vhd') + '.status' + storage_account.delete_blob(disk.container, status_file, key) + rescue Azure::Armrest::NotFoundException + # Ignore, does not always exist. + else + log_message("Deleted blob #{disk.container}/#{status_file}") if options[:verbose] + end + end + end + + # Delete a +service+ type resource using its name and resource group, + # and wait for the operation to complete before returning. + # + # If the operation fails because a dependent resource is still attached, + # then the error is logged (in verbose mode) and ignored. + # + def delete_and_wait(service, name, group, options) + resource_type = service.class.to_s.sub('Service', '').split('::').last + + log_message("Deleting #{resource_type} #{name}/#{group}") if options[:verbose] + + headers = service.delete(name, group) + + loop do + status = wait(headers) + break if status.downcase.start_with?('succ') # Succeeded, Success, etc. + end + + log_message("Deleted #{resource_type} #{name}/#{group}") if options[:verbose] + rescue Azure::Armrest::BadRequestException, Azure::Armrest::PreconditionFailedException => err + if options[:verbose] + msg = "Unable to delete #{resource_type} #{name}/#{group}, skipping. Message: #{err.message}" + log_message(msg, 'warn') + end + end + def vm_operate(action, vmname, group, options = {}) raise ArgumentError, "must specify resource group" unless group raise ArgumentError, "must specify name of the vm" unless vmname url = build_url(group, vmname, action) rest_post(url) nil + end + + # Simple log messager. Use the Configuration.log if defined. + def log_message(msg, level = 'info') + if Azure::Armrest::Configuration.log + Azure::Armrest::Configuration.log.send(level.to_sym, msg) + else + warn msg + end end end end end