fog/fog-vsphere

View on GitHub
lib/fog/vsphere/compute.rb

Summary

Maintainability
F
4 days
Test Coverage
require 'digest/sha2'
# rubocop:disable Lint/RescueWithoutErrorClass
# rubocop:disable Metrics/ModuleLength
module Fog
  module Vsphere
    class Compute < Fog::Service
      requires :vsphere_username, :vsphere_password, :vsphere_server
      recognizes :vsphere_port, :vsphere_path, :vsphere_ns
      recognizes :vsphere_rev, :vsphere_ssl, :vsphere_expected_pubkey_hash
      recognizes :vsphere_debug

      model_path 'fog/vsphere/models/compute'
      model :server
      collection :servers
      model :servertype
      collection :servertypes
      model :datacenter
      collection :datacenters
      model :interface
      collection :interfaces
      model :interfacetype
      collection :interfacetypes
      model :volume
      collection :volumes
      model :snapshot
      collection :snapshots
      model :template
      collection :templates
      model :cluster
      collection :clusters
      model :resource_pool
      collection :resource_pools
      model :network
      collection :networks
      model :datastore
      collection :datastores
      model :storage_pod
      collection :storage_pods
      model :folder
      collection :folders
      model :customvalue
      collection :customvalues
      model :customfield
      collection :customfields
      model :scsicontroller
      model :process
      model :cdrom
      collection :cdroms
      model :rule
      collection :rules
      model :host
      collection :hosts
      model :ticket
      collection :tickets

      request_path 'fog/vsphere/requests/compute'
      request :current_time
      request :cloudinit_to_customspec
      request :list_virtual_machines
      request :vm_power_off
      request :vm_power_on
      request :vm_suspend
      request :vm_reboot
      request :vm_clone
      request :vm_destroy
      request :vm_migrate
      request :vm_execute
      request :list_datacenters
      request :get_datacenter
      request :list_clusters
      request :get_cluster
      request :list_resource_pools
      request :get_resource_pool
      request :create_resource_pool
      request :update_resource_pool
      request :destroy_resource_pool
      request :list_networks
      request :get_network
      request :list_datastores
      request :get_datastore
      request :list_storage_pods
      request :get_storage_pod
      request :list_compute_resources
      request :get_compute_resource
      request :list_templates
      request :get_template
      request :get_folder
      request :list_folders
      request :create_vm
      request :update_vm
      request :list_vm_interfaces
      request :modify_vm_interface
      request :modify_vm_volume
      request :modify_vm_cdrom
      request :list_vm_volumes
      request :list_vm_cdroms
      request :get_virtual_machine
      request :vm_reconfig_hardware
      request :vm_reconfig_memory
      request :vm_reconfig_volumes
      request :vm_reconfig_cpus
      request :vm_reconfig_cdrom
      request :vm_rename
      request :vm_config_vnc
      request :create_folder
      request :list_server_types
      request :get_server_type
      request :list_interface_types
      request :get_interface_type
      request :list_vm_customvalues
      request :list_customfields
      request :get_vm_first_scsi_controller
      request :list_vm_scsi_controllers
      request :set_vm_customvalue
      request :vm_take_snapshot
      request :list_vm_snapshots
      request :list_child_snapshots
      request :revert_to_snapshot
      request :list_processes
      request :upload_iso
      request :folder_destroy
      request :create_rule
      request :list_rules
      request :destroy_rule
      request :list_hosts
      request :create_group
      request :list_groups
      request :destroy_group
      request :get_host
      request :modify_vm_controller
      request :vm_revert_snapshot
      request :vm_remove_snapshot
      request :vm_acquire_ticket
      request :vm_relocate
      request :host_shutdown
      request :host_start_maintenance
      request :host_finish_maintenance
      request :get_vm_first_sata_controller

      module Shared
        attr_reader :vsphere_is_vcenter
        attr_reader :vsphere_rev
        attr_reader :vsphere_server
        attr_reader :vsphere_username

        protected

        ATTR_TO_PROP = {
          id: 'config.instanceUuid',
          name: 'name',
          uuid: 'config.uuid',
          template: 'config.template',
          parent: 'parent',
          hostname: 'summary.guest.hostName',
          operatingsystem: 'summary.guest.guestFullName',
          ipaddress: 'guest.ipAddress',
          power_state: 'runtime.powerState',
          connection_state: 'runtime.connectionState',
          hypervisor: 'runtime.host',
          tools_state: 'guest.toolsStatus',
          tools_version: 'guest.toolsVersionStatus',
          memory_mb: 'config.hardware.memoryMB',
          cpus: 'config.hardware.numCPU',
          disks: 'config.hardware.device',
          partitions:  'guest.disk',
          corespersocket: 'config.hardware.numCoresPerSocket',
          overall_status: 'overallStatus',
          guest_id: 'config.guestId',
          hardware_version: 'config.version',
          cpuHotAddEnabled: 'config.cpuHotAddEnabled',
          memoryHotAddEnabled: 'config.memoryHotAddEnabled',
          firmware: 'config.firmware',
          boot_order: 'config.bootOptions.bootOrder',
          annotation: 'config.annotation',
          extra_config: 'config.extraConfig'
        }.freeze

        def convert_vm_view_to_attr_hash(vms)
          vms = connection.serviceContent.propertyCollector.collectMultiple(vms, *ATTR_TO_PROP.values.uniq)
          vms.map { |vm| props_to_attr_hash(*vm) }
        end

        # Utility method to convert a VMware managed object into an attribute hash.
        # This should only really be necessary for the real class.
        # This method is expected to be called by the request methods
        # in order to massage VMware Managed Object References into Attribute Hashes.
        def convert_vm_mob_ref_to_attr_hash(vm_mob_ref)
          return nil unless vm_mob_ref

          props = vm_mob_ref.collect!(*ATTR_TO_PROP.values.uniq)
          props_to_attr_hash vm_mob_ref, props
        end

        # rubocop:disable Metrics/MethodLength
        def props_to_attr_hash(vm_mob_ref, props)
          # NOTE: Object.tap is in 1.8.7 and later.
          # Here we create the hash object that this method returns, but first we need
          # to add a few more attributes that require additional calls to the vSphere
          # API. The hypervisor name and mac_addresses attributes may not be available
          # so we need catch any exceptions thrown during lookup and set them to nil.
          #
          # The use of the "tap" method here is a convenience, it allows us to update the
          # hash object without explicitly returning the hash at the end of the method.
          Hash[ATTR_TO_PROP.map { |k, v| [k.to_s, props[v]] }].tap do |attrs|
            attrs['id'] ||= vm_mob_ref._ref
            attrs['mo_ref'] = vm_mob_ref._ref
            # The name method "magically" appears after a VM is ready and
            # finished cloning.
            attrs['boot_order'] = parse_boot_order(attrs['boot_order'])

            if attrs['hypervisor'].is_a?(RbVmomi::VIM::HostSystem)
              host = attrs['hypervisor']

              # @note
              #   - User might not have permission to access host
              #   - i.e. host.name might raise NoPermission error
              has_permission_to_access_host = true
              begin
                host.name
              rescue RbVmomi::Fault => e
                raise e unless e.message && e.message['NoPermission:']
                has_permission_to_access_host = false
              end

              attrs['datacenter'] = proc {
                begin
                  if has_permission_to_access_host
                    parent_attribute(host.path, :datacenter)[1]
                  else
                    parent_attribute(vm_mob_ref.path, :datacenter)[1]
                  end
                rescue
                  nil
                end
              }
              attrs['cluster'] = proc {
                begin
                           parent_attribute(host.path, :cluster)[1]
                         rescue
                           nil
                         end
              }
              attrs['hypervisor'] = proc {
                begin
                           host.name
                         rescue
                           nil
                         end
              }
              attrs['resource_pool'] = proc {
                begin
                          (vm_mob_ref.resourcePool || host.resourcePool).name
                        rescue
                          nil
                        end
              }

              attrs['disks'] = parse_disks(attrs['disks'])
              attrs['partitions'] = parse_partitions(attrs['partitions'])
              attrs['extra_config'] = parse_extra_config(attrs['extra_config'])
            end
            # This inline rescue catches any standard error.  While a VM is
            # cloning, a call to the macs method will throw and NoMethodError
            attrs['mac_addresses'] = proc {
              begin
                        vm_mob_ref.macs
                      rescue
                        nil
                      end
            }
            # Rescue nil to catch testing while vm_mob_ref isn't reaL??
            attrs['path'] = begin
                              '/' + attrs['parent'].path.map(&:last).join('/')
                            rescue
                              nil
                            end
          end
        end

        # returns the parent object based on a type
        # provides both real RbVmomi object and its name.
        # e.g.
        # [Datacenter("datacenter-2"), "dc-name"]
        # rubocop:enable Metrics/MethodLength
        def parent_attribute(path, type)
          element = case type
                    when :datacenter
                      RbVmomi::VIM::Datacenter
                    when :cluster
                      RbVmomi::VIM::ComputeResource
                    when :host
                      RbVmomi::VIM::HostSystem
                    else
                      raise 'Unknown type'
                    end
          path.select { |x| x[0].is_a? element }.flatten
        rescue
          nil
        end

        # Maps RbVmomi boot order values to fog understandable strings.
        # Result is uniqued, what effectively means that the order is defined by the first occurence.
        def parse_boot_order(vm_boot_devs)
          return unless vm_boot_devs.is_a?(Array)
          vm_boot_devs.map do |vm_boot_dev|
            case vm_boot_dev
            when RbVmomi::VIM::VirtualMachineBootOptionsBootableEthernetDevice
              'network'
            when RbVmomi::VIM::VirtualMachineBootOptionsBootableDiskDevice
              'disk'
            when RbVmomi::VIM::VirtualMachineBootOptionsBootableCdromDevice
              'cdrom'
            when RbVmomi::VIM::VirtualMachineBootOptionsBootableFloppyDevice
              'floppy'
            end
          end.compact.uniq
        end

        # Flattens Array of RbVmomi::VIM::OptionValue to simple hash
        def parse_extra_config(vm_extra_config)
          return unless vm_extra_config.is_a?(Array)
          vm_extra_config.map { |entry| [entry[:key], entry[:value]] }.to_h
        end

        def parse_disks(vm_disks)
          return unless vm_disks.is_a?(Array)
          vm_disks.grep(RbVmomi::VIM::VirtualDisk).map do |d|
            { :label => d.deviceInfo.label, :capacity => d.capacityInBytes }
          end
        end

        def parse_partitions(vm_partitions)
          return unless vm_partitions.is_a?(Array)
          vm_partitions.grep(RbVmomi::VIM::GuestDiskInfo).map do |p|
            { :path => p.diskPath, :free => p.freeSpace, :capacity => p.capacity }
          end
        end

        # returns vmware managed obj id string
        def managed_obj_id(obj)
          obj.to_s.match(/\("([^"]+)"\)/)[1]
        end

        def is_uuid?(id)
          !(id =~ /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/).nil?
        end
      end

      class Mock
        include Shared
        # rubocop:disable Metrics/MethodLength
        def self.data
          @data ||= Hash.new do |hash, key|
            hash[key] = {
              servers: {
                '5032c8a5-9c5e-ba7a-3804-832a03e16381' => {
                  'resource_pool'    => 'Resources',
                  'memory_mb'        => 2196,
                  'mac_addresses'    => { 'Network adapter 1' => '00:50:56:a9:00:28' },
                  'power_state'      => 'poweredOn',
                  'cpus'             => 1,
                  'hostname'         => 'dhcp75-197.virt.bos.redhat.com',
                  'mo_ref'           => 'vm-562',
                  'connection_state' => 'connected',
                  'overall_status'   => 'green',
                  'datacenter'       => 'Solutions',
                  'volumes'          =>
                    [{
                      'id'        => '6000C29c-a47d-4cd9-5249-c371de775f06',
                      'datastore' => 'Storage1',
                      'mode'      => 'persistent',
                      'size'      => 8_388_608,
                      'thin'      => true,
                      'name'      => 'Hard disk 1',
                      'filename'  => '[Storage1] rhel6-mfojtik/rhel6-mfojtik.vmdk',
                      'size_gb'   => 8
                    }],
                  'scsi_controllers' =>
                    [{ 'shared_bus' => 'noSharing',
                       'type'        => 'VirtualLsiLogicController',
                       'unit_number' => 7,
                       'key'         => 1000 }],
                  'interfaces'       =>
                    [{ 'mac' => '00:50:56:a9:00:28',
                       'network' => 'dvportgroup-123456',
                       'name'    => 'Network adapter 1',
                       'status'  => 'ok',
                       'summary' => 'VM Network' }],
                  'cdroms' =>
                    [{
                      'name'                => 'CD-/DVD-Drive 1',
                      'filename'            => nil,
                      'key'                 => 3000,
                      'controller_key'      => 200,
                      'unit_number'         => 0,
                      'start_connected'     => false,
                      'allow_guest_control' => true,
                      'connected'           => false
                    }],
                  'hypervisor'       => 'gunab.puppetlabs.lan',
                  'guest_id'         => 'rhel6_64Guest',
                  'tools_state'      => 'toolsOk',
                  'cluster'          => 'Solutionscluster',
                  'name'             => 'rhel64',
                  'operatingsystem'  => 'Red Hat Enterprise Linux 6 (64-bit)',
                  'path'             => '/Datacenters/Solutions/vm',
                  'uuid'             => '4229f0e9-bfdc-d9a7-7bac-12070772e6dc',
                  'instance_uuid'    => '5032c8a5-9c5e-ba7a-3804-832a03e16381',
                  'id'               => '5032c8a5-9c5e-ba7a-3804-832a03e16381',
                  'tools_version'    => 'guestToolsUnmanaged',
                  'ipaddress'        => '192.168.100.184',
                  'template'         => false
                },
                '502916a3-b42e-17c7-43ce-b3206e9524dc' => {
                  'resource_pool'    => 'Resources',
                  'memory_mb'        => 512,
                  'power_state'      => 'poweredOn',
                  'mac_addresses'    => { 'Network adapter 1' => '00:50:56:a9:00:00' },
                  'hostname'         => nil,
                  'cpus'             => 1,
                  'connection_state' => 'connected',
                  'mo_ref'           => 'vm-621',
                  'overall_status'   => 'green',
                  'datacenter'       => 'Solutions',
                  'volumes'          =>
                    [{ 'thin' => false,
                       'size_gb'   => 10,
                       'datastore' => 'datastore1',
                       'filename'  => '[datastore1] i-1342439683/i-1342439683.vmdk',
                       'size'      => 10_485_762,
                       'name'      => 'Hard disk 1',
                       'mode'      => 'persistent',
                       'id'        => '6000C29b-f364-d073-8316-8e98ac0a0eae' }],
                  'scsi_controllers' =>
                    [{ 'shared_bus' => 'noSharing',
                       'type'        => 'VirtualLsiLogicController',
                       'unit_number' => 7,
                       'key'         => 1000 }],
                  'interfaces'       =>
                    [{ 'summary' => 'VM Network',
                       'mac'     => '00:50:56:a9:00:00',
                       'status'  => 'ok',
                       'network' => 'dvportgroup-123456',
                       'name'    => 'Network adapter 1' }],
                  'hypervisor'       => 'gunab.puppetlabs.lan',
                  'guest_id'         => nil,
                  'cluster'          => 'Solutionscluster',
                  'tools_state'      => 'toolsNotInstalled',
                  'name'             => 'i-1342439683',
                  'operatingsystem'  => nil,
                  'path'             => '/',
                  'tools_version'    => 'guestToolsNotInstalled',
                  'uuid'             => '4229e0de-30cb-ceb2-21f9-4d8d8beabb52',
                  'instance_uuid'    => '502916a3-b42e-17c7-43ce-b3206e9524dc',
                  'id'               => '502916a3-b42e-17c7-43ce-b3206e9524dc',
                  'ipaddress'        => nil,
                  'template'         => false
                },
                '5029c440-85ee-c2a1-e9dd-b63e39364603' => {
                  'resource_pool'    => 'Resources',
                  'memory_mb'        => 2196,
                  'power_state'      => 'poweredOn',
                  'mac_addresses'    => { 'Network adapter 1' => '00:50:56:b2:00:af' },
                  'hostname'         => 'centos56gm.localdomain',
                  'cpus'             => 1,
                  'connection_state' => 'connected',
                  'mo_ref'           => 'vm-715',
                  'overall_status'   => 'green',
                  'datacenter'       => 'Solutions',
                  'hypervisor'       => 'gunab.puppetlabs.lan',
                  'guest_id'         => 'rhel6_64Guest',
                  'cluster'          => 'Solutionscluster',
                  'tools_state'      => 'toolsOk',
                  'name'             => 'jefftest',
                  'operatingsystem'  => 'Red Hat Enterprise Linux 6 (64-bit)',
                  'path'             => '/Solutions/wibble',
                  'tools_version'    => 'guestToolsUnmanaged',
                  'ipaddress'        => '192.168.100.187',
                  'uuid'             => '42329da7-e8ab-29ec-1892-d6a4a964912a',
                  'instance_uuid'    => '5029c440-85ee-c2a1-e9dd-b63e39364603',
                  'id'               => '5029c440-85ee-c2a1-e9dd-b63e39364603',
                  'template'         => false
                }
              },
              datacenters: {
                'Solutions' => { name: 'Solutions', status: 'grey', path: ['Solutions'] }
              },
              datastores: {
                'Storage1' => {
                  'id' => 'datastore-123456',
                  'name' => 'Storage1',
                  'datacenter' => 'Solutions',
                  'type' => 'VMFS',
                  'freespace' => 697_471_860_736,
                  'accessible' => true,
                  'capacity' => 1_099_243_192_320,
                  'uncommitted' => 977_158_537_741,
                  'cluster' => []
                },
                'datastore1' => {
                  'id' => 'datastore-789123',
                  'name' => 'datastore1',
                  'datacenter' => 'Solutions',
                  'type' => 'VMFS',
                  'freespace' => 697_471_860_736,
                  'accessible' => true,
                  'capacity' => 1_099_243_192_320,
                  'uncommitted' => 977_158_537_741,
                  'cluster' => ['Solutionscluster']
                }
              },
              networks: {
                'network1' => {
                  'id' => 'dvportgroup-123456',
                  'name' => 'network1',
                  'datacenter' => 'Solutions',
                  'accessible' => true,
                  'virtualswitch' => nil,
                  'cluster' => ['Solutionscluster']
                },
                'network2' => {
                  'id' => 'dvportgroup-789123',
                  'name' => 'network2',
                  'datacenter' => 'Solutions',
                  'accessible' => true,
                  'virtualswitch' => nil,
                  'cluster' => []
                }
              },
              folders: {
                'wibble' => {
                  'name' => 'wibble',
                  'datacenter' => 'Solutions',
                  'path' => '/Solutions/wibble',
                  'type' => 'vm'
                },
                'empty' => {
                  'name' => 'empty',
                  'datacenter' => 'Solutions',
                  'path' => '/Solutions/empty',
                  'type' => 'vm'
                }
              },
              storage_pods:                 [{ id: 'group-p123456',
                                               name: 'Datastore Cluster 1',
                                               freespace: '4856891834368',
                                               capacity: '7132061630464',
                                               datacenter: 'Solutions' }],
              clusters:                 [{ id: '1d4d9a3f-e4e8-4c40-b7fc-263850068fa4',
                                           name: 'Solutionscluster',
                                           num_host: '4',
                                           num_cpu_cores: '16',
                                           overall_status: 'green',
                                           datacenter: 'Solutions',
                                           full_path: 'Solutionscluster',
                                           klass: 'RbVmomi::VIM::ComputeResource' },
                                         { id: 'e4195973-102b-4096-bbd6-5429ff0b35c9',
                                           name: 'Problemscluster',
                                           num_host: '4',
                                           num_cpu_cores: '32',
                                           overall_status: 'green',
                                           datacenter: 'Solutions',
                                           full_path: 'Problemscluster',
                                           klass: 'RbVmomi::VIM::ComputeResource' },
                                         {
                                           klass: 'RbVmomi::VIM::Folder',
                                           clusters: [{ id: '03616b8d-b707-41fd-b3b5-The first',
                                                        name: 'Problemscluster',
                                                        num_host: '4',
                                                        num_cpu_cores: '32',
                                                        overall_status: 'green',
                                                        datacenter: 'Solutions',
                                                        full_path: 'Nested/Problemscluster',
                                                        klass: 'RbVmomi::VIM::ComputeResource' },
                                                      { id: '03616b8d-b707-41fd-b3b5-the Second',
                                                        name: 'Lastcluster',
                                                        num_host: '8',
                                                        num_cpu_cores: '32',
                                                        overall_status: 'green',
                                                        datacenter: 'Solutions',
                                                        full_path: 'Nested/Lastcluster',
                                                        klass: 'RbVmomi::VIM::ComputeResource' }]
                                         }],
              rules: {
                'anti-affinity-foo' => {
                  datacenter: 'Solutions',
                  cluster: 'Solutionscluster',
                  key: 4242,
                  name: 'anti-affinity-foo',
                  enabled: true,
                  type: RbVmomi::VIM::ClusterAntiAffinityRuleSpec,
                  vm_ids: ['5032c8a5-9c5e-ba7a-3804-832a03e16381', '502916a3-b42e-17c7-43ce-b3206e9524dc']
                }
              },
              hosts: {
                'Host1' => {
                  datacenter: 'Solutions',
                  cluster: 'Solutionscluster',
                  name: 'host1.example.com',
                  model: 'PowerEdge R730',
                  vendor: 'Dell Inc.',
                  ipaddress: '1.2.3.4',
                  ipaddress6: nil,
                  hostname: 'host1',
                  domainname: 'example.com',
                  product_name: 'VMware ESXi',
                  uuid: '4c4c4544-0051-3610-8046-c4c44f584a32',
                  cpu_cores: 20,
                  cpu_sockets: 2,
                  cpu_threads: 40,
                  cpu_hz: 2_599_999_534,
                  memory: 824_597_241_856,
                  product_version: '6.0.0',
                  vm_ids: ['5032c8a5-9c5e-ba7a-3804-832a03e16381', '502916a3-b42e-17c7-43ce-b3206e9524dc']
                }
              }
            }
          end
        end

        # rubocop:enable Metrics/MethodLength
        def initialize(options = {})
          require 'rbvmomi'
          @vsphere_username = options[:vsphere_username]
          @vsphere_password = 'REDACTED'
          @vsphere_server   = options[:vsphere_server]
          @vsphere_expected_pubkey_hash = options[:vsphere_expected_pubkey_hash]
          @vsphere_is_vcenter = true
          @vsphere_rev = '4.0'
        end

        def data
          self.class.data[@vsphere_username]
        end

        def reset_data
          self.class.data.delete(@vsphere_username)
        end
      end

      class Real
        include Shared

        def initialize(options = {})
          require 'rbvmomi'
          @vsphere_username = options[:vsphere_username]
          @vsphere_password = options[:vsphere_password]
          @vsphere_server   = options[:vsphere_server]
          @vsphere_port     = options[:vsphere_port] || 443
          @vsphere_path     = options[:vsphere_path] || '/sdk'
          @vsphere_ns       = options[:vsphere_ns] || 'urn:vim25'
          @vsphere_rev      = options[:vsphere_rev] || '4.0'
          @vsphere_ssl      = options[:vsphere_ssl] || true
          @vsphere_debug    = options[:vsphere_debug] || false
          @vsphere_expected_pubkey_hash = options[:vsphere_expected_pubkey_hash]
          @vsphere_must_reauthenticate = false
          @vsphere_is_vcenter = nil
          @connection = nil
          connect
          negotiate_revision(options[:vsphere_rev])
          authenticate
        end

        def connection
          if @connection.nil? || @connection.serviceContent.sessionManager.currentSession.nil?
            Fog::Logger.debug('Reconnecting to vSphere.')
            @connection = nil
            reload
          end
          @connection
        end

        def reload
          connect
          # Check if the negotiation was ever run
          negotiate if @vsphere_is_vcenter.nil?
          authenticate
        end

        private

        def negotiate_revision(revision = nil)
          # Negotiate the API revision
          unless revision
            rev = @connection.serviceContent.about.apiVersion
            @connection.rev = [rev, ENV['FOG_VSPHERE_REV'] || '4.1'].max
          end

          @vsphere_is_vcenter = @connection.serviceContent.about.apiType == 'VirtualCenter'
          @vsphere_rev = @connection.rev
        end

        def connect
          # This is a state variable to allow digest validation of the SSL cert
          bad_cert = false
          loop do
            begin
              @connection = RbVmomi::VIM.new host: @vsphere_server,
                                             port: @vsphere_port,
                                             path: @vsphere_path,
                                             ns: @vsphere_ns,
                                             rev: @vsphere_rev,
                                             ssl: @vsphere_ssl,
                                             insecure: bad_cert,
                                             debug: @vsphere_debug

              # Create a shadow class to change the behaviour of @connection.obj2xml
              # so that xsd:any types are converted to xsd:int (and not xsd:long).
              #
              # This is a known issue with RbVmomi.
              #
              # See https://communities.vmware.com/message/2505334 for discussion
              # and https://github.com/rlane/rbvmomi/pull/30 for an unmerged
              # pull request that fixes it in RbVmomi.
              #
              class <<@connection
                def obj2xml(xml, name, type, is_array, o, attrs = {})
                  case o
                  when Integer
                    attrs['xsi:type'] = 'xsd:int' if type(type) == RbVmomi::BasicTypes::AnyType
                    xml.tag! name, o.to_s, attrs
                    xml
                  else
                    super xml, name, type, is_array, o, attrs
                  end
                end
              end

              break
            rescue OpenSSL::SSL::SSLError
              raise if bad_cert
              bad_cert = true
            end
          end

          validate_ssl_connection if bad_cert
        end

        def authenticate
          @connection.serviceContent.sessionManager.Login userName: @vsphere_username,
                                                          password: @vsphere_password
        rescue RbVmomi::VIM::InvalidLogin => e
          raise Fog::Vsphere::Errors::ServiceError, e.message
        end

        # Verify a SSL certificate based on the hashed public key
        def validate_ssl_connection
          pubkey = @connection.http.peer_cert.public_key
          pubkey_hash = Digest::SHA2.hexdigest(pubkey.to_s)
          expected_pubkey_hash = @vsphere_expected_pubkey_hash
          if pubkey_hash != expected_pubkey_hash
            raise Fog::Vsphere::Errors::SecurityError, "The remote system presented a public key with hash #{pubkey_hash} but we're expecting a hash of #{expected_pubkey_hash || '<unset>'}.  If you are sure the remote system is authentic set vsphere_expected_pubkey_hash: <the hash printed in this message> in ~/.fog"
          end
        end

        def list_container_view(datacenter_obj_or_name, type, container_object = nil)
          dc = if datacenter_obj_or_name.is_a?(String)
                 find_raw_datacenter(datacenter_obj_or_name)
               else
                 datacenter_obj_or_name
               end

          container = if container_object
                        dc.public_send(container_object)
                      else
                        dc
                      end

          container_view = connection.serviceContent.viewManager.CreateContainerView(
            container: dc,
            type: [type],
            recursive: true
          )

          result = container_view.view
          container_view.DestroyView
          result
        end
      end
    end
  end
end