text: #
  #   Copyright 2011 Red Hat, Inc.
  #
  #   Licensed under the Apache License, Version 2.0 (the "License");
  #   you may not use this file except in compliance with the License.
  #   You may obtain a copy of the License at
  #
  #       http://www.apache.org/licenses/LICENSE-2.0
  #
  #   Unless required by applicable law or agreed to in writing, software
  #   distributed under the License is distributed on an "AS IS" BASIS,
  #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  #   See the License for the specific language governing permissions and
  #   limitations under the License.
  
  import zope
  import oz.GuestFactory
  import oz.TDL
  import os
  import guestfs
  import libxml2
  import traceback
  import json
  import ConfigParser
  import logging
  import subprocess
  from xml.etree.ElementTree import fromstring
  from imgfac.ApplicationConfiguration import ApplicationConfiguration
  from imgfac.ImageFactoryException import ImageFactoryException
  from imgfac.FactoryUtils import launch_inspect_and_mount, shutdown_and_close, remove_net_persist, create_cloud_info
  try:
      from VSphereHelper import VSphereHelper
  except:
      # psphere may be end of life
      # we only need it for pushing (aka target images) - this plugin is widely used
      # for creating VMDK target images so let's not fail just because psphere is not
      # present and/or has issues
      # TODO: Either move to a supported vSphere API binding or drop push support entirely
      logging.warning("VSphereHelper failed to load - pushing to vSphere will not work")
  from VMDKstream import convert_to_stream
  from imgfac.CloudDelegate import CloudDelegate
  from imgfac.FactoryUtils import check_qcow_size
  
  rhel5_module_script='''echo "alias scsi_hostadapter2 mptbase" >> /etc/modprobe.conf
  echo "alias scsi_hostadapter3 mptspi" >> /etc/modprobe.conf
  KERNEL=`grubby --default-kernel`
  KERNELVERSION=`grubby --default-kernel | cut -f 2- -d "-"`
  NEWINITRD="`grubby --info=$KERNEL | grep initrd | cut -f 2 -d "="`-vsphere"
  mkinitrd $NEWINITRD $KERNELVERSION
  grubby --add-kernel=$KERNEL --copy-default --make-default --initrd=$NEWINITRD --title="Red Hat Enterprise Linux Server ($KERNELVERSION) Image Factory vSphere module update"
  rm /root/vsphere-module.sh'''
  
  class vSphere(object):
      """docstring for Fedora_vsphere_Builder"""
      zope.interface.implements(CloudDelegate)
  
      def __init__(self):
          super(vSphere, self).__init__()
          self.app_config = ApplicationConfiguration().configuration
          self.log = logging.getLogger('%s.%s' % (__name__, self.__class__.__name__))
  
      def activity(self, activity):
          # Simple helper function
          # Activity should be a one line human-readable string indicating the task in progress
          # We log it at DEBUG and also set it as the status_detail on our active image
          self.log.debug(activity)
          self.active_image.status_detail['activity'] = activity
  
      def log_exc(self):
          self.log.debug("Exception caught in ImageFactory")
          self.log.debug(traceback.format_exc())
  
      def delete_from_provider(self, builder, provider, credentials, target, parameters):
          self.log.debug("Deleting vSphere image (%s)" % (self.builder.provider_image.identifier_on_provider))
  
          provider_data = self.get_dynamic_provider_data(provider)
          if provider_data is None:
              raise ImageFactoryException("VMWare instance not found in XML or JSON provided")
          self.generic_decode_credentials(credentials, provider_data, "vsphere")
          helper = VSphereHelper(provider_data['api-url'], self.username, self.password)
          # This call raises an exception on error
          helper.delete_vm(self.builder.provider_image.identifier_on_provider)
  
      def builder_should_create_target_image(self, builder, target, image_id, template, parameters):
          self.log.info('builder_should_create_target_image() called on vSphere plugin - returning True')
          return True
  
      def builder_will_create_target_image(self, builder, target, image_id, template, parameters):
          tdlobj = oz.TDL.TDL(xmlstring=template.xml, rootpw_required=self.app_config["tdl_require_root_pw"])
          if tdlobj.distro == "RHEL-5":
              merge_content = { "commands": [ { "name": "execute-module-script", "type": "raw" , "command": "/bin/sh /root/vsphere-module.sh" } ],
                                "files" : [ { "name": "/root/vsphere-module.sh", "type": "raw", "file": rhel5_module_script } ] }
              try:
                  builder.os_plugin.add_cloud_plugin_content(merge_content)
              except:
                  self.log.error("Failed to add RHEL-5 specific vSphere customization to cloud plugin tasks")
                  raise
  
      def builder_did_create_target_image(self, builder, target, image_id, template, parameters):
          self.log.info('builder_did_create_target_image() called in vSphere plugin')
          self.status="BUILDING"
  
          # TODO: This is a convenience variable for refactoring - rename
          self.new_image_id = builder.target_image.identifier
  
          # TODO: More convenience vars - revisit
          self.template = template
          self.target = target
          self.builder = builder
          self.image = builder.target_image.data
  
          # This lets our logging helper know what image is being operated on
          self.active_image = self.builder.target_image
  
          self.tdlobj = oz.TDL.TDL(xmlstring=self.template.xml, rootpw_required=self.app_config["tdl_require_root_pw"])
          # Add in target specific content
          #TODO - URGENT - make this work again
          #self.add_target_content()
          # Oz assumes unique names - TDL built for multiple backends guarantees
          # they are not unique.  We don't really care about the name so just
          # force uniqueness
          #  Oz now uses the tdlobject name property directly in several places
          # so we must change it
          self.tdlobj.name = "factory-build-" + self.new_image_id
  
          # In contrast to our original builders, we enter the cloud plugins with a KVM file already
          # created as the base_image.  As a result, all the Oz building steps are gone (and can be found
          # in the OS plugin(s)
  
          # OS plugin has already provided the initial file for us to work with
          # which we can currently assume is a raw KVM compatible image
  
          # Add the cloud-info file
          self.modify_oz_filesystem()
  
          self.log.info("Transforming image for use on VMWare")
          vmdk_format = parameters.get('vsphere_vmdk_format', 'streaming')
          builder.target_image.parameters['vsphere_vmdk_format'] = vmdk_format
          if vmdk_format == 'streaming':
              self.vmware_transform_image_stream_vmdk()
          elif vmdk_format == 'standard':
              self.vmware_transform_image_standard_vmdk()
          else:
              raise Exception("Requested unknown VMDK format (%s)" % (vmdk_format))
  
          self.percent_complete=100
          self.status="COMPLETED"
  
      def vmware_transform_image_standard_vmdk(self):
          # On entry the image points to our generic KVM raw image
          # Convert to stream-optimized VMDK and then update the image property
          target_image = self.image + ".tmp.vmdk"
          self.log.debug("Converting raw kvm image (%s) to standard VMDK (%s) using qemu-img" % (self.image, target_image))
          qemu_img_cmd = [ 'qemu-img', 'convert', '-O', 'vmdk', self.image, target_image ]
          subprocess.check_call(qemu_img_cmd)
          self.log.debug("VMDK conversion complete")
          os.unlink(self.image)
          os.rename(target_image, self.image)
  
      def vmware_transform_image_stream_vmdk(self):
          if (check_qcow_size(self.image)) is not None:
              import subprocess
              self.log.debug("Input image is qcow; converting to raw")
              raw_output_fname = "{0}.raw".format(self.image)
              qemucmd = ['qemu-img', 'convert', '-f', 'qcow2', '-O', 'raw', self.image, raw_output_fname]
              subprocess.check_call(qemucmd)
              os.unlink(self.image)
              os.rename(raw_output_fname, self.image)
          # On entry the image points to our generic KVM raw image
          # Convert to stream-optimized VMDK and then update the image property
          target_image = self.image + ".tmp.vmdk"
          self.log.debug("Converting raw kvm image (%s) to vmware stream-optimized image (%s)" % (self.image, target_image))
          convert_to_stream(self.image, target_image)
          self.log.debug("VMWare stream conversion complete")
          os.unlink(self.image)
          os.rename(self.image + ".tmp.vmdk", self.image)
  
      def modify_oz_filesystem(self):
          self.log.debug("Doing further Factory specific modification of Oz image")
          guestfs_handle = launch_inspect_and_mount(self.builder.target_image.data)
          remove_net_persist(guestfs_handle)
          create_cloud_info(guestfs_handle, self.target)
          shutdown_and_close(guestfs_handle)
  
      def push_image_to_provider(self, builder, provider, credentials, target, target_image, parameters):
          self.log.info('push_image_to_provider() called in vSphere')
  
          # TODO: This is a convenience variable for refactoring - rename
          self.new_image_id = builder.provider_image.identifier
  
          self.tdlobj = oz.TDL.TDL(xmlstring=builder.target_image.template, rootpw_required=self.app_config["tdl_require_root_pw"])
          self.builder = builder
          self.active_image = self.builder.provider_image
          self.vmware_push_image_upload(target_image, provider, credentials)
  
      def vmware_push_image_upload(self, target_image_id, provider, credentials):
          # BuildDispatcher is now the only location for the logic to map a provider to its data and target
          provider_data = self.get_dynamic_provider_data(provider)
          if provider_data is None:
              raise ImageFactoryException("VMWare instance not found in XML or JSON provided")
  
          self.generic_decode_credentials(credentials, provider_data, "vsphere")
  
          # Image is always here and it is the target_image datafile
          input_image = self.builder.target_image.data
  
          # Example of some JSON for westford_esx
          # {"westford_esx": {"api-url": "https://vsphere.virt.bos.redhat.com/sdk", "username": "Administrator", "password": "changeme",
          #       "datastore": "datastore1", "network_name": "VM Network" } }
  
          vm_name = "factory-image-" + self.new_image_id
          helper = VSphereHelper(provider_data['api-url'], self.username, self.password)
  
          # Newer Oz versions introduce a configurable disk size in TDL
          # We must still detect that it is present and pass it in this call
          try:
              disksize=getattr(self.tdlobj, "disksize")
          except AttributeError:
              disksize = 10
          disksize_str = str(int(disksize)*1024*1024 + 2) + "KB"
  
          helper.create_vm(input_image, vm_name, provider_data['compute_resource'], provider_data['datastore'], 
                           disksize_str, [ { "network_name": provider_data['network_name'], "type": "VirtualE1000"} ], 
                           "512MB", 1, 'otherLinux64Guest')
          self.builder.provider_image.identifier_on_provider = vm_name
          self.builder.provider_account_identifier = self.username
          self.percent_complete = 100
  
      def generic_decode_credentials(self, credentials, provider_data, target):
          # convenience function for simple creds (rhev-m and vmware currently)
          doc = libxml2.parseDoc(credentials)
  
          self.username = None
          _usernodes = doc.xpathEval("//provider_credentials/%s_credentials/username" % (target))
          if len(_usernodes) > 0:
              self.username = _usernodes[0].content
          else:
              try:
                  self.username = provider_data['username']
              except KeyError:
                  raise ImageFactoryException("No username specified in config file or in push call")
          self.provider_account_identifier = self.username
  
          _passnodes = doc.xpathEval("//provider_credentials/%s_credentials/password" % (target))
          if len(_passnodes) > 0:
              self.password = _passnodes[0].content
          else:
              try:
                  self.password = provider_data['password']
              except KeyError:
                  raise ImageFactoryException("No password specified in config file or in push call")
  
          doc.freeDoc()
  
      def get_dynamic_provider_data(self, provider):
          # Get provider details for RHEV-M or VSphere
          # First try to interpret this as an ad-hoc/dynamic provider def
          # If this fails, try to find it in one or the other of the config files
          # If this all fails return None
          # We use this in the builders as well so I have made it "public"
  
          try:
              xml_et = fromstring(provider)
              return xml_et.attrib
          except Exception as e:
              self.log.debug('Testing provider for XML: %s' % e)
              pass
  
          try:
              jload = json.loads(provider)
              return jload
          except ValueError as e:
              self.log.debug('Testing provider for JSON: %s' % e)
              pass
  
          return None
  
      def abort(self):
          pass
  