fog/fog-azure-rm

View on GitHub
lib/fog/azurerm/requests/compute/create_virtual_machine.rb

Summary

Maintainability
D
2 days
Test Coverage
F
12%
require 'base64'

WHITE_SPACE = ' '.freeze

module Fog
  module Compute
    class AzureRM
      # This class provides the actual implementation for service calls.
      class Real
        def create_virtual_machine(vm_config, async = false)
          vm_name = vm_config[:name]
          rg_name = vm_config[:resource_group]

          # In case of updating the VM, we check if the user has passed any value for os_disk_name
          # If the user has not passed any value, we try to retrieve the value of os_disk_name from the VM
          # If the VM exists then the os_disk_name is retrieved; else it remains nil
          os_disk_parameters = get_os_disk_parameters(rg_name, vm_name) if vm_config[:os_disk_name].nil? || vm_config[:os_disk_vhd_uri].nil?
          vm_config[:os_disk_name] = os_disk_parameters[:os_disk_name] if vm_config[:os_disk_name].nil?
          vm_config[:os_disk_vhd_uri] = os_disk_parameters[:os_disk_vhd_uri] if vm_config[:os_disk_vhd_uri].nil?

          msg = "Creating Virtual Machine '#{vm_name}' in Resource Group '#{rg_name}'..."
          Fog::Logger.debug msg

          vm = Azure::ARM::Compute::Models::VirtualMachine.new
          vm.location = vm_config[:location]
          vm.tags = vm_config[:tags]
          vm.availability_set = get_vm_availability_set(vm_config[:availability_set_id])
          vm.hardware_profile = get_hardware_profile(vm_config[:vm_size])
          vm.os_profile = get_os_profile(vm_config)
          vm.network_profile = get_network_profile(vm_config[:network_interface_card_ids])
          vm.storage_profile = get_storage_profile(vm_config)

          begin
            response = if async
                         @compute_mgmt_client.virtual_machines.create_or_update_async(rg_name, vm_name, vm)
                       else
                         @compute_mgmt_client.virtual_machines.create_or_update(rg_name, vm_name, vm)
                       end
          rescue MsRestAzure::AzureOperationError => e
            raise_azure_exception(e, msg)
          end

          unless async
            unless vm_config[:vhd_path].nil? || vm_config[:managed_disk_storage_type].nil?
              delete_image(rg_name, vm_name)
              delete_storage_account_or_container(rg_name, vm_config[:storage_account_name], vm_name)
            end
          end

          Fog::Logger.debug "Virtual Machine #{vm_name} created successfully!" unless async
          response
        end

        private

        def get_vm_availability_set(availability_set_id)
          sub_resource = nil
          unless availability_set_id.nil?
            sub_resource = MsRestAzure::SubResource.new
            sub_resource.id = availability_set_id
          end
          sub_resource
        end

        def get_hardware_profile(vm_size)
          hw_profile = Azure::ARM::Compute::Models::HardwareProfile.new
          hw_profile.vm_size = vm_size
          hw_profile
        end

        def get_os_profile(vm_config)
          # Arguments unpacking
          platform = vm_config[:platform]
          vm_name = vm_config[:name]
          username = vm_config[:username]
          password = vm_config[:password]
          custom_data = vm_config[:custom_data] unless vm_config[:custom_data].nil?
          provision_vm_agent = vm_config[:provision_vm_agent]
          enable_automatic_updates = vm_config[:enable_automatic_updates]
          disable_password_auth = vm_config[:disable_password_authentication]
          ssh_key_path = vm_config[:ssh_key_path]
          ssh_key_data = vm_config[:ssh_key_data]

          # Build and return os profile object
          os_profile = Azure::ARM::Compute::Models::OSProfile.new
          os_profile.computer_name = vm_name
          os_profile.admin_username = username
          os_profile.admin_password = password
          os_profile.custom_data = Base64.strict_encode64(custom_data) unless vm_config[:custom_data].nil?

          if platform.casecmp(WINDOWS).zero?
            os_profile.windows_configuration = get_windows_config(provision_vm_agent, enable_automatic_updates)
          else
            os_profile.linux_configuration = get_linux_config(disable_password_auth, ssh_key_path, ssh_key_data)
          end

          os_profile
        end

        def get_windows_config(provision_vm_agent, enable_automatic_updates)
          windows_config = Azure::ARM::Compute::Models::WindowsConfiguration.new
          windows_config.provision_vmagent = provision_vm_agent
          windows_config.enable_automatic_updates = enable_automatic_updates
          windows_config
        end

        def get_linux_config(disable_password_auth, ssh_key_path, ssh_key_data)
          linux_config = Azure::ARM::Compute::Models::LinuxConfiguration.new
          linux_config.disable_password_authentication = disable_password_auth

          unless ssh_key_path.nil? || ssh_key_data.nil?
            ssh_config = Azure::ARM::Compute::Models::SshConfiguration.new
            ssh_public_key = Azure::ARM::Compute::Models::SshPublicKey.new
            ssh_public_key.path = ssh_key_path
            ssh_public_key.key_data = ssh_key_data
            ssh_config.public_keys = [ssh_public_key]
            linux_config.ssh = ssh_config
          end

          linux_config
        end

        def get_network_profile(network_interface_card_ids)
          network_interface_cards = []
          network_interface_card_ids.each_with_index do |id, index|
            nic = Azure::ARM::Compute::Models::NetworkInterfaceReference.new
            nic.id = id
            nic.primary = index == PRIMARY_NIC_INDEX
            network_interface_cards << nic
          end

          network_profile = Azure::ARM::Compute::Models::NetworkProfile.new
          network_profile.network_interfaces = network_interface_cards
          network_profile
        end

        def get_storage_profile(vm_config)
          # Arguments unpacking
          managed_disk_storage_type = vm_config[:managed_disk_storage_type]

          storage_profile = if managed_disk_storage_type.nil?
                              get_unmanaged_vm_storage_profile(vm_config)
                            else
                              get_managed_vm_storage_profile(vm_config)
                            end
          storage_profile
        end

        def get_unmanaged_vm_storage_profile(vm_config)
          # Arguments unpacking
          vm_name = vm_config[:name]
          storage_account_name = vm_config[:storage_account_name]
          publisher = vm_config[:publisher]
          offer = vm_config[:offer]
          sku = vm_config[:sku]
          version = vm_config[:version]
          vhd_path = vm_config[:vhd_path]
          os_disk_caching = vm_config[:os_disk_caching]
          platform = vm_config[:platform]
          resource_group = vm_config[:resource_group]
          os_disk_size = vm_config[:os_disk_size]
          location = vm_config[:location]
          image_ref = vm_config[:image_ref]
          os_disk_name = vm_config[:os_disk_name]
          os_disk_vhd_uri = vm_config[:os_disk_vhd_uri]

          storage_profile = Azure::ARM::Compute::Models::StorageProfile.new
          # Set OS disk VHD path
          os_disk = Azure::ARM::Compute::Models::OSDisk.new
          vhd = Azure::ARM::Compute::Models::VirtualHardDisk.new
          vhd.uri = os_disk_vhd_uri.nil? ? get_blob_endpoint(storage_account_name) + "/vhds/#{vm_name}_os_disk.vhd" : os_disk_vhd_uri
          os_disk.vhd = vhd

          if vhd_path.nil? && image_ref.nil?
            # Marketplace
            storage_profile.image_reference = image_reference(publisher, offer, sku, version)
          elsif !vhd_path.nil? && image_ref.nil?
            # VHD
            new_vhd_path = copy_vhd_to_storage_account(resource_group, storage_account_name, vhd_path, location, vm_name)
            img_vhd = Azure::ARM::Compute::Models::VirtualHardDisk.new
            img_vhd.uri = new_vhd_path
            os_disk.image = img_vhd
          else
            # Image
            image_resource_group = get_resource_group_from_id(image_ref)
            image_name = get_image_name(image_ref)
            image = get_image(image_resource_group, image_name)
            storage_profile.image_reference = Azure::ARM::Compute::Models::ImageReference.new
            storage_profile.image_reference.id = image.id
          end

          storage_profile.os_disk = configure_os_disk_object(os_disk, os_disk_name, os_disk_caching, os_disk_size, platform, vm_name)
          storage_profile
        end

        def get_managed_vm_storage_profile(vm_config)
          # Argument unpacking
          managed_disk_storage_type = vm_config[:managed_disk_storage_type]
          vhd_path = vm_config[:vhd_path]
          resource_group = vm_config[:resource_group]
          storage_account_name = vm_config[:storage_account_name]
          location = vm_config[:location]
          publisher = vm_config[:publisher]
          offer = vm_config[:offer]
          sku = vm_config[:sku]
          version = vm_config[:version]
          os_disk_caching = vm_config[:os_disk_caching]
          os_disk_size = vm_config[:os_disk_size]
          platform = vm_config[:platform]
          vm_name = vm_config[:name]
          image_ref = vm_config[:image_ref]
          os_disk_name = vm_config[:os_disk_name]

          # Build storage profile
          storage_profile = Azure::ARM::Compute::Models::StorageProfile.new
          os_disk = Azure::ARM::Compute::Models::OSDisk.new
          managed_disk = Azure::ARM::Compute::Models::ManagedDiskParameters.new
          managed_disk.storage_account_type = managed_disk_storage_type
          os_disk.managed_disk = managed_disk

          if vhd_path.nil? && image_ref.nil?
            # Marketplace
            storage_profile.image_reference = image_reference(publisher, offer, sku, version)
          elsif !vhd_path.nil? && image_ref.nil?
            # VHD
            new_vhd_path = copy_vhd_to_storage_account(resource_group, storage_account_name, vhd_path, location, vm_name)
            image = create_image(image_config_params(location, new_vhd_path, platform, resource_group, vm_name))
            storage_profile.image_reference = Azure::ARM::Compute::Models::ImageReference.new
            storage_profile.image_reference.id = image.id
          else
            # Image
            image_resource_group = get_resource_group_from_id(image_ref)
            image_name = get_image_name(image_ref)
            image = get_image(image_resource_group, image_name)
            storage_profile.image_reference = Azure::ARM::Compute::Models::ImageReference.new
            storage_profile.image_reference.id = image.id
          end

          storage_profile.os_disk = configure_os_disk_object(os_disk, os_disk_name, os_disk_caching, os_disk_size, platform, vm_name)
          storage_profile
        end

        def image_reference(publisher, offer, sku, version)
          image_reference = Azure::ARM::Compute::Models::ImageReference.new
          image_reference.publisher = publisher
          image_reference.offer = offer
          image_reference.sku = sku
          image_reference.version = version
          image_reference
        end

        def image_config_params(location, new_vhd_path, platform, resource_group, vm_name)
          {
            location: location,
            new_vhd_path: new_vhd_path,
            platform: platform,
            resource_group: resource_group,
            vm_name: vm_name
          }
        end

        def configure_os_disk_object(os_disk, os_disk_name, os_disk_caching, os_disk_size, platform, vm_name)
          # It will use the os_disk_name provided or it will generate a name for itself if it is nil
          os_disk.name = os_disk_name.nil? ? "#{vm_name}_os_disk" : os_disk_name
          os_disk.os_type = platform
          os_disk.disk_size_gb = os_disk_size unless os_disk_size.nil?
          os_disk.create_option = Azure::ARM::Compute::Models::DiskCreateOptionTypes::FromImage
          os_disk.caching = unless os_disk_caching.nil?
                              case os_disk_caching
                              when 'None'
                                Azure::ARM::Compute::Models::CachingTypes::None
                              when 'ReadOnly'
                                Azure::ARM::Compute::Models::CachingTypes::ReadOnly
                              when 'ReadWrite'
                                Azure::ARM::Compute::Models::CachingTypes::ReadWrite
                              end
                            end
          os_disk
        end

        def copy_vhd_to_storage_account(resource_group, storage_account_name, vhd_path, location, vm_name)
          # Copy if VHD does not exist belongs to same storage account.
          vhd_storage_account_name = (vhd_path.split('/')[2]).split('.')[0]
          if storage_account_name != vhd_storage_account_name
            if storage_account_name.nil?
              new_time = current_time
              storage_account_name = "sa#{new_time}"
              storage_account_config = storage_account_config_params(location, resource_group, storage_account_name)
              storage_account = @storage_service.storage_accounts.create(storage_account_config)
            else
              storage_account = @storage_service.storage_accounts.get(resource_group, storage_account_name)
            end

            access_key = storage_account.get_access_keys.first.value
            storage_data = Fog::Storage::AzureRM.new(azure_storage_account_name: storage_account_name, azure_storage_access_key: access_key)
            new_time = current_time
            container_name = "customvhd-#{vm_name.downcase}-os-image"
            blob_name = "vhd_image#{new_time}.vhd"
            storage_data.directories.create(key: container_name)
            storage_data.copy_blob_from_uri(container_name, blob_name, vhd_path)

            until storage_data.get_blob_properties(container_name, blob_name).properties[:copy_status] == 'success'
              Fog::Logger.debug 'Waiting disk to ready'
              sleep(10)
            end

            new_vhd_path = get_blob_endpoint(storage_account_name) + "/#{container_name}/#{blob_name}"
            Fog::Logger.debug "Path:#{new_vhd_path}. | Copy done"
          else
            new_vhd_path = vhd_path
          end

          new_vhd_path
        end

        def storage_account_config_params(location, resource_group, storage_account_name)
          {
            name: storage_account_name,
            location: location,
            resource_group: resource_group,
            account_type: 'Standard',
            replication: 'LRS',
            tags:
            {
              TEMPORARY_STORAGE_ACCOUNT_TAG_KEY => TEMPORARY_STORAGE_ACCOUNT_TAG_VALUE
            }
          }
        end

        def delete_storage_account_or_container(resource_group, storage_account_name, vm_name)
          delete_storage_account(resource_group) if storage_account_name.nil?
          delete_storage_container(resource_group, storage_account_name, vm_name) unless storage_account_name.nil?
        end

        def delete_storage_container(resource_group, storage_account_name, vm_name)
          access_key = @storage_service.get_storage_access_keys(resource_group, storage_account_name).first.value
          container_name = "customvhd-#{vm_name.downcase}-os-image"
          @storage_service.directories.delete_temporary_container(storage_account_name, access_key, container_name)
        end

        def delete_storage_account(resource_group)
          @storage_service.storage_accounts.delete_storage_account_from_tag(resource_group,
                                                                            TEMPORARY_STORAGE_ACCOUNT_TAG_KEY,
                                                                            TEMPORARY_STORAGE_ACCOUNT_TAG_VALUE)
        end

        def get_os_disk_parameters(resource_group, virtual_machine_name)
          os_disk_parameters = {}

          begin
            vm = get_virtual_machine(resource_group, virtual_machine_name, false)
          rescue
            return os_disk_parameters
          end

          unless vm.storage_profile.nil?
            os_disk_parameters[:os_disk_name] = vm.storage_profile.os_disk.name
            os_disk_parameters[:os_disk_vhd_uri] = vm.storage_profile.os_disk.vhd.uri unless vm.storage_profile.os_disk.vhd.nil?
          end

          os_disk_parameters
        end
      end

      # This class provides the mock implementation for unit tests.
      class Mock
        def create_virtual_machine(*)
          vm = {
            'location' => 'westus',
            'id' => '/subscriptions/########-####-####-####-############/resourceGroups/fog-test-rg/providers/Microsoft.Compute/virtualMachines/fog-test-server',
            'name' => 'fog-test-server',
            'type' => 'Microsoft.Compute/virtualMachines',
            'properties' =>
            {
              'hardwareProfile' =>
                {
                  'vmSize' => 'Standard_A0'
                },
              'storageProfile' =>
                {
                  'imageReference' =>
                    {
                      'publisher' => 'MicrosoftWindowsServerEssentials',
                      'offer' => 'WindowsServerEssentials',
                      'sku' => 'WindowsServerEssentials',
                      'version' => 'latest'
                    },
                  'osDisk' =>
                    {
                      'name' => 'fog-test-server_os_disk',
                      'vhd' =>
                        {
                          'uri' => 'http://mystorage1.blob.core.windows.net/vhds/fog-test-server_os_disk.vhd'
                        },
                      'createOption' => 'FromImage',
                      'osType' => 'Linux',
                      'caching' => 'ReadWrite'
                    },
                  'dataDisks' => []
                },
              'osProfile' =>
                {
                  'computerName' => 'fog-test-server',
                  'adminUsername' => 'fog',
                  'linuxConfiguration' =>
                    {
                      'disablePasswordAuthentication' => true
                    },
                  'secrets' => [],
                  'customData' => 'ZWNobyBjdXN0b21EYXRh'
                },
              'networkProfile' =>
                {
                  'networkInterfaces' =>
                    [
                      {
                        'id' => '/subscriptions/########-####-####-####-############/resourceGroups/fog-test-rg/providers/Microsoft.Network/networkInterfaces/fog-test-vnet'
                      }
                    ]
                },
              'provisioningState' => 'Succeeded'
            }
          }

          vm_mapper = Azure::ARM::Compute::Models::VirtualMachine.mapper
          @compute_mgmt_client.deserialize(vm_mapper, vm, 'result.body')
        end
      end
    end
  end
end