From 32d790e7c47f5ad63ebd44eaf399b3d14206fd13 Mon Sep 17 00:00:00 2001 From: Justin Yu Date: Mon, 14 Oct 2013 15:23:50 -0700 Subject: [PATCH 1/7] Add Client.initialize to Client OM --- lib/ruby_vcloud_sdk/client.rb | 1774 +++++++++-------- lib/ruby_vcloud_sdk/config.rb | 6 +- lib/ruby_vcloud_sdk/connection/connection.rb | 27 +- .../xml/wrapper_classes/vcloud.rb | 8 +- spec/unit/client_spec.rb | 1181 +---------- spec/unit/client_spec_old.rb | 1155 +++++++++++ 6 files changed, 2131 insertions(+), 2020 deletions(-) create mode 100644 spec/unit/client_spec_old.rb diff --git a/lib/ruby_vcloud_sdk/client.rb b/lib/ruby_vcloud_sdk/client.rb index 3279b1c..8ac2952 100644 --- a/lib/ruby_vcloud_sdk/client.rb +++ b/lib/ruby_vcloud_sdk/client.rb @@ -1,893 +1,919 @@ -require "rest_client" # Need this for the exception classes -require "set" +require 'rest_client' # Need this for the exception classes +require 'set' module VCloudSdk class Client attr_reader :ovdc - def initialize(url, username, password, entities, control, - connection = nil) - @logger = Config.logger - @url = url - @organization = entities["organization"] - @ovdc_name = entities["virtual_datacenter"] - @vapp_catalog_name = entities["vapp_catalog"] - @media_catalog_name = entities["media_catalog"] - @control = control - @retries = @control["retries"] - @time_limit = @control["time_limit_sec"] - construct_rest_logger - Config.configure({ "rest_logger" => @rest_logger, - "rest_throttle" => control["rest_throttle"] }) + RETRIES = + { + default: 5, + upload_vapp_files: 7, + cpi: 1 + } + + TIME_LIMIT_SEC = + { + default: 120, + delete_vapp_template: 120, + delete_vapp: 120, + delete_media: 120, + instantiate_vapp_template: 300, + power_on: 600, + power_off: 600, + undeploy: 720, + process_descriptor_vapp_template: 300, + http_request: 240 + } + + REST_THROTTLE = + { + min: 0, + max: 1 + } + + private_constant :RETRIES, :TIME_LIMIT_SEC, :REST_THROTTLE + + def initialize(url, username, password, option = {}, logger = nil) + @logger = logger || Logger.new(STDOUT) + @retries = option[:retries] || RETRIES + @time_limit = option[:time_limit_sec] || TIME_LIMIT_SEC - if connection - @connection = connection - else - @connection = Connection::Connection.new(@url, @organization, - @time_limit["http_request"]) - end + construct_rest_logger + Config.configure( + { + rest_logger: @rest_logger, + rest_throttle: option[:rest_throttle] || REST_THROTTLE + }) + + @connection = Connection::Connection.new( + @url, + @time_limit[:http_request]) @root = @connection.connect(username, password) @admin_root = @connection.get(@root.admin_root) @entity_resolver_link = @root.entity_resolver.href # We assume the organization does not change often so we can get it at # login and cache it - @admin_org = @connection.get(@admin_root.organization(@organization)) - @logger.info("Successfully connected.") - end - - def get_catalog_vapp(id) - resolve_entity(id) - end - - def get_vapp(obj) - if obj.is_a?(Xml::VApp) - obj - elsif obj.is_a?(String) - resolve_entity(obj) - else - raise CloudError, "Expecting Xml::VApp or String, got #{obj.inspect}." - end - end - - def upload_vapp_template(vapp_name, directory) - ovdc = get_ovdc - @logger.info("Uploading VM #{vapp_name} to #{ovdc["name"]} in " + - "organization #{@organization}") - # if directory behaves like an OVFDirectory, then use it - is_ovf_directory = [:ovf_file, :ovf_file_path, :vmdk_file, - :vmdk_file_path].reduce(true) do |present, name| - present && directory.respond_to?(name) - end - ovf_directory = is_ovf_directory ? directory : - OVFDirectory.new(directory) - upload_params = Xml::WrapperFactory.create_instance( - "UploadVAppTemplateParams") - upload_params.name = vapp_name - vapp_template = @connection.post(ovdc.upload_link, upload_params) - catalog_name = @vapp_catalog_name - vapp_template = upload_vapp_files(vapp_template, ovf_directory) - raise ObjectNotFoundError, "Error uploading vApp template" unless - vapp_template - @logger.info("#{vapp_template.name} has tasks in progress. " + - "Waiting until done.") - vapp_template.running_tasks.each do |task| - monitor_task(task, @time_limit["process_descriptor_vapp_template"]) - end - err_tasks = @connection.get(vapp_template).tasks.find_all { - |t| t.status != Xml::TASK_STATUS[:SUCCESS] } - unless err_tasks.empty? - @logger.error("Error uploading vApp template. " + - "Non-successful tasks:#{err_tasks}.") - raise CloudError, "Error uploading vApp template" - end - @logger.info("vApp #{vapp_name} uploaded, adding to " + - "catalog #{catalog_name}") - catalog_item = add_catalog_item(vapp_template, catalog_name) - @logger.info("vApp #{vapp_name} added to catalog " + - "#{catalog_name} #{catalog_item.to_s}") - catalog_item - rescue ApiError => e - log_exception(e, "Error in uploading vApp template #{vapp_name}") - rollback_upload_vapp(vapp_template) - raise e - end - - def insert_catalog_media(vm, catalog_media_name) - catalog_media = get_catalog_media(catalog_media_name) - media = @connection.get(catalog_media.entity) - current_vm = @connection.get(vm) - insert_media(current_vm, media) - end - - def eject_catalog_media(vm, catalog_media_name) - catalog_media = get_catalog_media(catalog_media_name) - unless catalog_media - raise CatalogMediaNotFoundError, - "Catalog media #{catalog_media_name} not found." - end - media = @connection.get(catalog_media.entity) - current_vm = @connection.get(vm) - eject_media(current_vm, media) - end - - def upload_catalog_media(media_name, file, storage_profile = nil, - image_type = "iso") - ovdc = get_ovdc - @logger.info("Uploading media #{media_name} to #{storage_profile}/" + - "#{ovdc["name"]} in organization #{@organization}") - catalog_name = @media_catalog_name - upload_params = Xml::WrapperFactory.create_instance("Media") - upload_params.name = media_name - media_file = file.is_a?(String) ? File.new(file, "rb") : file - upload_params.size = media_file.stat.size - upload_params.image_type = image_type - upload_params.storage_profile = storage_profile - media = @connection.post(ovdc.upload_media_link, upload_params) - incomplete_file = media.incomplete_files.pop - @connection.put_file(incomplete_file.upload_link, media_file) - media = @connection.get(media) - add_catalog_item(media, catalog_name) - rescue ArgumentError, ApiError => e - log_exception(e, "Error uploading media #{media_name}" + - "to catalog #{catalog_name}. #{e.message}") - delete_media(media) if media - raise e - end - - def delete_catalog_media(name) - raise ArgumentError, "Media name cannot be nil." unless name - catalog_media = get_catalog_media(name) - if catalog_media - media = @connection.get(catalog_media.entity) - delete_media(media) - @connection.delete(catalog_media) - end - rescue RestClient::ResourceNotFound => e - # Media might be deleted already - @logger.debug("Catalog media #{name} no longer exists.") - end - - def delete_catalog_vapp(id) - raise ArgumentError, "Catalog ID cannot be nil." unless id - catalog_vapp = get_catalog_vapp(id) - if catalog_vapp - vapp = @connection.get(catalog_vapp.entity) - delete_vapp_template(vapp) - @connection.delete(catalog_vapp) - end - rescue => e - # vApp template might be deleted already - @logger.debug("Catalog vApp #{id} no longer exists.") - end - - def delete_vapp(vapp) - @logger.info("Deleting vApp #{vapp.name}.") - current_vapp = @connection.get(vapp) - if is_vapp_status(current_vapp, :POWERED_ON) - raise CloudError, - "vApp #{vapp.name} is powered on, power-off before deleting." - end - delete_vapp_or_template(current_vapp, @retries["default"], - @time_limit["delete_vapp"], "vApp") - end - - def instantiate_vapp_template(source_template_id, vapp_name, - description = nil, disk_locality = nil) - catalog_item = get_catalog_vapp(source_template_id) - unless catalog_item - @logger.error("Catalog item with ID #{source_template_id} not " + - "found in catalog #{@vapp_catalog_name}.") - raise ObjectNotFoundError, "Item with ID #{source_template_id} " + - "not found in catalog #{@vapp_catalog_name}." - end - src_vapp_template = @connection.get(catalog_item.entity) - instantiate_vapp_params = Xml::WrapperFactory.create_instance( - "InstantiateVAppTemplateParams") - instantiate_vapp_params.name = vapp_name - instantiate_vapp_params.description = description - instantiate_vapp_params.source = src_vapp_template - instantiate_vapp_params.all_eulas_accepted = true - instantiate_vapp_params.linked_clone = false - instantiate_vapp_params.set_locality = locality_spec(src_vapp_template, - disk_locality) - vdc = get_ovdc - vapp = @connection.post(vdc.instantiate_vapp_template_link, - instantiate_vapp_params) - vapp.running_tasks.each do |task| - begin - monitor_task(task, @time_limit["instantiate_vapp_template"]) - rescue ApiError => e - log_exception(e, "Instantiate vApp template #{vapp_name} failed." + - " Task #{task.operation} did not complete successfully.") - delete_vapp(vapp) - raise e - end - end - @connection.get(vapp) - end - - def reconfigure_vm(vm, &b) - b.call(vm) - monitor_task(@connection.post("#{vm.reconfigure_link.href}", vm, - Xml::MEDIA_TYPE[:VM])) - end - - def get_metadata(entity, key) - metadata = @connection.get(generate_metadata_href(entity, key)) - metadata.value - end - - def set_metadata(entity, key, value) - metadata = Xml::WrapperFactory.create_instance("MetadataValue") - metadata.value = value - task = @connection.put(generate_metadata_href(entity, key), metadata, - Xml::MEDIA_TYPE[:METADATA_ITEM_VALUE]) - monitor_task(task) - end - - def delete_networks(vapp, exclude_nets = []) - current_vapp = get_vapp(vapp) - raise ObjectNotFoundError, "Cannot delete nets, vApp #{vapp.name} no " + - "longer exists" unless current_vapp - current = current_vapp.network_config_section.network_configs.map { - |n| n.network_name } - nets = current - exclude_nets - @logger.debug("nets:: current:#{current}, exclude:#{exclude_nets}, " + - "to delete:#{nets}") - return if nets.nil? || nets.length == 0 - delete_network(current_vapp, *nets) - end - - def add_network(vapp, network, vapp_net_name = nil, - fence_mode = Xml::FENCE_MODES[:BRIDGED]) - current_network = @connection.get(network) - raise ObjectNotFoundError, "Cannot add network to vApp #{vapp.name}. " + - "The network #{network.name} no longer exists." unless current_network - current_vapp = get_vapp(vapp) - raise ObjectNotFoundError, "Cannot add network to vApp #{vapp.name}. " + - "The vApp #{vapp.name} no longer exists." unless current_vapp - network_config = Xml::WrapperFactory.create_instance("NetworkConfig") - new_vapp_net_name = vapp_net_name.nil? ? - current_network["name"] : vapp_net_name - copy_network_settings(current_network, network_config, - new_vapp_net_name, fence_mode) - current_vapp.network_config_section.add_network_config(network_config) - task = @connection.put(current_vapp.network_config_section, - current_vapp.network_config_section, - Xml::MEDIA_TYPE[:NETWORK_CONFIG_SECTION]) - monitor_task(task) - end - - # There must be no NICs on the network when it is deleted. Otherwise the - # task will fail. Use set_nic_network to move NICs onto other network or - # the NONE network prior to deleting the network from the vApp. - def delete_network(vapp, *network_names) - raise ArgumentError, "Must specify a network name to delete." if - network_names.nil? || network_names.length == 0 - unique_network_names = network_names.uniq - @logger.info("Delete networks(s) #{unique_network_names.join(" ")} " + - "from vApp #{vapp.name}") - current_vapp = get_vapp(vapp) - unique_network_names.each do |n| - current_vapp.network_config_section.delete_network_config(n) - end - task = @connection.put(current_vapp.network_config_section, - current_vapp.network_config_section, - Xml::MEDIA_TYPE[:NETWORK_CONFIG_SECTION]) - monitor_task(task) - end - - # Size at creation is in bytes - # We currently assumes the disk is SCSI and bus sub type LSILOGIC - def create_disk(name, size_mb, vm = nil, retries = @retries["default"]) - new_disk = Xml::WrapperFactory.create_instance("DiskCreateParams") - new_disk.name = name - new_disk.size_bytes = size_mb * 1024 * 1024 # VCD expects bytes - new_disk.bus_type = Xml::HARDWARE_TYPE[:SCSI_CONTROLLER] - new_disk.bus_sub_type = Xml::BUS_SUB_TYPE[:LSILOGIC] - new_disk.add_locality(vm) if vm - vdc = get_ovdc - @logger.info("Creating independent disk #{name} of #{size_mb}MB.") - @logger.info("Disk locality ist set to #{vm.name} #{vm.urn}.") if vm - disk = @connection.post(vdc.add_disk_link, new_disk, - Xml::MEDIA_TYPE[:DISK_CREATE_PARAMS]) - raise ApiRequestError unless disk.respond_to?(:running_tasks) - # Creating a disk returns a disk with tasks inside - retries.times do |try| - return disk if disk.running_tasks.nil? || disk.running_tasks.empty? - @logger.info("Disk #{disk.urn} has running tasks. Waiting for " + - "tasks to finish. Try: #{try}/#{retries} ." ) - disk.running_tasks.each do |t| - monitor_task(t) - end - disk = @connection.get(disk) - end - end - - def delete_disk(disk) - current_disk = @connection.get(disk) - unless current_disk - @logger.warn("Disk #{disk.name} #{disk.urn} no longer exists.") - return - end - task = @connection.delete(current_disk.delete_link) - monitor_task(task) do |t| - @logger.info("Deleted disk #{current_disk.name} #{current_disk.urn}") - t - end - end - - def attach_disk(disk, vm) - current_vm = @connection.get(vm) - raise ObjectNotFoundError, "VM #{vm.name} not found." unless current_vm - - current_disk = @connection.get(disk) - unless current_disk - raise ObjectNotFoundError, "Disk #{disk.name} not found." - end - - params = Xml::WrapperFactory.create_instance("DiskAttachOrDetachParams") - params.disk_href = current_disk.href - task = @connection.post(current_vm.attach_disk_link, params, - Xml::MEDIA_TYPE[:DISK_ATTACH_DETACH_PARAMS]) - monitor_task(task) do |t| - @logger.info("Attached disk #{current_disk.name} to VM " + - "#{current_vm.name}.") - t - end - end - - def detach_disk(disk, vm) - current_vm = @connection.get(vm) - raise ObjectNotFoundError, "VM #{vm.name} not found." unless current_vm - - current_disk = @connection.get(disk) - unless current_disk - raise ObjectNotFoundError, "Disk #{disk.name} not found." - end - - disk_href = current_disk.href - - if is_vapp_status(current_vm, :SUSPENDED) - @logger.debug("vApp #{current_vm.name} suspended, discard state " + - "before detaching disk.") - raise VmSuspendedError, "discard state first" - end - - begin - get_disk_id(current_vm, disk_href) - rescue DiskNotFoundError - @logger.warn("Disk #{current_disk.name} not found on VM " + - "#{current_vm.name}. No need to detach.") - return - end - params = Xml::WrapperFactory.create_instance("DiskAttachOrDetachParams") - params.disk_href = disk_href - task = @connection.post(current_vm.detach_disk_link, params, - Xml::MEDIA_TYPE[:DISK_ATTACH_DETACH_PARAMS]) - monitor_task(task) do |t| - @logger.info("Detached disk #{current_disk.name} from VM " + - "#{current_vm.name}.") - t - end - end - - def get_disk(disk_id) - resolve_entity(disk_id) - end - - def power_on_vapp(vapp) - @logger.info("Powering on vApp #{vapp.name} .") - current_vapp = @connection.get(vapp) - unless current_vapp - raise ObjectNotFoundError, "vApp #{vapp.name} not found." - end - @logger.debug("vApp status: #{current_vapp["status"]}") - if is_vapp_status(current_vapp, :POWERED_ON) - @logger.info("vApp #{vapp.name} already powered-on.") - return - end - unless current_vapp.power_on_link - raise CloudError, "vApp #{vapp.name} not in a state to be " + - "powered on." - end - task = @connection.post(current_vapp.power_on_link, nil) - monitor_task(task, @time_limit["power_on"]) - @logger.info("vApp #{current_vapp.name} powered on.") - task - end - - def power_off_vapp(vapp, undeploy = true) - @logger.info("Powering off vApp #{vapp.name} .") - @logger.info("Undeploying vApp #{vapp.name} .") if undeploy - current_vapp = @connection.get(vapp) - unless current_vapp - raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." - end - @logger.debug("vApp status: #{current_vapp["status"]}") - - if is_vapp_status(current_vapp, :SUSPENDED) - @logger.debug("vApp #{current_vapp.name} suspended, discard state " + - "before powering off.") - raise VappSuspendedError, "discard state first" - end - - if undeploy - # Since we do not apparently differentiate between powered-off and - # undeployed in our status, we should check if the undeploy link is - # available first. If undeploy is not available and status is - # powered_off then it is undeployed. - unless current_vapp.undeploy_link - if is_vapp_status(current_vapp, :POWERED_OFF) - @logger.info("vApp #{vapp.name} already powered-off, undeployed.") - return - end - raise CloudError, "vApp #{vapp.name} not in a state be " + - "powered-off, undeployed." - end - params = Xml::WrapperFactory.create_instance("UndeployVAppParams") - task = @connection.post(current_vapp.undeploy_link, params) - monitor_task(task, @time_limit["undeploy"]) - @logger.info("vApp #{current_vapp.name} powered-off, undeployed.") - task - else - unless current_vapp.power_off_link - if is_vapp_status(current_vapp, :POWERED_OFF) - @logger.info("vApp #{vapp.name} already powered off.") - return - end - raise CloudError, "vApp #{vapp.name} not in a state be powered off." - end - task = @connection.post(current_vapp.power_off_link, nil) - monitor_task(task, @time_limit["power_off"]) - @logger.info("vApp #{current_vapp.name} powered off.") - task - end - end - - def discard_suspended_state_vapp(vapp) - @logger.info("Discarding suspended state of vApp #{vapp.name}.") - current_vapp = @connection.get(vapp) - unless current_vapp - raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." - end - @logger.debug("vApp status: #{current_vapp["status"]}") - - return unless is_vapp_status(current_vapp, :SUSPENDED) - - @logger.info("Discarding suspended state of vApp #{current_vapp.name}.") - task = @connection.post(current_vapp.discard_state, nil) - monitor_task(task, @time_limit["undeploy"]) - current_vapp = @connection.get(current_vapp) - @logger.info("vApp #{current_vapp.name} suspended state discarded.") - task - end - - def reboot_vapp(vapp) - @logger.info("Rebooting vApp #{vapp.name}.") - current_vapp = @connection.get(vapp) - unless current_vapp - raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." - end - @logger.debug("vApp status: #{current_vapp["status"]}") - - if is_vapp_status(current_vapp, :SUSPENDED) - @logger.debug("vApp #{current_vapp.name} suspended.") - raise VappSuspendedError, "vapp suspended" - end - if is_vapp_status(current_vapp, :POWERED_OFF) - @logger.debug("vApp #{current_vapp.name} powered off.") - raise VappPoweredOffError, "vapp powered off" - end - - @logger.info("Rebooting vApp #{current_vapp.name}.") - task = @connection.post(current_vapp.reboot_link, nil) - monitor_task(task) - current_vapp = @connection.get(current_vapp) - @logger.info("vApp #{current_vapp.name} rebooted.") - task - end - - def get_ovdc - vdc = @admin_org.vdc(@ovdc_name) - raise ObjectNotFoundError, "VDC #{@ovdc_name} not found." unless vdc - @connection.get(vdc) - end - - def get_catalog(name) - catalog = @connection.get(@admin_org.catalog(name)) - end + @admin_org = @connection.get(@admin_root.organization) + @logger.info('Successfully connected.') + end + + # def get_catalog_vapp(id) + # resolve_entity(id) + # end + # + # def get_vapp(obj) + # if obj.is_a?(Xml::VApp) + # obj + # elsif obj.is_a?(String) + # resolve_entity(obj) + # else + # raise CloudError, "Expecting Xml::VApp or String, got #{obj.inspect}." + # end + # end + # + # def upload_vapp_template(vapp_name, directory) + # ovdc = get_ovdc + # @logger.info("Uploading VM #{vapp_name} to #{ovdc["name"]} in " + + # "organization #{@organization}") + # # if directory behaves like an OVFDirectory, then use it + # is_ovf_directory = [:ovf_file, :ovf_file_path, :vmdk_file, + # :vmdk_file_path].reduce(true) do |present, name| + # present && directory.respond_to?(name) + # end + # ovf_directory = is_ovf_directory ? directory : + # OVFDirectory.new(directory) + # upload_params = Xml::WrapperFactory.create_instance( + # "UploadVAppTemplateParams") + # upload_params.name = vapp_name + # vapp_template = @connection.post(ovdc.upload_link, upload_params) + # catalog_name = @vapp_catalog_name + # vapp_template = upload_vapp_files(vapp_template, ovf_directory) + # raise ObjectNotFoundError, "Error uploading vApp template" unless + # vapp_template + # @logger.info("#{vapp_template.name} has tasks in progress. " + + # "Waiting until done.") + # vapp_template.running_tasks.each do |task| + # monitor_task(task, @time_limit["process_descriptor_vapp_template"]) + # end + # err_tasks = @connection.get(vapp_template).tasks.find_all { + # |t| t.status != Xml::TASK_STATUS[:SUCCESS] } + # unless err_tasks.empty? + # @logger.error("Error uploading vApp template. " + + # "Non-successful tasks:#{err_tasks}.") + # raise CloudError, "Error uploading vApp template" + # end + # @logger.info("vApp #{vapp_name} uploaded, adding to " + + # "catalog #{catalog_name}") + # catalog_item = add_catalog_item(vapp_template, catalog_name) + # @logger.info("vApp #{vapp_name} added to catalog " + + # "#{catalog_name} #{catalog_item.to_s}") + # catalog_item + # rescue ApiError => e + # log_exception(e, "Error in uploading vApp template #{vapp_name}") + # rollback_upload_vapp(vapp_template) + # raise e + # end + # + # def insert_catalog_media(vm, catalog_media_name) + # catalog_media = get_catalog_media(catalog_media_name) + # media = @connection.get(catalog_media.entity) + # current_vm = @connection.get(vm) + # insert_media(current_vm, media) + # end + # + # def eject_catalog_media(vm, catalog_media_name) + # catalog_media = get_catalog_media(catalog_media_name) + # unless catalog_media + # raise CatalogMediaNotFoundError, + # "Catalog media #{catalog_media_name} not found." + # end + # media = @connection.get(catalog_media.entity) + # current_vm = @connection.get(vm) + # eject_media(current_vm, media) + # end + # + # def upload_catalog_media(media_name, file, storage_profile = nil, + # image_type = "iso") + # ovdc = get_ovdc + # @logger.info("Uploading media #{media_name} to #{storage_profile}/" + + # "#{ovdc["name"]} in organization #{@organization}") + # catalog_name = @media_catalog_name + # upload_params = Xml::WrapperFactory.create_instance("Media") + # upload_params.name = media_name + # media_file = file.is_a?(String) ? File.new(file, "rb") : file + # upload_params.size = media_file.stat.size + # upload_params.image_type = image_type + # upload_params.storage_profile = storage_profile + # media = @connection.post(ovdc.upload_media_link, upload_params) + # incomplete_file = media.incomplete_files.pop + # @connection.put_file(incomplete_file.upload_link, media_file) + # media = @connection.get(media) + # add_catalog_item(media, catalog_name) + # rescue ArgumentError, ApiError => e + # log_exception(e, "Error uploading media #{media_name}" + + # "to catalog #{catalog_name}. #{e.message}") + # delete_media(media) if media + # raise e + # end + # + # def delete_catalog_media(name) + # raise ArgumentError, "Media name cannot be nil." unless name + # catalog_media = get_catalog_media(name) + # if catalog_media + # media = @connection.get(catalog_media.entity) + # delete_media(media) + # @connection.delete(catalog_media) + # end + # rescue RestClient::ResourceNotFound => e + # # Media might be deleted already + # @logger.debug("Catalog media #{name} no longer exists.") + # end + # + # def delete_catalog_vapp(id) + # raise ArgumentError, "Catalog ID cannot be nil." unless id + # catalog_vapp = get_catalog_vapp(id) + # if catalog_vapp + # vapp = @connection.get(catalog_vapp.entity) + # delete_vapp_template(vapp) + # @connection.delete(catalog_vapp) + # end + # rescue => e + # # vApp template might be deleted already + # @logger.debug("Catalog vApp #{id} no longer exists.") + # end + # + # def delete_vapp(vapp) + # @logger.info("Deleting vApp #{vapp.name}.") + # current_vapp = @connection.get(vapp) + # if is_vapp_status(current_vapp, :POWERED_ON) + # raise CloudError, + # "vApp #{vapp.name} is powered on, power-off before deleting." + # end + # delete_vapp_or_template(current_vapp, @retries["default"], + # @time_limit["delete_vapp"], "vApp") + # end + # + # def instantiate_vapp_template(source_template_id, vapp_name, + # description = nil, disk_locality = nil) + # catalog_item = get_catalog_vapp(source_template_id) + # unless catalog_item + # @logger.error("Catalog item with ID #{source_template_id} not " + + # "found in catalog #{@vapp_catalog_name}.") + # raise ObjectNotFoundError, "Item with ID #{source_template_id} " + + # "not found in catalog #{@vapp_catalog_name}." + # end + # src_vapp_template = @connection.get(catalog_item.entity) + # instantiate_vapp_params = Xml::WrapperFactory.create_instance( + # "InstantiateVAppTemplateParams") + # instantiate_vapp_params.name = vapp_name + # instantiate_vapp_params.description = description + # instantiate_vapp_params.source = src_vapp_template + # instantiate_vapp_params.all_eulas_accepted = true + # instantiate_vapp_params.linked_clone = false + # instantiate_vapp_params.set_locality = locality_spec(src_vapp_template, + # disk_locality) + # vdc = get_ovdc + # vapp = @connection.post(vdc.instantiate_vapp_template_link, + # instantiate_vapp_params) + # vapp.running_tasks.each do |task| + # begin + # monitor_task(task, @time_limit["instantiate_vapp_template"]) + # rescue ApiError => e + # log_exception(e, "Instantiate vApp template #{vapp_name} failed." + + # " Task #{task.operation} did not complete successfully.") + # delete_vapp(vapp) + # raise e + # end + # end + # @connection.get(vapp) + # end + # + # def reconfigure_vm(vm, &b) + # b.call(vm) + # monitor_task(@connection.post("#{vm.reconfigure_link.href}", vm, + # Xml::MEDIA_TYPE[:VM])) + # end + # + # def get_metadata(entity, key) + # metadata = @connection.get(generate_metadata_href(entity, key)) + # metadata.value + # end + # + # def set_metadata(entity, key, value) + # metadata = Xml::WrapperFactory.create_instance("MetadataValue") + # metadata.value = value + # task = @connection.put(generate_metadata_href(entity, key), metadata, + # Xml::MEDIA_TYPE[:METADATA_ITEM_VALUE]) + # monitor_task(task) + # end + # + # def delete_networks(vapp, exclude_nets = []) + # current_vapp = get_vapp(vapp) + # raise ObjectNotFoundError, "Cannot delete nets, vApp #{vapp.name} no " + + # "longer exists" unless current_vapp + # current = current_vapp.network_config_section.network_configs.map { + # |n| n.network_name } + # nets = current - exclude_nets + # @logger.debug("nets:: current:#{current}, exclude:#{exclude_nets}, " + + # "to delete:#{nets}") + # return if nets.nil? || nets.length == 0 + # delete_network(current_vapp, *nets) + # end + # + # def add_network(vapp, network, vapp_net_name = nil, + # fence_mode = Xml::FENCE_MODES[:BRIDGED]) + # current_network = @connection.get(network) + # raise ObjectNotFoundError, "Cannot add network to vApp #{vapp.name}. " + + # "The network #{network.name} no longer exists." unless current_network + # current_vapp = get_vapp(vapp) + # raise ObjectNotFoundError, "Cannot add network to vApp #{vapp.name}. " + + # "The vApp #{vapp.name} no longer exists." unless current_vapp + # network_config = Xml::WrapperFactory.create_instance("NetworkConfig") + # new_vapp_net_name = vapp_net_name.nil? ? + # current_network["name"] : vapp_net_name + # copy_network_settings(current_network, network_config, + # new_vapp_net_name, fence_mode) + # current_vapp.network_config_section.add_network_config(network_config) + # task = @connection.put(current_vapp.network_config_section, + # current_vapp.network_config_section, + # Xml::MEDIA_TYPE[:NETWORK_CONFIG_SECTION]) + # monitor_task(task) + # end + # + # # There must be no NICs on the network when it is deleted. Otherwise the + # # task will fail. Use set_nic_network to move NICs onto other network or + # # the NONE network prior to deleting the network from the vApp. + # def delete_network(vapp, *network_names) + # raise ArgumentError, "Must specify a network name to delete." if + # network_names.nil? || network_names.length == 0 + # unique_network_names = network_names.uniq + # @logger.info("Delete networks(s) #{unique_network_names.join(" ")} " + + # "from vApp #{vapp.name}") + # current_vapp = get_vapp(vapp) + # unique_network_names.each do |n| + # current_vapp.network_config_section.delete_network_config(n) + # end + # task = @connection.put(current_vapp.network_config_section, + # current_vapp.network_config_section, + # Xml::MEDIA_TYPE[:NETWORK_CONFIG_SECTION]) + # monitor_task(task) + # end + # + # # Size at creation is in bytes + # # We currently assumes the disk is SCSI and bus sub type LSILOGIC + # def create_disk(name, size_mb, vm = nil, retries = @retries["default"]) + # new_disk = Xml::WrapperFactory.create_instance("DiskCreateParams") + # new_disk.name = name + # new_disk.size_bytes = size_mb * 1024 * 1024 # VCD expects bytes + # new_disk.bus_type = Xml::HARDWARE_TYPE[:SCSI_CONTROLLER] + # new_disk.bus_sub_type = Xml::BUS_SUB_TYPE[:LSILOGIC] + # new_disk.add_locality(vm) if vm + # vdc = get_ovdc + # @logger.info("Creating independent disk #{name} of #{size_mb}MB.") + # @logger.info("Disk locality ist set to #{vm.name} #{vm.urn}.") if vm + # disk = @connection.post(vdc.add_disk_link, new_disk, + # Xml::MEDIA_TYPE[:DISK_CREATE_PARAMS]) + # raise ApiRequestError unless disk.respond_to?(:running_tasks) + # # Creating a disk returns a disk with tasks inside + # retries.times do |try| + # return disk if disk.running_tasks.nil? || disk.running_tasks.empty? + # @logger.info("Disk #{disk.urn} has running tasks. Waiting for " + + # "tasks to finish. Try: #{try}/#{retries} ." ) + # disk.running_tasks.each do |t| + # monitor_task(t) + # end + # disk = @connection.get(disk) + # end + # end + # + # def delete_disk(disk) + # current_disk = @connection.get(disk) + # unless current_disk + # @logger.warn("Disk #{disk.name} #{disk.urn} no longer exists.") + # return + # end + # task = @connection.delete(current_disk.delete_link) + # monitor_task(task) do |t| + # @logger.info("Deleted disk #{current_disk.name} #{current_disk.urn}") + # t + # end + # end + # + # def attach_disk(disk, vm) + # current_vm = @connection.get(vm) + # raise ObjectNotFoundError, "VM #{vm.name} not found." unless current_vm + # + # current_disk = @connection.get(disk) + # unless current_disk + # raise ObjectNotFoundError, "Disk #{disk.name} not found." + # end + # + # params = Xml::WrapperFactory.create_instance("DiskAttachOrDetachParams") + # params.disk_href = current_disk.href + # task = @connection.post(current_vm.attach_disk_link, params, + # Xml::MEDIA_TYPE[:DISK_ATTACH_DETACH_PARAMS]) + # monitor_task(task) do |t| + # @logger.info("Attached disk #{current_disk.name} to VM " + + # "#{current_vm.name}.") + # t + # end + # end + # + # def detach_disk(disk, vm) + # current_vm = @connection.get(vm) + # raise ObjectNotFoundError, "VM #{vm.name} not found." unless current_vm + # + # current_disk = @connection.get(disk) + # unless current_disk + # raise ObjectNotFoundError, "Disk #{disk.name} not found." + # end + # + # disk_href = current_disk.href + # + # if is_vapp_status(current_vm, :SUSPENDED) + # @logger.debug("vApp #{current_vm.name} suspended, discard state " + + # "before detaching disk.") + # raise VmSuspendedError, "discard state first" + # end + # + # begin + # get_disk_id(current_vm, disk_href) + # rescue DiskNotFoundError + # @logger.warn("Disk #{current_disk.name} not found on VM " + + # "#{current_vm.name}. No need to detach.") + # return + # end + # params = Xml::WrapperFactory.create_instance("DiskAttachOrDetachParams") + # params.disk_href = disk_href + # task = @connection.post(current_vm.detach_disk_link, params, + # Xml::MEDIA_TYPE[:DISK_ATTACH_DETACH_PARAMS]) + # monitor_task(task) do |t| + # @logger.info("Detached disk #{current_disk.name} from VM " + + # "#{current_vm.name}.") + # t + # end + # end + # + # def get_disk(disk_id) + # resolve_entity(disk_id) + # end + # + # def power_on_vapp(vapp) + # @logger.info("Powering on vApp #{vapp.name} .") + # current_vapp = @connection.get(vapp) + # unless current_vapp + # raise ObjectNotFoundError, "vApp #{vapp.name} not found." + # end + # @logger.debug("vApp status: #{current_vapp["status"]}") + # if is_vapp_status(current_vapp, :POWERED_ON) + # @logger.info("vApp #{vapp.name} already powered-on.") + # return + # end + # unless current_vapp.power_on_link + # raise CloudError, "vApp #{vapp.name} not in a state to be " + + # "powered on." + # end + # task = @connection.post(current_vapp.power_on_link, nil) + # monitor_task(task, @time_limit["power_on"]) + # @logger.info("vApp #{current_vapp.name} powered on.") + # task + # end + # + # def power_off_vapp(vapp, undeploy = true) + # @logger.info("Powering off vApp #{vapp.name} .") + # @logger.info("Undeploying vApp #{vapp.name} .") if undeploy + # current_vapp = @connection.get(vapp) + # unless current_vapp + # raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." + # end + # @logger.debug("vApp status: #{current_vapp["status"]}") + # + # if is_vapp_status(current_vapp, :SUSPENDED) + # @logger.debug("vApp #{current_vapp.name} suspended, discard state " + + # "before powering off.") + # raise VappSuspendedError, "discard state first" + # end + # + # if undeploy + # # Since we do not apparently differentiate between powered-off and + # # undeployed in our status, we should check if the undeploy link is + # # available first. If undeploy is not available and status is + # # powered_off then it is undeployed. + # unless current_vapp.undeploy_link + # if is_vapp_status(current_vapp, :POWERED_OFF) + # @logger.info("vApp #{vapp.name} already powered-off, undeployed.") + # return + # end + # raise CloudError, "vApp #{vapp.name} not in a state be " + + # "powered-off, undeployed." + # end + # params = Xml::WrapperFactory.create_instance("UndeployVAppParams") + # task = @connection.post(current_vapp.undeploy_link, params) + # monitor_task(task, @time_limit["undeploy"]) + # @logger.info("vApp #{current_vapp.name} powered-off, undeployed.") + # task + # else + # unless current_vapp.power_off_link + # if is_vapp_status(current_vapp, :POWERED_OFF) + # @logger.info("vApp #{vapp.name} already powered off.") + # return + # end + # raise CloudError, "vApp #{vapp.name} not in a state be powered off." + # end + # task = @connection.post(current_vapp.power_off_link, nil) + # monitor_task(task, @time_limit["power_off"]) + # @logger.info("vApp #{current_vapp.name} powered off.") + # task + # end + # end + # + # def discard_suspended_state_vapp(vapp) + # @logger.info("Discarding suspended state of vApp #{vapp.name}.") + # current_vapp = @connection.get(vapp) + # unless current_vapp + # raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." + # end + # @logger.debug("vApp status: #{current_vapp["status"]}") + # + # return unless is_vapp_status(current_vapp, :SUSPENDED) + # + # @logger.info("Discarding suspended state of vApp #{current_vapp.name}.") + # task = @connection.post(current_vapp.discard_state, nil) + # monitor_task(task, @time_limit["undeploy"]) + # current_vapp = @connection.get(current_vapp) + # @logger.info("vApp #{current_vapp.name} suspended state discarded.") + # task + # end + # + # def reboot_vapp(vapp) + # @logger.info("Rebooting vApp #{vapp.name}.") + # current_vapp = @connection.get(vapp) + # unless current_vapp + # raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." + # end + # @logger.debug("vApp status: #{current_vapp["status"]}") + # + # if is_vapp_status(current_vapp, :SUSPENDED) + # @logger.debug("vApp #{current_vapp.name} suspended.") + # raise VappSuspendedError, "vapp suspended" + # end + # if is_vapp_status(current_vapp, :POWERED_OFF) + # @logger.debug("vApp #{current_vapp.name} powered off.") + # raise VappPoweredOffError, "vapp powered off" + # end + # + # @logger.info("Rebooting vApp #{current_vapp.name}.") + # task = @connection.post(current_vapp.reboot_link, nil) + # monitor_task(task) + # current_vapp = @connection.get(current_vapp) + # @logger.info("vApp #{current_vapp.name} rebooted.") + # task + # end + # + # def get_ovdc + # vdc = @admin_org.vdc(@ovdc_name) + # raise ObjectNotFoundError, "VDC #{@ovdc_name} not found." unless vdc + # @connection.get(vdc) + # end + # + # def get_catalog(name) + # catalog = @connection.get(@admin_org.catalog(name)) + # end private - ERROR_STATUSES = [Xml::TASK_STATUS[:ABORTED], Xml::TASK_STATUS[:ERROR], - Xml::TASK_STATUS[:CANCELED]] - SUCCESS_STATUS = [Xml::TASK_STATUS[:SUCCESS]] - - def resolve_entity(id) - url = "#{@entity_resolver_link}#{id}" - entity = @connection.get(url) - raise ObjectNotFoundError, "Unable to get entity" unless entity - @connection.get(entity.link) - end - - def get_disk_id(vm, disk_href) - hardware_section = vm.hardware_section - disk = hardware_section.hard_disks.find do |d| - d.host_resource["disk"] == disk_href - end - unless disk - raise DiskNotFoundError, "Disk with href #{disk_href} not attached " + - "to VM #{vm.name}." - end - disk.disk_id - end - - def log_exception(e, message = nil) - @logger.error(message) if message - @logger.error(e.message) - @logger.error(e.backtrace.join("\n\r")) - end - - def copy_network_settings(network, network_config, vapp_net_name, - fence_mode) - config_ip_scope = network_config.ip_scope - net_ip_scope = network.ip_scope - config_ip_scope.is_inherited = net_ip_scope.is_inherited? - config_ip_scope.gateway= net_ip_scope.gateway - config_ip_scope.netmask = net_ip_scope.netmask - if net_ip_scope.start_address - config_ip_scope.start_address = net_ip_scope.start_address - end - if net_ip_scope.end_address - config_ip_scope.end_address = net_ip_scope.end_address - end - network_config.fence_mode = fence_mode - network_config.parent_network["name"] = network["name"] - network_config.parent_network["href"] = network["href"] - network_config["networkName"] = vapp_net_name - end - - def delete_vapp_template(vapp_template) - delete_vapp_or_template(vapp_template, @retries["default"], - @time_limit["delete_vapp_template"], "vApp Template") - end - - def check_vapp_for_remove_link(vapp) - current_vapp = @connection.get(vapp) - unless current_vapp.remove_link - raise ObjectNotFoundError, "No link available to delete vApp." - end - return current_vapp - end - - def delete_vapp_or_template(vapp, retries, time_limit, type_name) - retries.times do |try| - @logger.info("Deleting #{type_name} #{vapp.name}") - current_vapp = @connection.get(vapp) - if (current_vapp.running_tasks.empty?) - Util.retry_operation(current_vapp, @retries["default"], - @control["backoff"]) do - current_vapp = check_vapp_for_remove_link(current_vapp) - end - Util.retry_operation(current_vapp.remove_link, @retries["default"], - @control["backoff"]) do - monitor_task(@connection.delete(current_vapp.remove_link), - time_limit) do |task| - @logger.info("#{type_name} #{current_vapp.name} deleted.") - return task - end - end - else - @logger.info("#{vapp.name} has tasks in progress, wait until done.") - current_vapp.running_tasks.each do |task| - monitor_task(task) - end - sleep (@control["backoff"] ** try) - end - end - raise ApiRequestError, - "Unable to delete #{type_name} after #{retries} attempts" - end - - def insert_media(vm, media, retries = @retries["default"]) - params = Xml::WrapperFactory.create_instance("MediaInsertOrEjectParams") - params.media_href = media.href - - # Wait for media to be ready - retries.times do |try| - @logger.info("Inserting media #{media.name} into VM #{vm.name}.") - current_media = @connection.get(media) - if (current_media.running_tasks.empty?) - Util.retry_operation(vm.insert_media_link, @retries["default"], - @control["backoff"]) do - task = @connection.post(vm.insert_media_link, params, - Xml::MEDIA_TYPE[:MEDIA_INSERT_EJECT_PARAMS]) - monitor_task(task) do |t| - raise CloudError, "Error inserting media #{media.name} " + - "into VM #{vm.name}." if t.status != "success" - @logger.info("Inserted media #{media.name} into VM #{vm.name}.") - return t - end - end - else - @logger.info("#{current_media.name} has tasks in progress, " + - "waiting until done.") - current_media.running_tasks.each do |task| - monitor_task(task) - end - sleep (@control["backoff"] ** try) - end - end - raise ApiRequestError, "Unable to insert media #{media.name} into " + - "VM #{vm.name} after #{retries} attempts" - end - - def eject_media(vm, media, retries = @retries["default"]) - params = Xml::WrapperFactory.create_instance("MediaInsertOrEjectParams") - params.media_href = media.href - - #Wait for media to be ready - retries.times do |try| - @logger.info("Ejecting media #{media.name} from VM #{vm.name}.") - current_media = @connection.get(media) - if (current_media.running_tasks.empty?) - return eject_media_task(vm, params, media) - else - @logger.info("#{current_media.name} has tasks in progress, " + - "waiting until done.") - current_media.running_tasks.each do |task| - monitor_task(task) - end - sleep (@control["backoff"] ** try) - end - end - raise ApiRequestError, "Unable to eject media #{media.name} from " + - "VM #{vm.name} after #{retries} attempts" - end - - def delete_media(media, retries = @retries["default"], - time_limit = @time_limit["delete_media"]) - retries.times do |try| - @logger.info("Deleting media #{media.name}") - current_media = @connection.get(media) - if (current_media.running_tasks.empty?) - Util.retry_operation(current_media.delete_link, @retries["default"], - @control["backoff"]) do - monitor_task(@connection.delete(current_media.delete_link), - time_limit) do |task| - @logger.info("Media #{current_media.name} deleted.") - return task - end - end - else - @logger.info("#{current_media.name} has tasks in progress, " + - "waiting until done.") - current_media.running_tasks.each do |task| - monitor_task(task) - end - sleep (@control["backoff"] ** try) - end - end - raise ApiRequestError, "Unable to delete #{type_name} after " - "#{retries} attempts" - end - - def get_catalog_media(name) - get_catalog_item(name, Xml::MEDIA_TYPE[:MEDIA], @media_catalog_name) - end - - # Get catalog item from catalog by name and type. - # Raises an exception if catalog is not found. - # Returns nil if an item matching the name and type is not found. - # Otherwise, returns the catalog item. - # The catalog item is not the uderlying object itself, i.e. vApp template. - def get_catalog_item(name, item_type, catalog_name) - raise ObjectNotFoundError, "Catalog item name cannot be nil" unless name - unless @admin_org.catalog(catalog_name) - raise ObjectNotFoundError, "Catalog #{catalog_name} not found." - end - # For some reason, if the catalog no longer exists, - # VCD throws a Forbidden exception when getting - catalog = @connection.get(@admin_org.catalog(catalog_name)) - items = catalog.catalog_items(name) - if items.nil? || items.empty? - @logger.debug("Item #{name} does not exist in catalog #{catalog_name}") - return nil - end - items.each do |i| - entity = @connection.get(i) - # Return the entity node. Another get on that node is necessary to - # get the actual object itself - return entity if entity.entity["type"] == item_type - end - nil - end - - def get_vm_network_connections(vm) - current_vm = @connection.get(vm) - unless current_vm - raise ObjectNotFoundError, "VM #{vm.name} no longer exists." - end - @connection.get(current_vm.network_connection_section) - end - - def task_progressed?(current_task, prev_progress, prev_status) - (current_task.progress && (current_task.progress != prev_progress)) || - (current_task.status && (current_task.status != prev_status)) - end - - def task_is_success(current_task, success = SUCCESS_STATUS) - success.map { |s| s.downcase }.find { - |s| s == current_task.status.downcase } - end - - def task_has_error(current_task, error_statuses = ERROR_STATUSES) - error_statuses.map { |s| s.downcase }.find { - |s| s == current_task.status.downcase } - end - - def monitor_task(task, time_limit = @time_limit["default"], - error_statuses = ERROR_STATUSES, success = SUCCESS_STATUS, - delay = @control["delay"], &b) - iterations = time_limit / delay - i = 0 - prev_progress = task.progress - prev_status = task.status - current_task = task - while (i < iterations) - @logger.debug("#{current_task.urn} #{current_task.operation} is " + - "#{current_task.status}") - if task_is_success(current_task, success) - if b - return b.call(current_task) - else - return current_task - end - elsif task_has_error(current_task, error_statuses) - raise ApiRequestError, "Task #{task.urn} #{task.operation} did " + - "not complete successfully." - elsif task_progressed?(current_task, prev_progress, prev_status) - @logger.debug("task status #{prev_status} => " + - "#{current_task.status}, progress #{prev_progress}%" + - " => #{current_task.progress}%, timer #{i} reset.") - prev_progress = current_task.progress - prev_status = current_task.status - i = 0 #reset clock if status changes or running task makes progress - sleep(delay) - else - @logger.debug("Approximately #{i * delay}s elapsed waiting for " + - "#{current_task.operation} to reach " + - "#{success.join("/")}/#{error_statuses.join("/")}." + - " Checking again in #{delay} seconds.") - @logger.debug("Task #{task.urn} progress: " + - "#{current_task.progress} %.") if current_task.progress - sleep(delay) - end - current_task = @connection.get(task) - i += 1 - end - raise ApiTimeoutError, "Task #{task.operation} did not complete " + - "within limit of #{time_limit} seconds." - end - - - # TODO use times.upload_vapp_files - def upload_vapp_files(vapp, ovf_directory, - tries = @retries["upload_vapp_files"], try = 0) - current_vapp = @connection.get(vapp) - return current_vapp if !current_vapp.files || current_vapp.files.empty? - - @logger.debug("vapp files left to upload #{current_vapp.files}.") - @logger.debug("vapp incomplete files left to upload " + - "#{current_vapp.incomplete_files}.") - raise ApiTimeoutError, "Unable to finish uploading vApp after " + - "#{tries} tries #{current_vapp.files}." if tries == try - - current_vapp.incomplete_files.each do |f| - # switch on extension - case f.name.split(".").pop.downcase - when "ovf" - @logger.info("Uploading OVF file: " + - "#{ovf_directory.ovf_file_path} for #{vapp.name}") - @connection.put(f.upload_link, ovf_directory.ovf_file.read, - Xml::MEDIA_TYPE[:OVF]) - when "vmdk" - @logger.info("Uploading VMDK file " + - "#{ovf_directory.vmdk_file_path(f.name)} for #{vapp.name}") - @connection.put_file(f.upload_link, - ovf_directory.vmdk_file(f.name)) - end - end - #repeat - sleep (2 ** try) - upload_vapp_files(current_vapp, ovf_directory, tries, try + 1) - end - - def add_catalog_item(item, catalog_name) - unless @admin_org.catalog(catalog_name) - raise ArgumentError, - "Error adding #{item.name}, catalog #{catalog_name} not found." - end - catalog = @connection.get(@admin_org.catalog(catalog_name)) - raise ObjectNotFoundError, "Error adding #{item.name}, catalog " + - "#{catalog_name} not available." unless catalog - catalog_item = Xml::WrapperFactory.create_instance("CatalogItem") - catalog_item.name = item.name - catalog_item.entity = item - @logger.info("Adding #{catalog_item.name} to catalog #{catalog_name}") - @connection.post(catalog.add_item_link, catalog_item, - Xml::ADMIN_MEDIA_TYPE[:CATALOG_ITEM]) - end - - def generate_metadata_href(entity, key) - raise ObjectNotFoundError, "Entity #{entity.name} does not expose a " + - "metadata link method." if !entity.respond_to?(:metadata_link) - "#{entity.metadata_link.href}/#{key}" - end - - def get_vapp_by_name(name) - @logger.debug("Getting vApp #{name}") - vdc = get_ovdc - node = vdc.get_vapp(name) - raise ObjectNotFoundError, "vApp #{name} does not exist." unless node - vapp = @connection.get(node) - raise ObjectNotFoundError, "vApp #{name} does not exist." unless vapp - vapp - end - - def locality_spec(src_vapp_template, disk_locality) - disk_locality ||= [] - locality = {} - disk_locality.each do |disk| - current_disk = @connection.get(disk) - unless current_disk - @logger.warn("Disk #{disk.name} no longer exists.") - next - end - src_vapp_template.vms.each do |vm| - locality[vm] = current_disk - end - end - locality - end - - def is_vapp_status(current_vapp, status) - current_vapp["status"] == Xml::RESOURCE_ENTITY_STATUS[status].to_s - end - - def rollback_upload_vapp(vapp_template) - @logger.error("Rolling back changes.") - begin - delete_vapp_template(vapp_template) if vapp_template - rescue => rollbackex - log_exception(rollbackex, "Error in rolling back failed vApp " + - "template #{vapp_name}.") - end - end - - def eject_media_task(vm, params, media) - Util.retry_operation(vm.eject_media_link, @retries["default"], - @control["backoff"]) do - task = @connection.post(vm.eject_media_link, params, - Xml::MEDIA_TYPE[:MEDIA_INSERT_EJECT_PARAMS]) - monitor_task(task) do |t| - if t.status != "success" - raise CloudError, "Error ejecting media #{media.name} from " + - "VM #{vm.name}." - end - @logger.info("Ejected media #{media.name} from VM #{vm.name}.") - return t - end - end - end - + # ERROR_STATUSES = [Xml::TASK_STATUS[:ABORTED], Xml::TASK_STATUS[:ERROR], + # Xml::TASK_STATUS[:CANCELED]] + # SUCCESS_STATUS = [Xml::TASK_STATUS[:SUCCESS]] + # + # def resolve_entity(id) + # url = "#{@entity_resolver_link}#{id}" + # entity = @connection.get(url) + # raise ObjectNotFoundError, "Unable to get entity" unless entity + # @connection.get(entity.link) + # end + # + # def get_disk_id(vm, disk_href) + # hardware_section = vm.hardware_section + # disk = hardware_section.hard_disks.find do |d| + # d.host_resource["disk"] == disk_href + # end + # unless disk + # raise DiskNotFoundError, "Disk with href #{disk_href} not attached " + + # "to VM #{vm.name}." + # end + # disk.disk_id + # end + # + # def log_exception(e, message = nil) + # @logger.error(message) if message + # @logger.error(e.message) + # @logger.error(e.backtrace.join("\n\r")) + # end + # + # def copy_network_settings(network, network_config, vapp_net_name, + # fence_mode) + # config_ip_scope = network_config.ip_scope + # net_ip_scope = network.ip_scope + # config_ip_scope.is_inherited = net_ip_scope.is_inherited? + # config_ip_scope.gateway= net_ip_scope.gateway + # config_ip_scope.netmask = net_ip_scope.netmask + # if net_ip_scope.start_address + # config_ip_scope.start_address = net_ip_scope.start_address + # end + # if net_ip_scope.end_address + # config_ip_scope.end_address = net_ip_scope.end_address + # end + # network_config.fence_mode = fence_mode + # network_config.parent_network["name"] = network["name"] + # network_config.parent_network["href"] = network["href"] + # network_config["networkName"] = vapp_net_name + # end + # + # def delete_vapp_template(vapp_template) + # delete_vapp_or_template(vapp_template, @retries["default"], + # @time_limit["delete_vapp_template"], "vApp Template") + # end + # + # def check_vapp_for_remove_link(vapp) + # current_vapp = @connection.get(vapp) + # unless current_vapp.remove_link + # raise ObjectNotFoundError, "No link available to delete vApp." + # end + # return current_vapp + # end + # + # def delete_vapp_or_template(vapp, retries, time_limit, type_name) + # retries.times do |try| + # @logger.info("Deleting #{type_name} #{vapp.name}") + # current_vapp = @connection.get(vapp) + # if (current_vapp.running_tasks.empty?) + # Util.retry_operation(current_vapp, @retries["default"], + # @control["backoff"]) do + # current_vapp = check_vapp_for_remove_link(current_vapp) + # end + # Util.retry_operation(current_vapp.remove_link, @retries["default"], + # @control["backoff"]) do + # monitor_task(@connection.delete(current_vapp.remove_link), + # time_limit) do |task| + # @logger.info("#{type_name} #{current_vapp.name} deleted.") + # return task + # end + # end + # else + # @logger.info("#{vapp.name} has tasks in progress, wait until done.") + # current_vapp.running_tasks.each do |task| + # monitor_task(task) + # end + # sleep (@control["backoff"] ** try) + # end + # end + # raise ApiRequestError, + # "Unable to delete #{type_name} after #{retries} attempts" + # end + # + # def insert_media(vm, media, retries = @retries["default"]) + # params = Xml::WrapperFactory.create_instance("MediaInsertOrEjectParams") + # params.media_href = media.href + # + # # Wait for media to be ready + # retries.times do |try| + # @logger.info("Inserting media #{media.name} into VM #{vm.name}.") + # current_media = @connection.get(media) + # if (current_media.running_tasks.empty?) + # Util.retry_operation(vm.insert_media_link, @retries["default"], + # @control["backoff"]) do + # task = @connection.post(vm.insert_media_link, params, + # Xml::MEDIA_TYPE[:MEDIA_INSERT_EJECT_PARAMS]) + # monitor_task(task) do |t| + # raise CloudError, "Error inserting media #{media.name} " + + # "into VM #{vm.name}." if t.status != "success" + # @logger.info("Inserted media #{media.name} into VM #{vm.name}.") + # return t + # end + # end + # else + # @logger.info("#{current_media.name} has tasks in progress, " + + # "waiting until done.") + # current_media.running_tasks.each do |task| + # monitor_task(task) + # end + # sleep (@control["backoff"] ** try) + # end + # end + # raise ApiRequestError, "Unable to insert media #{media.name} into " + + # "VM #{vm.name} after #{retries} attempts" + # end + # + # def eject_media(vm, media, retries = @retries["default"]) + # params = Xml::WrapperFactory.create_instance("MediaInsertOrEjectParams") + # params.media_href = media.href + # + # #Wait for media to be ready + # retries.times do |try| + # @logger.info("Ejecting media #{media.name} from VM #{vm.name}.") + # current_media = @connection.get(media) + # if (current_media.running_tasks.empty?) + # return eject_media_task(vm, params, media) + # else + # @logger.info("#{current_media.name} has tasks in progress, " + + # "waiting until done.") + # current_media.running_tasks.each do |task| + # monitor_task(task) + # end + # sleep (@control["backoff"] ** try) + # end + # end + # raise ApiRequestError, "Unable to eject media #{media.name} from " + + # "VM #{vm.name} after #{retries} attempts" + # end + # + # def delete_media(media, retries = @retries["default"], + # time_limit = @time_limit["delete_media"]) + # retries.times do |try| + # @logger.info("Deleting media #{media.name}") + # current_media = @connection.get(media) + # if (current_media.running_tasks.empty?) + # Util.retry_operation(current_media.delete_link, @retries["default"], + # @control["backoff"]) do + # monitor_task(@connection.delete(current_media.delete_link), + # time_limit) do |task| + # @logger.info("Media #{current_media.name} deleted.") + # return task + # end + # end + # else + # @logger.info("#{current_media.name} has tasks in progress, " + + # "waiting until done.") + # current_media.running_tasks.each do |task| + # monitor_task(task) + # end + # sleep (@control["backoff"] ** try) + # end + # end + # raise ApiRequestError, "Unable to delete #{type_name} after " + # "#{retries} attempts" + # end + # + # def get_catalog_media(name) + # get_catalog_item(name, Xml::MEDIA_TYPE[:MEDIA], @media_catalog_name) + # end + # + # # Get catalog item from catalog by name and type. + # # Raises an exception if catalog is not found. + # # Returns nil if an item matching the name and type is not found. + # # Otherwise, returns the catalog item. + # # The catalog item is not the uderlying object itself, i.e. vApp template. + # def get_catalog_item(name, item_type, catalog_name) + # raise ObjectNotFoundError, "Catalog item name cannot be nil" unless name + # unless @admin_org.catalog(catalog_name) + # raise ObjectNotFoundError, "Catalog #{catalog_name} not found." + # end + # # For some reason, if the catalog no longer exists, + # # VCD throws a Forbidden exception when getting + # catalog = @connection.get(@admin_org.catalog(catalog_name)) + # items = catalog.catalog_items(name) + # if items.nil? || items.empty? + # @logger.debug("Item #{name} does not exist in catalog #{catalog_name}") + # return nil + # end + # items.each do |i| + # entity = @connection.get(i) + # # Return the entity node. Another get on that node is necessary to + # # get the actual object itself + # return entity if entity.entity["type"] == item_type + # end + # nil + # end + # + # def get_vm_network_connections(vm) + # current_vm = @connection.get(vm) + # unless current_vm + # raise ObjectNotFoundError, "VM #{vm.name} no longer exists." + # end + # @connection.get(current_vm.network_connection_section) + # end + # + # def task_progressed?(current_task, prev_progress, prev_status) + # (current_task.progress && (current_task.progress != prev_progress)) || + # (current_task.status && (current_task.status != prev_status)) + # end + # + # def task_is_success(current_task, success = SUCCESS_STATUS) + # success.map { |s| s.downcase }.find { + # |s| s == current_task.status.downcase } + # end + # + # def task_has_error(current_task, error_statuses = ERROR_STATUSES) + # error_statuses.map { |s| s.downcase }.find { + # |s| s == current_task.status.downcase } + # end + # + # def monitor_task(task, time_limit = @time_limit["default"], + # error_statuses = ERROR_STATUSES, success = SUCCESS_STATUS, + # delay = @control["delay"], &b) + # iterations = time_limit / delay + # i = 0 + # prev_progress = task.progress + # prev_status = task.status + # current_task = task + # while (i < iterations) + # @logger.debug("#{current_task.urn} #{current_task.operation} is " + + # "#{current_task.status}") + # if task_is_success(current_task, success) + # if b + # return b.call(current_task) + # else + # return current_task + # end + # elsif task_has_error(current_task, error_statuses) + # raise ApiRequestError, "Task #{task.urn} #{task.operation} did " + + # "not complete successfully." + # elsif task_progressed?(current_task, prev_progress, prev_status) + # @logger.debug("task status #{prev_status} => " + + # "#{current_task.status}, progress #{prev_progress}%" + + # " => #{current_task.progress}%, timer #{i} reset.") + # prev_progress = current_task.progress + # prev_status = current_task.status + # i = 0 #reset clock if status changes or running task makes progress + # sleep(delay) + # else + # @logger.debug("Approximately #{i * delay}s elapsed waiting for " + + # "#{current_task.operation} to reach " + + # "#{success.join("/")}/#{error_statuses.join("/")}." + + # " Checking again in #{delay} seconds.") + # @logger.debug("Task #{task.urn} progress: " + + # "#{current_task.progress} %.") if current_task.progress + # sleep(delay) + # end + # current_task = @connection.get(task) + # i += 1 + # end + # raise ApiTimeoutError, "Task #{task.operation} did not complete " + + # "within limit of #{time_limit} seconds." + # end + # + # + # # TODO use times.upload_vapp_files + # def upload_vapp_files(vapp, ovf_directory, + # tries = @retries["upload_vapp_files"], try = 0) + # current_vapp = @connection.get(vapp) + # return current_vapp if !current_vapp.files || current_vapp.files.empty? + # + # @logger.debug("vapp files left to upload #{current_vapp.files}.") + # @logger.debug("vapp incomplete files left to upload " + + # "#{current_vapp.incomplete_files}.") + # raise ApiTimeoutError, "Unable to finish uploading vApp after " + + # "#{tries} tries #{current_vapp.files}." if tries == try + # + # current_vapp.incomplete_files.each do |f| + # # switch on extension + # case f.name.split(".").pop.downcase + # when "ovf" + # @logger.info("Uploading OVF file: " + + # "#{ovf_directory.ovf_file_path} for #{vapp.name}") + # @connection.put(f.upload_link, ovf_directory.ovf_file.read, + # Xml::MEDIA_TYPE[:OVF]) + # when "vmdk" + # @logger.info("Uploading VMDK file " + + # "#{ovf_directory.vmdk_file_path(f.name)} for #{vapp.name}") + # @connection.put_file(f.upload_link, + # ovf_directory.vmdk_file(f.name)) + # end + # end + # #repeat + # sleep (2 ** try) + # upload_vapp_files(current_vapp, ovf_directory, tries, try + 1) + # end + # + # def add_catalog_item(item, catalog_name) + # unless @admin_org.catalog(catalog_name) + # raise ArgumentError, + # "Error adding #{item.name}, catalog #{catalog_name} not found." + # end + # catalog = @connection.get(@admin_org.catalog(catalog_name)) + # raise ObjectNotFoundError, "Error adding #{item.name}, catalog " + + # "#{catalog_name} not available." unless catalog + # catalog_item = Xml::WrapperFactory.create_instance("CatalogItem") + # catalog_item.name = item.name + # catalog_item.entity = item + # @logger.info("Adding #{catalog_item.name} to catalog #{catalog_name}") + # @connection.post(catalog.add_item_link, catalog_item, + # Xml::ADMIN_MEDIA_TYPE[:CATALOG_ITEM]) + # end + # + # def generate_metadata_href(entity, key) + # raise ObjectNotFoundError, "Entity #{entity.name} does not expose a " + + # "metadata link method." if !entity.respond_to?(:metadata_link) + # "#{entity.metadata_link.href}/#{key}" + # end + # + # def get_vapp_by_name(name) + # @logger.debug("Getting vApp #{name}") + # vdc = get_ovdc + # node = vdc.get_vapp(name) + # raise ObjectNotFoundError, "vApp #{name} does not exist." unless node + # vapp = @connection.get(node) + # raise ObjectNotFoundError, "vApp #{name} does not exist." unless vapp + # vapp + # end + # + # def locality_spec(src_vapp_template, disk_locality) + # disk_locality ||= [] + # locality = {} + # disk_locality.each do |disk| + # current_disk = @connection.get(disk) + # unless current_disk + # @logger.warn("Disk #{disk.name} no longer exists.") + # next + # end + # src_vapp_template.vms.each do |vm| + # locality[vm] = current_disk + # end + # end + # locality + # end + # + # def is_vapp_status(current_vapp, status) + # current_vapp["status"] == Xml::RESOURCE_ENTITY_STATUS[status].to_s + # end + # + # def rollback_upload_vapp(vapp_template) + # @logger.error("Rolling back changes.") + # begin + # delete_vapp_template(vapp_template) if vapp_template + # rescue => rollbackex + # log_exception(rollbackex, "Error in rolling back failed vApp " + + # "template #{vapp_name}.") + # end + # end + # + # def eject_media_task(vm, params, media) + # Util.retry_operation(vm.eject_media_link, @retries["default"], + # @control["backoff"]) do + # task = @connection.post(vm.eject_media_link, params, + # Xml::MEDIA_TYPE[:MEDIA_INSERT_EJECT_PARAMS]) + # monitor_task(task) do |t| + # if t.status != "success" + # raise CloudError, "Error ejecting media #{media.name} from " + + # "VM #{vm.name}." + # end + # @logger.info("Ejected media #{media.name} from VM #{vm.name}.") + # return t + # end + # end + # end + # + + # TODO: move to connection.rb def construct_rest_logger - @logger.debug("constructing rest_logger") - rest_log_filename = File.join(File.dirname(@logger.instance_eval { - @logdev }.dev.path), "rest") - log_file = File.open(rest_log_filename, "w") + @logger.debug('constructing rest_logger') + rest_log_filename = File.join( + File.dirname(@logger.instance_eval { @logdev }.dev.path), + 'rest') + log_file = File.open(rest_log_filename, 'w') log_file.sync = true @rest_logger = Logger.new(log_file || STDOUT) diff --git a/lib/ruby_vcloud_sdk/config.rb b/lib/ruby_vcloud_sdk/config.rb index 43d6d23..cedba7f 100644 --- a/lib/ruby_vcloud_sdk/config.rb +++ b/lib/ruby_vcloud_sdk/config.rb @@ -7,9 +7,9 @@ class << self attr_accessor :rest_throttle def configure(config) - @logger = config["logger"] || @logger || Logger.new(STDOUT) - @rest_logger = config["rest_logger"] || @logger - @rest_throttle = config["rest_throttle"] + @logger = config[:logger] || @logger || Logger.new(STDOUT) + @rest_logger = config[:rest_logger] || @logger + @rest_throttle = config[:rest_throttle] end end end diff --git a/lib/ruby_vcloud_sdk/connection/connection.rb b/lib/ruby_vcloud_sdk/connection/connection.rb index 0619351..0f9456d 100644 --- a/lib/ruby_vcloud_sdk/connection/connection.rb +++ b/lib/ruby_vcloud_sdk/connection/connection.rb @@ -1,33 +1,32 @@ -require "base64" -require "rest_client" +require 'base64' +require 'rest_client' module VCloudSdk module Connection class Connection - SECURITY_CHECK = "/cloud/security_check" - ACCEPT = "application/*+xml;version=5.1" + ACCEPT = 'application/*+xml;version=5.1' - def initialize(hostname_port, organization, request_timeout = nil, + def initialize(url, request_timeout = nil, rest_client = nil, site = nil, file_uploader = nil) - @organization = organization @logger = Config.logger @rest_logger = Config.rest_logger @rest_throttle = Config.rest_throttle rest_client = RestClient unless rest_client rest_client.log = @rest_logger request_timeout = 60 unless request_timeout - @site = site.nil? ? rest_client::Resource.new(hostname_port, - :timeout => request_timeout) : site - @file_uploader = file_uploader.nil? ? FileUploader : file_uploader + @site = site || rest_client::Resource.new( + url, + timeout: request_timeout) + @file_uploader = file_uploader || FileUploader end def connect(username, password) - login = "#{username}@#{@organization}" - login_password = "#{login}:#{password}" + login_password = "#{username}:#{password}" auth_header_value = "Basic #{Base64.encode64(login_password)}" + # TODO: call 'api/versions' first response = @site["/api/sessions"].post( - {:Authorization=>auth_header_value, :Accept=>ACCEPT}) + { Authorization: auth_header_value, Accept: ACCEPT }) @logger.debug(response) @cookies = response.cookies unless @cookies["vcloud-token"].gsub!("+", "%2B").nil? @@ -130,8 +129,8 @@ def log_exceptions(e) end def delay() - @rest_throttle["min"] + rand(@rest_throttle["max"] - - @rest_throttle["min"]) + @rest_throttle[:min] + rand(@rest_throttle[:max] - + @rest_throttle[:min]) end def get_nested_resource(destination) diff --git a/lib/ruby_vcloud_sdk/xml/wrapper_classes/vcloud.rb b/lib/ruby_vcloud_sdk/xml/wrapper_classes/vcloud.rb index 3be3689..7c81b90 100644 --- a/lib/ruby_vcloud_sdk/xml/wrapper_classes/vcloud.rb +++ b/lib/ruby_vcloud_sdk/xml/wrapper_classes/vcloud.rb @@ -3,11 +3,13 @@ module Xml class VCloud < Wrapper def organizations - get_nodes("OrganizationReference") + get_nodes('OrganizationReference') end - def organization(name) - get_nodes("OrganizationReference", {"name" => name}).first + def organization + # TODO: check and make sure that + # there is only one "OrganizationReference" node in response + get_nodes('OrganizationReference').first end end diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index a2abac4..c75ad28 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -1,1155 +1,84 @@ -require "spec_helper" -require_relative "client_response" -require "stringio" -require "logger" -require "nokogiri/diff" +require 'spec_helper' +require_relative 'client_response' module VCloudSdk - vcd = VCloudSdk::Test::vcd_settings logger = Config.logger - - Config.configure({"logger" => logger, - "rest_logger" =>VCloudSdk::Test::rest_logger(logger), - "rest_throttle" => vcd["control"]["rest_throttle"]}) - - describe Client, :min, :all do - let(:url) { vcd["url"] } - let(:username) { vcd["user"] } - let(:password) { vcd["password"] } - let(:control) { vcd["control"] } - let(:entities) { vcd["entities"] } - let(:auth_cookies) { {"vcloud-token" => vcd["testing"]["cookies"]} } - - def mock_rest_connection - @upload_file_state = "success" - current_vapp_state = "nothing" - finalize_vapp_task_state = "running" - delete_vapp_template_task_state = "running" - delete_vapp_task_state = "running" - change_vm_task_state = "running" - catalog_state = "not_added" - template_instantiate_state = "running" - vapp_power_state = "off" - existing_media_state = "busy" - metadata_value = "" - metadata_xml = "" - rest_client = double("Rest Client") - response_mapping = { - :get => { - Test::Response::ADMIN_VCLOUD_LINK => lambda { - |url, headers| Test::Response::VCLOUD_RESPONSE }, - Test::Response::ADMIN_ORG_LINK => lambda { - |url, headers| Test::Response::ADMIN_ORG_RESPONSE }, - Test::Response::VDC_LINK => lambda { - |url, headers| Test::Response::VDC_RESPONSE }, - Test::Response::CATALOG_LINK => lambda { |url, headers| - case (catalog_state) - when "not_added" - Test::Response::CATALOG_RESPONSE - when "added" - Test::Response::CATALOG_ITEM_ADDED_RESPONSE - end - }, - Test::Response::CATALOG_ITEM_VAPP_LINK => lambda { - |url, headers| Test::Response::CATALOG_ADD_ITEM_RESPONSE - }, - Test::Response::VAPP_TEMPLATE_LINK => lambda { |url, headers| - case (current_vapp_state) - when "ovf_uploaded" - Test::Response::VAPP_TEMPLATE_NO_DISKS_RESPONSE - when "nothing" - Test::Response::VAPP_TEMPLATE_UPLOAD_OVF_WAITING_RESPONSE - when "disks_uploaded" - Test::Response::VAPP_TEMPLATE_UPLOAD_COMPLETE - when "disks_upload_failed" - Test::Response::VAPP_TEMPLATE_UPLOAD_FAILED - when "finalized" - Test::Response::VAPP_TEMPLATE_READY_RESPONSE - end - }, - Test::Response::FINALIZE_UPLOAD_TASK_LINK => lambda { - |url, headers| - case (finalize_vapp_task_state) - when "running" - finalize_vapp_task_state = "success" - current_vapp_state = "finalized" - Test::Response::FINALIZE_UPLOAD_TASK_RESPONSE - when "success" - Test::Response::FINALIZE_UPLOAD_TASK_DONE_RESPONSE - end - }, - Test::Response::VAPP_TEMPLATE_DELETE_TASK_LINK => lambda { - |url, headers| - case (delete_vapp_template_task_state) - when "running" - delete_vapp_template_task_state = "success" - Test::Response::VAPP_TEMPLATE_DELETE_RUNNING_TASK - when "success" - Test::Response::VAPP_TEMPLATE_DELETE_DONE_TASK - end - }, - Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_ITEM_LINK => - lambda { |url, headers| - Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_ITEM_RESPONSE - }, - Test::Response::EXISTING_VAPP_TEMPLATE_LINK => lambda { - |url, headers| - Test::Response::EXISTING_VAPP_TEMPLATE_READY_RESPONSE - }, - Test::Response::EXISTING_VAPP_TEMPLATE_INSTANTIATE_TASK_LINK => - lambda { |url, headers| - case (template_instantiate_state) - when "running" - template_instantiate_state = "success" - Test::Response:: - EXISTING_VAPP_TEMPLATE_INSTANTIATE_TASK_START_RESPONSE - when "success" - Test::Response:: - EXISTING_VAPP_TEMPLATE_INSTANTIATE_TASK_SUCCESS_RESPONSE - end - }, - Test::Response::INSTANTIATED_VAPP_LINK => lambda { - |url, headers| - case(vapp_power_state) - when "off" - Test::Response::INSTANTIAED_VAPP_RESPONSE - when "on" - Test::Response::INSTANTIAED_VAPP_ON_RESPONSE - when "powered-off" - Test::Response::INSTANTIAED_VAPP_POWERED_OFF_RESPONSE - when "suspended" - Test::Response::INSTANTIATED_SUSPENDED_VAPP_RESPONSE - end - }, - Test::Response::INSTANTIATED_VAPP_DELETE_TASK_LINK => lambda { - |url, headers| - case (delete_vapp_task_state) - when "running" - delete_vapp_task_state = "success" - Test::Response::INSTANTIATED_VAPP_DELETE_RUNNING_TASK - when "success" - Test::Response::INSTANTIATED_VAPP_DELETE_DONE_TASK - end - }, - Test::Response::INSTANTIATED_VM_LINK => lambda { |url, headers| - Test::Response::INSTANTIATED_VM_RESPONSE - }, - Test::Response::INSTANTIATED_VM_CPU_LINK => lambda { - |url, headers| Test::Response::INSTANTIATED_VM_CPU_RESPONSE - }, - Test::Response::INSTANTIATED_VM_MEMORY_LINK => lambda { - |url, headers| Test::Response::INSTANTIATED_VM_MEMORY_RESPONSE - }, - Test::Response::INSTANTIATED_VM_MODIFY_TASK_LINK => lambda { - |url, headers| - case(change_vm_task_state) - when "running" - change_vm_task_state = "success" - Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING - when "success" - Test::Response::INSTANTIATED_VM_MODIFY_TASK_SUCCESS - end - }, - Test::Response::EXISTING_VAPP_LINK => lambda { |url, headers| - Test::Response::INSTANTIAED_VAPP_RESPONSE - }, - Test::Response::INSTANTIATED_VAPP_POWER_ON_TASK_LINK => lambda { - |url, headers| - Test::Response::INSTANTED_VAPP_POWER_TASK_SUCCESS - }, - Test::Response::ORG_NETWORK_LINK => lambda { |url, headers| - Test::Response::ORG_NETWORK_RESPONSE - }, - Test::Response::INSTANTIATED_VAPP_NETWORK_CONFIG_SECTION_LINK => - lambda { |url, headers| - Test::Response:: - INSTANTIATED_VAPP_NETWORK_CONFIG_SECTION_RESPONSE - }, - Test::Response:: - INSTANTIATED_VAPP_NETWORK_CONFIG_ADD_NETWORK_TASK_LINK => - lambda { |url, headers| - Test::Response:: - INSTANTIATED_VAPP_NETWORK_CONFIG_MODIFY_NETWORK_TASK_SUCCESS - }, - Test::Response::INSTANTIATED_VM_NETWORK_SECTION_LINK => lambda { - |url, headers| - Test::Response::INSTANTIATED_VM_NETWORK_SECTION_RESPONSE - }, - Test::Response::MEDIA_LINK => lambda { |url, headers| - Test::Response::MEDIA_UPLOAD_PENDING_RESPONSE - }, - Test::Response::EXISTING_MEDIA_CATALOG_ITEM_LINK => lambda { - |url, headers| Test::Response::EXISTING_MEDIA_CATALOG_ITEM - }, - Test::Response::EXISTING_MEDIA_LINK => lambda { |url, headers| - case(existing_media_state) - when "busy" - existing_media_state = "done" - Test::Response::EXISTING_MEDIA_BUSY_RESPONSE - when "done" - Test::Response::EXISTING_MEDIA_DONE_RESPONSE - end - }, - Test::Response::METADATA_SET_LINK => lambda { |url, headers| - metadata_xml - }, - Test::Response::INDY_DISK_URL => lambda { |url, headers| - Test::Response::INDY_DISK_RESPONSE - }, - Test::Response::EXISTING_VAPP_RESOLVER_URL => lambda { - |url,headers| Test::Response::EXISTING_VAPP_RESOLVER_RESPONSE - }, - Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_RESOLVER_URL => - lambda { |url,headers| - Test::Response:: - EXISTING_VAPP_TEMPLATE_CATALOG_RESOLVER_RESPONSE - }, - Test::Response::VAPP_TEMPLATE_CATALOG_RESOLVER_URL => lambda { - |url,headers| - Test::Response::VAPP_TEMPLATE_CATALOG_RESOLVER_RESPONSE - } - }, - :post => { - Test::Response::LOGIN_LINK => lambda { |url, data, headers| + Config.configure( + { + logger: logger, + rest_throttle: { min: 0, max: 1 } + }) + + response_mapping = { + get: { + Test::Response::ADMIN_VCLOUD_LINK => + lambda do |url, headers| + Test::Response::VCLOUD_RESPONSE + end, + Test::Response::ADMIN_ORG_LINK => + lambda do |url, headers| + Test::Response::ADMIN_ORG_RESPONSE + end + }, + post: { + Test::Response::LOGIN_LINK => + lambda do |url, data, headers| session_object = Test::Response::SESSION def session_object.cookies - {"vcloud-token" => - VCloudSdk::Test::vcd_settings["testing"]["cookies"]} + { 'vcloud-token' => 'fake-cookie' } end session_object - }, - Test::Response::VDC_VAPP_UPLOAD_LINK => lambda { - |url, data, headers| - current_vapp_state = "nothing" - Test::Response::VAPP_TEMPLATE_UPLOAD_OVF_WAITING_RESPONSE - }, - Test::Response::CATALOG_ADD_ITEM_LINK => lambda { - |url, data, headers| - case(Xml::WrapperFactory.wrap_document(data)) - when Xml::WrapperFactory.wrap_document( - Test::Response::CATALOG_ADD_VAPP_REQUEST) - catalog_state = "added" - Test::Response::CATALOG_ADD_ITEM_RESPONSE - when Xml::WrapperFactory.wrap_document( - Test::Response::MEDIA_ADD_TO_CATALOG_REQUEST) - catalog_state = "added" - Test::Response::MEDIA_ADD_TO_CATALOG_RESPONSE - else - Config.logger.error("Response mapping not found for " + - "POST and #{url} and #{data}") - raise "Response mapping not found." - end - }, - Test::Response::VAPP_TEMPLATE_INSTANTIATE_LINK => lambda { - |url, data, headers| - Test::Response::EXISTING_VAPP_TEMPLATE_INSTANTIATE_RESPONSE - }, - Test::Response::RECONFIGURE_VM_LINK => lambda { - |url, data, headers| - Test::Response::RECONFIGURE_VM_TASK - }, - Test::Response::INSTANTIATED_VAPP_POWER_ON_LINK => lambda { - |url, data, headers| - vapp_power_state = "on" - Test::Response::INSTANTED_VAPP_POWER_TASK_RUNNING - }, - Test::Response::INSTANTIATED_VAPP_POWER_OFF_LINK => lambda { - |url, data, headers| - vapp_power_state = "powered-off" - Test::Response::INSTANTED_VAPP_POWER_TASK_RUNNING - }, - Test::Response::INSTANTIATED_VAPP_POWER_REBOOT_LINK => lambda { - |url, data, headers| - vapp_power_state = "on" - Test::Response::INSTANTED_VAPP_POWER_TASK_RUNNING - }, - Test::Response::INSTANTIATED_VAPP_UNDEPLOY_LINK => lambda { - |url, data, headers| - vapp_power_state = "off" - Test::Response::INSTANTED_VAPP_POWER_TASK_RUNNING - }, - Test::Response::INSTANTIATED_VAPP_DISCARD_STATE_LINK => lambda { - |url, data, headers| - vapp_power_state = "off" - Test::Response::INSTANTED_VAPP_POWER_TASK_RUNNING - }, - Test::Response::MEDIA_UPLOAD_LINK => lambda { - |url, data, headers| - Test::Response::MEDIA_UPLOAD_PENDING_RESPONSE - }, - Test::Response::INSTANTIATED_VM_INSERT_MEDIA_LINK => lambda { - |url, data, headers| - Test::Response::INSTANTIATED_VM_INSERT_MEDIA_TASK_DONE - }, - Test::Response::VDC_INDY_DISKS_LINK => lambda { - |url, data, headers| - Test::Response::INDY_DISK_CREATE_RESPONSE - }, - Test::Response::INSTANTIATED_VM_ATTACH_DISK_LINK => lambda { - |url, data, headers| - Test::Response::INDY_DISK_ATTACH_TASK - } - }, - :put => { - Test::Response::VAPP_TEMPLATE_UPLOAD_OVF_LINK => lambda { - |url, data, headers| - current_vapp_state = "ovf_uploaded" - "" - }, - Test::Response::INSTANTIATED_VM_CPU_LINK => lambda { - |url, data, headers| - change_vm_task_state = "running" - Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING - }, - Test::Response::INSTANTIATED_VM_MEMORY_LINK => lambda { - |url, data, headers| - change_vm_task_state = "running" - Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING - }, - Test::Response::INSTANTIATED_VM_LINK => lambda { - |url, data, headers| - change_vm_task_state = "running" - Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING - }, - Test::Response::INSTANTIATED_VM_HARDWARE_SECTION_LINK => - lambda { |url, data, headers| - change_vm_task_state = "running" - Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING - }, - Test::Response::INSTANTIATED_VAPP_NETWORK_CONFIG_SECTION_LINK => - lambda { |url, data, headers| - Test::Response:: - INSTANTIATED_VAPP_NETWORK_CONFIG_MODIFY_NETWORK_TASK_SUCCESS - }, - Test::Response::INSTANTIATED_VM_NETWORK_SECTION_LINK => lambda { - |url, data, headers| - change_vm_task_state = "running" - Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING - }, - Test::Response::METADATA_SET_LINK => lambda { - |url, data, headers| - received = Xml::WrapperFactory.wrap_document(data) - metadata_value = received.value - metadata_xml = data - Test::Response::METADATA_SET_TASK_DONE - } - }, - :delete => { - Test::Response::VAPP_TEMPLATE_LINK => lambda { |url, headers| - Test::Response::VAPP_TEMPLATE_DELETE_RUNNING_TASK - }, - Test::Response::INSTANTIATED_VAPP_LINK => lambda { - |url, headers| - Test::Response::INSTANTIATED_VAPP_DELETE_RUNNING_TASK - }, - Test::Response::CATALOG_ITEM_VAPP_LINK => lambda { - |url, headers| - nil - }, - Test::Response::EXISTING_MEDIA_LINK => lambda { |url, headers| - Test::Response::EXISTING_MEDIA_DELETE_TASK_DONE - }, - Test::Response::EXISTING_MEDIA_CATALOG_ITEM_LINK => lambda { - |url, headers| - nil - }, - Test::Response::MEDIA_LINK => lambda { |url, headers| - Test::Response::MEDIA_DELETE_TASK_DONE - }, - Test::Response::INDY_DISK_URL => lambda { |url, headers| - Test::Response::INDY_DISK_DELETE_TASK - }, - } + end } + } - #Working around Ruby 1.8"s lack of define_singleton_method - metaclass = class << response_mapping; - self; - end + def response_mapping.get_mapping(http_method, url) + mapping = self[http_method][url] + if mapping.nil? + err_msg = "Response mapping not found for #{http_method} and #{url}" + Config.logger.error(err_msg) + raise err_msg + end - metaclass.send :define_method, :get_mapping do |http_method, url| - mapping = self[http_method][url] - if mapping.nil? - Config.logger.error("Response mapping not found for " + - "#{http_method} and #{url}") - # string substitution doesn"t work here for some reason - raise "Response mapping not found." - else - mapping - end - end + mapping + end - rest_client.stub(:get) do |headers| - response_mapping.get_mapping(:get, build_url).call(build_url, headers) - end - rest_client.stub(:post) do |data, headers| - response_mapping.get_mapping(:post, build_url).call(build_url, data, - headers) - end - rest_client.stub(:put) do |data, headers| - response_mapping.get_mapping(:put, build_url).call(build_url, data, - headers) - end - rest_client.stub(:delete) do |headers| - response_mapping.get_mapping(:delete, build_url).call(build_url, - headers) - end - rest_client.stub(:vapp_state=) do |value| - current_vapp_state = value - end - rest_client.stub(:vapp_state) do - current_vapp_state - end - rest_client.stub(:response_mapping) do - response_mapping - end - rest_client.stub(:vapp_power_state=) do |value| - vapp_power_state = value - end - rest_client.stub(:[]) do |value| - @resource = value - @rest_connection - end + describe Client, :min, :all do - rest_client - end + let(:url) { 'https://10.147.0.0:8443' } + let(:username) { 'cfadmin' } + let(:password) { 'akimbi' } + let(:response_mapping) { response_mapping } def build_url url + @resource end - def nested(url) - URI.parse(url).path - end - - class MockRestClient - class << self - attr_accessor :log - end - end - - def create_mock_client - @rest_connection = mock_rest_connection() - file_uploader = double("File Uploader") - file_uploader.stub(:upload) do - @rest_connection.vapp_state = @upload_file_state == "success" ? - "disks_uploaded" : "disks_upload_failed" - end - conn = Connection::Connection.new(url, entities["organization"], - control["time_limit_sec"]["http_request"], MockRestClient, - @rest_connection, file_uploader) - conn.stub(:file_uploader) do - file_uploader - end - conn.stub(:rest_connection) do - @rest_connection - end - conn - end - - def create_mock_ovf_directory(string_io) - directory = double("Directory") - # Actual content of the OVF is irrelevant as long as the client gives - # back the same one given to it - ovf_string = "ovf_string" - ovf_string_io = StringIO.new(ovf_string) - directory.stub(:ovf_file_path) { "ovf_file" } - directory.stub(:ovf_file) { - ovf_string_io - } - directory.stub(:vmdk_file_path) do |file_name| - file_name - end - directory.stub(:vmdk_file) do |file_name| - string_io - end - directory - end - - def create_mock_media_file() - media_string = Test::Response::MEDIA_CONTENT - string_io = StringIO.new(media_string) - string_io.stub(:path) { "bogus/bogus.iso" } - string_io.stub(:stat) { - o = Object.new - o.stub(:size) { media_string.length } - o - } - string_io - end - - - describe "VCD Adapter client", :positive, :min, :all do - it "logs into organization with usename and password and get the " + - "organization VDC" do - conn = double("Connection") - root_session = Xml::WrapperFactory.wrap_document( - Test::Response::SESSION) - vcloud_response = Xml::WrapperFactory.wrap_document( - Test::Response::VCLOUD_RESPONSE) - admin_org_response = Xml::WrapperFactory.wrap_document( - Test::Response::ADMIN_ORG_RESPONSE) - vdc_response = Xml::WrapperFactory.wrap_document( - Test::Response::VDC_RESPONSE) - conn.should_receive(:connect).with(username, password).and_return( - root_session) - conn.should_receive(:get).with(root_session.admin_root).and_return( - vcloud_response) - conn.should_receive(:get).with(vcloud_response.organization( - entities["organization"])).and_return(admin_org_response) - Client.new(nil, username, password, entities, control, conn) - end - end - - describe "VCD Adapter client", :upload, :all do - it "uploads an OVF to the OVDC", :positive do - vmdk_string = "vmdk" - vmdk_string_io = StringIO.new(vmdk_string) - directory = create_mock_ovf_directory(vmdk_string_io) - - conn = create_mock_client - - vapp_name = Test::Response::VAPP_TEMPLATE_NAME - - client = Client.new(nil, username, password, entities, control, conn) - - conn.file_uploader.should_receive(:upload).with( - Test::Response::VAPP_TEMPLATE_DISK_UPLOAD_1, vmdk_string_io, - auth_cookies) - catalog_item = client.upload_vapp_template(vapp_name, directory) - # Since the wrapper classes are used to hide away the raw XML, - # compare the wrapped versions - catalog_item.should eq(Xml::WrapperFactory.wrap_document( - Test::Response::CATALOG_ADD_ITEM_RESPONSE)) - end - - it "reports an exception upon error in transferring an OVF file", - :negative do - vmdk_string = "vmdk" - vmdk_string_io = StringIO.new(vmdk_string) - directory = create_mock_ovf_directory(vmdk_string_io) - - conn = create_mock_client - @upload_file_state = "failed" - vapp_name = Test::Response::VAPP_TEMPLATE_NAME - client = Client.new(nil, username, password, entities, control, conn) - - conn.file_uploader.should_receive(:upload).with( - Test::Response::VAPP_TEMPLATE_DISK_UPLOAD_1, vmdk_string_io, - auth_cookies) - expect { - client.upload_vapp_template(vapp_name, directory) - }.to raise_exception("Error uploading vApp template") - @upload_file_state = "success" - end - - it "deletes the vApp template if there's an error uploading it to " + - "the catalog", :negative do - vmdk_string = "vmdk" - vmdk_string_io = StringIO.new(vmdk_string) - directory = create_mock_ovf_directory(vmdk_string_io) - - conn = create_mock_client - conn.rest_connection.response_mapping[:post][ - Test::Response::CATALOG_ADD_ITEM_LINK] = - lambda { |url, data, headers| - raise ApiError, "Bogus add to catalog error" - } - vapp_name = Test::Response::VAPP_TEMPLATE_NAME - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::VAPP_TEMPLATE_LINK)) - conn.rest_connection.should_receive(:delete).with(anything()) - client = Client.new(nil, username, password, entities, control, conn) - expect { - client.upload_vapp_template(vapp_name, directory) - }.to raise_exception("Bogus add to catalog error") - end - - end - - describe "VCD Adapter client", :delete, :all do - it "deletes a vApp template from the catalog", :positive do - vmdk_string = "vmdk" - vmdk_string_io = StringIO.new(vmdk_string) - directory = create_mock_ovf_directory(vmdk_string_io) - - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - vapp_name = Test::Response::VAPP_TEMPLATE_NAME - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::VAPP_TEMPLATE_LINK)) - conn.rest_connection.should_receive(:delete).with(anything()) - catalog_item = client.upload_vapp_template(vapp_name, directory) - client.delete_catalog_vapp(catalog_item.urn) - end - - it "no exception for delete catalog vapp if the vApp does not exist", - :negative do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - client.delete_catalog_vapp("bogus") - end - - it "raise exception for upload media to catalog if the catalog does " + - "not exist", :negative do - conn = create_mock_client - ent_clone = entities.clone - ent_clone["media_catalog"] = "bogus" - media_file = create_mock_media_file() - client = Client.new(nil, username, password, ent_clone, control, conn) - media_name = Test::Response::MEDIA_NAME - - conn.file_uploader.stub(:upload) do |href, data, headers| - href.should eq(Test::Response::MEDIA_ISO_LINK) - data.read.should eq(Test::Response::MEDIA_CONTENT) - end - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::MEDIA_LINK)) - conn.rest_connection.should_receive(:delete).with(anything()) - expect { - client.upload_catalog_media(media_name, media_file) - }.to raise_exception(/.+ catalog .+ not found\./) - end - - it "should not raise if underlying vApp no longer exists when " + - "deleting a vApp from the catalog", :negative do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - conn.rest_connection.response_mapping[:delete][ - Test::Response::VAPP_TEMPLATE_LINK] = lambda { |urls, headers| - raise RestClient::ResourceNotFound - } - client.delete_catalog_vapp(Test::Response::VAPP_TEMPLATE_NAME) - end - end - - describe "VCD Adapter client", :instantiate_template, :all do - xit "instantiates a vApp from the catalog", :positive do - # marked pending because it failed on our local dev environment, - # and it's unclear if BOSH team is going to maintain ownership - # of this gem - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN - vapp_name = Test::Response::VAPP_NAME - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::VAPP_TEMPLATE_INSTANTIATE_LINK)) - conn.rest_connection.should_receive(:post).with( - Test::Response::EXISTING_VAPP_TEMPLATE_INSTANTIATE_REQUEST, - anything()) - client.instantiate_vapp_template(catalog_vapp_id, vapp_name) - end - - it "instantiates with locality a vApp from the catalog", :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN - vapp_name = Test::Response::VAPP_NAME - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::VAPP_TEMPLATE_INSTANTIATE_LINK)) - conn.rest_connection.should_receive(:post).with( - Test::Response:: - EXISTING_VAPP_TEMPLATE_INSTANTIATE_WITH_LOCALITY_REQUEST, - anything()) - client.instantiate_vapp_template(catalog_vapp_id, vapp_name, "desc", - [ Test::Response::INDY_DISK_URL ]) - end - - it "instantiates raises an exception if the task fails", :negative do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN - vapp_name = Test::Response::VAPP_NAME - conn.rest_connection.response_mapping[:get][ - Test::Response::EXISTING_VAPP_TEMPLATE_INSTANTIATE_TASK_LINK] = - lambda { |url, headers| - Test::Response:: - EXISTING_VAPP_TEMPLATE_INSTANTIATE_TASK_ERROR_RESPONSE - } - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_LINK)) - conn.rest_connection.should_receive(:delete).with(anything()) - expect { - client.instantiate_vapp_template(catalog_vapp_id, vapp_name) - }.to raise_exception(/.+Creating Virtual.+did not complete success.+/) - end - - end - - describe "VCD Adapter client", :modify_vm, :all do - it "reconfigures a VM", :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN - vapp_name = Test::Response::VAPP_NAME - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::RECONFIGURE_VM_LINK)) - conn.rest_connection.should_receive(:post).with( - Test::Response::RECONFIGURE_VM_REQUEST, anything()) - vapp = client.instantiate_vapp_template(catalog_vapp_id, vapp_name) - vm = vapp.vms.first - client.reconfigure_vm(vm) do |v| - vm.name = Test::Response::CHANGED_VM_NAME - vm.description = Test::Response::CHANGED_VM_DESCRIPTION - v.change_cpu_count(Test::Response::CHANGED_VM_CPU) - v.change_memory(Test::Response::CHANGED_VM_MEMORY) - v.add_hard_disk(Test::Response::CHANGED_VM_DISK) - v.delete_nic(*vm.hardware_section.nics) - end - end - - xit "gets information on HD", :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - vm = vapp.vms.first - vm.hardware_section.hard_disks.each do |h| - h.capacity_mb.should_not be_nil - h.disk_id.should_not be_nil - h.bus_sub_type.should_not be_nil - h.bus_type.should_not be_nil - end - - vm.hardware_section.nics.each do |n| - n.mac_address.should_not be_nil - end - - end - - end - - describe "VCD Adapter client", :power_vapp, :all do - it "finds and powers on a vApp and powers it off" , :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_POWER_ON_LINK)) - conn.rest_connection.should_receive(:post).with( - anything(), anything()) - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_POWER_OFF_LINK)) - conn.rest_connection.should_receive(:post).with( - anything(), anything()) - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - client.power_on_vapp(vapp) - client.power_off_vapp(vapp, false) - end - - it "finds and powers on a powered-on vApp" , :negative do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - client.power_on_vapp(vapp) - conn.rest_connection.should_not_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_POWER_ON_LINK)) - conn.rest_connection.should_not_receive(:post).with( - anything(), anything()) - client.power_on_vapp(vapp) - end - - it "finds and powers off a powered-off vApp" , :negative do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - client.power_off_vapp(vapp, false) - conn.rest_connection.should_not_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_POWER_OFF_LINK)) - conn.rest_connection.should_not_receive(:post).with( - anything(), anything()) - client.power_off_vapp(vapp, false) - end - - it "finds and powers on a vApp and undeploys it" , :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_POWER_ON_LINK)) - conn.rest_connection.should_receive(:post).with( - anything(), anything()) - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_UNDEPLOY_LINK)) - conn.rest_connection.should_receive(:post).with( - anything(), anything()) - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - client.power_on_vapp(vapp) - client.power_off_vapp(vapp) - end - - it "finds and undeploys an undeployed vapp" , :negative do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - client.power_off_vapp(vapp) - conn.rest_connection.should_not_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_UNDEPLOY_LINK)) - conn.rest_connection.should_not_receive(:post).with( - anything(), anything()) - client.power_off_vapp(vapp) - end - - it "finds and undeploys a powered-off vapp" , :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - client.power_on_vapp(vapp) - client.power_off_vapp(vapp, false) - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_UNDEPLOY_LINK)) - conn.rest_connection.should_receive(:post).with( - Test::Response::UNDEPLOY_PARAMS, anything()) - client.power_off_vapp(vapp) - end - - it "discards state of a suspended vapp" , :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - conn.rest_connection.response_mapping[:get][ - Test::Response::EXISTING_VAPP_LINK] = lambda { |url, headers| - Test::Response::INSTANTIATED_SUSPENDED_VAPP_RESPONSE } - conn.rest_connection.vapp_power_state = "suspended" - - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_DISCARD_STATE_LINK)) - conn.rest_connection.should_receive(:post).with( - anything(), anything()) - - client.discard_suspended_state_vapp(vapp) - end - - it "finds and powers on a vApp and reboots it" , :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_POWER_ON_LINK)) - conn.rest_connection.should_receive(:post).with( - anything(), anything()) - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_POWER_REBOOT_LINK)) - conn.rest_connection.should_receive(:post).with( - anything(), anything()) - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - client.power_on_vapp(vapp) - client.reboot_vapp(vapp) - end - - it "reboot of a powered-off vApp raises an exception" , :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - expect { - client.reboot_vapp(vapp) - }.to raise_exception(VappPoweredOffError) - end - - it "reboot of a suspended vApp raises an exception" , :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - conn.rest_connection.vapp_power_state = "suspended" - - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - expect { - client.reboot_vapp(vapp) - }.to raise_exception(VappSuspendedError) - end - end - - describe "VCD Adapter client", :delete_vapp, :all do - it "finds a vApp and deletes it without undeploying", :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - conn.rest_connection.should_not_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_UNDEPLOY_LINK)) - conn.rest_connection.should_not_receive(:post).with( - anything(), anything()) - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INSTANTIATED_VAPP_LINK)) - conn.rest_connection.should_receive(:delete).with(anything()) - client.delete_vapp(vapp) - end - - it "finds and powers on a vApp and fails on delete vApp", :negative do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - client.power_on_vapp(vapp) - conn.rest_connection.should_receive(:[]).once.with( - nested(Test::Response::INSTANTIATED_VAPP_LINK)) - conn.rest_connection.should_not_receive(:delete).with(anything()) - expect { - client.delete_vapp(vapp) - }.to raise_exception(/vApp .+ powered on, power-off before deleting./) - end - end - - describe "VCD Adapter client", :add_vapp_network, :all do - it "finds a network and vapp and adds the network to the vApp", - :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - network_name = Test::Response::ORG_NETWORK_NAME - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - network = client.get_ovdc.available_networks.find { - |n| n["name"] == network_name } - conn.rest_connection.should_receive(:[]).with(nested( - Test::Response::INSTANTIATED_VAPP_NETWORK_CONFIG_SECTION_LINK)) - conn.rest_connection.should_receive(:put).with(Test::Response:: - INSTANTIATED_VAPP_NETWORK_CONFIG_ADD_NETWORK_REQUEST, - anything()) - client.add_network(vapp, network) - end - - it "removes all networks", :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - - conn.rest_connection.response_mapping[:put][ - Test::Response::INSTANTIATED_VAPP_NETWORK_CONFIG_SECTION_LINK] = - lambda { |url, data, headers| - expected = Xml::WrapperFactory.wrap_document( - Test::Response:: - INSTANTIATED_VAPP_NETWORK_CONFIG_REMOVE_NETWORK_REQUEST) - received = Xml::WrapperFactory.wrap_document(data) - received.should eq(expected) - Test::Response:: - INSTANTIATED_VAPP_NETWORK_CONFIG_MODIFY_NETWORK_TASK_SUCCESS - } - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - client.delete_networks(vapp) - end - end - - describe "VCD Adapter client", :add_remove_insert_media, :all do - it "uploads media to VDC and adds it to the catalog", :positive do - conn = create_mock_client - media_file = create_mock_media_file() - client = Client.new(nil, username, password, entities, control, conn) - media_name = Test::Response::MEDIA_NAME - - conn.file_uploader.stub(:upload) do |href, data, headers| - href.should eq(Test::Response::MEDIA_ISO_LINK) - data.read.should eq(Test::Response::MEDIA_CONTENT) - end - - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::CATALOG_ADD_ITEM_LINK)) - conn.rest_connection.should_receive(:post).with( - Test::Response::MEDIA_ADD_TO_CATALOG_REQUEST, anything()) - - client.upload_catalog_media(media_name, media_file) - - end - - it "deletes a media from catalog and the VDC", :positive do - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - media_name = Test::Response::EXISTING_MEDIA_NAME - - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::EXISTING_MEDIA_LINK)) - conn.rest_connection.should_receive(:delete).with(anything()) - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::EXISTING_MEDIA_CATALOG_ITEM_LINK)) - conn.rest_connection.should_receive(:delete).with(anything()) - client.delete_catalog_media(media_name) - - end - - it "uploads media to VDC, fails to add to catalog, and rolls back", - :negative do - conn = create_mock_client - media_file = create_mock_media_file() - client = Client.new(nil, username, password, entities, control, conn) - media_name = Test::Response::MEDIA_NAME - - conn.rest_connection.response_mapping[:post][ - Test::Response::CATALOG_ADD_ITEM_LINK] = lambda { - |url, data, headers| - raise ApiError, "bogus error" - } - - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::MEDIA_LINK)) - conn.rest_connection.should_receive(:delete).with(anything()) - expect { - client.upload_catalog_media(media_name, media_file) - }.to raise_exception(/bogus error/) - + def mock_rest_connection + rest_client = double('Rest Client') + rest_client.stub(:get) do |headers| + response_mapping.get_mapping(:get, build_url).call(build_url, headers) end - - it "inserts a media into a VM", :positive do - conn = create_mock_client - - client = Client.new(nil, username, password, entities, control, conn) - media_name = Test::Response::EXISTING_MEDIA_NAME - - catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN - vapp_name = Test::Response::VAPP_NAME - - vapp = client.instantiate_vapp_template(catalog_vapp_id, vapp_name) - vm = vapp.vms.first - client.insert_catalog_media(vm, media_name) - + rest_client.stub(:post) do |data, headers| + response_mapping.get_mapping(:post, build_url).call(build_url, data, + headers) end - - end - - describe "VCD Adapter client", :metadata, :all do - it "sets and gets metadata on a VM", :positive do - conn = create_mock_client - - client = Client.new(nil, username, password, entities, control, conn) - - metadata_key = Test::Response::METADATA_KEY - metadata_value = Test::Response::METADATA_VALUE - vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) - vm = vapp.vms.first - client.set_metadata(vm, metadata_key, metadata_value) - value = client.get_metadata(vm, metadata_key) - value.should eq metadata_value + rest_client.stub(:[]) do |value| + @resource = value + rest_client end + conn = Connection::Connection.new(url, nil, nil, rest_client) end - describe "VCD Adapter client", :indy_disk, :all do - it "creates an indepdent disk", :positive do - disk_name = Test::Response::INDY_DISK_NAME - disk_size = Test::Response::INDY_DISK_SIZE - - conn = create_mock_client - - client = Client.new(nil, username, password, entities, control, conn) - - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::VDC_INDY_DISKS_LINK)) - conn.rest_connection.should_receive(:post).with( - Test::Response::INDY_DISK_CREATE_REQUEST, anything()) - - client.create_disk(disk_name, disk_size) - + describe '.initialize' do + it 'set up connection successfully' do + conn = mock_rest_connection + Connection::Connection.stub(:new).with(anything, anything).and_return conn + Client.new(url, username, password, {}, logger) end - - it "creates an indepdent disk but hits an error", :negative do - disk_name = Test::Response::INDY_DISK_NAME - disk_size = Test::Response::INDY_DISK_SIZE - - conn = create_mock_client - - client = Client.new(nil, username, password, entities, control, conn) - - conn.rest_connection.response_mapping[:post][ - Test::Response::VDC_INDY_DISKS_LINK] = lambda { - |href, data, headers| - Test::Response::INDY_DISK_CREATE_ERROR - } - expect { - client.create_disk(disk_name, disk_size) - }.to raise_exception(ApiRequestError) - - end - - it "attaches an indepdent disk to a VM and then detaches", :positive do - disk_name = Test::Response::INDY_DISK_NAME - - conn = create_mock_client - - client = Client.new(nil, username, password, entities, control, conn) - - catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN - vapp_name = Test::Response::VAPP_NAME - - vapp = client.instantiate_vapp_template(catalog_vapp_id, vapp_name) - vm = vapp.vms.first - - vdc = client.get_ovdc() - disk = vdc.disks(disk_name).first - - conn.rest_connection.response_mapping[:post][ - Test::Response::INSTANTIATED_VM_ATTACH_DISK_LINK] = lambda { - |href, data, headers| - data_xml = Nokogiri::XML(data) - expected_xml = Nokogiri::XML( - Test::Response::INDY_DISK_ATTACH_REQUEST) - equality = VCloudSdk::Test::compare_xml(data_xml, expected_xml) - equality.should == true - Test::Response::INDY_DISK_ATTACH_TASK - } - - client.attach_disk(disk, vm) - - conn.rest_connection.response_mapping[:post][ - Test::Response::INSTANTIATED_VM_DETACH_DISK_LINK] = lambda { - |href, data, headers| - data_xml = Nokogiri::XML(data) - expected_xml = Nokogiri::XML( - Test::Response::INDY_DISK_DETACH_REQUEST) - equality = VCloudSdk::Test::compare_xml(data_xml, expected_xml) - equality.should == true - Test::Response::INDY_DISK_DETACH_TASK - } - - client.detach_disk(disk, vm) - - - end - - it "attaches an indepdent disk to a VM but has an error", :negative do - disk_name = Test::Response::INDY_DISK_NAME - - conn = create_mock_client - - client = Client.new(nil, username, password, entities, control, conn) - - catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN - vapp_name = Test::Response::VAPP_NAME - - vapp = client.instantiate_vapp_template(catalog_vapp_id, vapp_name) - vm = vapp.vms.first - - vdc = client.get_ovdc() - disk = vdc.disks(disk_name).first - - conn.rest_connection.response_mapping[:post][ - Test::Response::INSTANTIATED_VM_ATTACH_DISK_LINK] = lambda { - |href, data, headers| - Test::Response::INDY_DISK_ATTACH_TASK_ERROR - } - - expect { - client.attach_disk(disk, vm) - }.to raise_exception(/.+ Attaching Disk .+ complete successfully\./) - - end - - it "deletes an independent disk", :positive do - disk_name = Test::Response::INDY_DISK_NAME - - conn = create_mock_client - client = Client.new(nil, username, password, entities, control, conn) - vdc = client.get_ovdc() - disk = vdc.disks(disk_name).first - conn.rest_connection.should_receive(:[]).with( - nested(Test::Response::INDY_DISK_URL)) - conn.rest_connection.should_receive(:delete).with(anything()) - client.delete_disk(disk) - end - end - end end diff --git a/spec/unit/client_spec_old.rb b/spec/unit/client_spec_old.rb new file mode 100644 index 0000000..a2abac4 --- /dev/null +++ b/spec/unit/client_spec_old.rb @@ -0,0 +1,1155 @@ +require "spec_helper" +require_relative "client_response" +require "stringio" +require "logger" +require "nokogiri/diff" + +module VCloudSdk + vcd = VCloudSdk::Test::vcd_settings + logger = Config.logger + + Config.configure({"logger" => logger, + "rest_logger" =>VCloudSdk::Test::rest_logger(logger), + "rest_throttle" => vcd["control"]["rest_throttle"]}) + + describe Client, :min, :all do + let(:url) { vcd["url"] } + let(:username) { vcd["user"] } + let(:password) { vcd["password"] } + let(:control) { vcd["control"] } + let(:entities) { vcd["entities"] } + let(:auth_cookies) { {"vcloud-token" => vcd["testing"]["cookies"]} } + + def mock_rest_connection + @upload_file_state = "success" + current_vapp_state = "nothing" + finalize_vapp_task_state = "running" + delete_vapp_template_task_state = "running" + delete_vapp_task_state = "running" + change_vm_task_state = "running" + catalog_state = "not_added" + template_instantiate_state = "running" + vapp_power_state = "off" + existing_media_state = "busy" + metadata_value = "" + metadata_xml = "" + rest_client = double("Rest Client") + response_mapping = { + :get => { + Test::Response::ADMIN_VCLOUD_LINK => lambda { + |url, headers| Test::Response::VCLOUD_RESPONSE }, + Test::Response::ADMIN_ORG_LINK => lambda { + |url, headers| Test::Response::ADMIN_ORG_RESPONSE }, + Test::Response::VDC_LINK => lambda { + |url, headers| Test::Response::VDC_RESPONSE }, + Test::Response::CATALOG_LINK => lambda { |url, headers| + case (catalog_state) + when "not_added" + Test::Response::CATALOG_RESPONSE + when "added" + Test::Response::CATALOG_ITEM_ADDED_RESPONSE + end + }, + Test::Response::CATALOG_ITEM_VAPP_LINK => lambda { + |url, headers| Test::Response::CATALOG_ADD_ITEM_RESPONSE + }, + Test::Response::VAPP_TEMPLATE_LINK => lambda { |url, headers| + case (current_vapp_state) + when "ovf_uploaded" + Test::Response::VAPP_TEMPLATE_NO_DISKS_RESPONSE + when "nothing" + Test::Response::VAPP_TEMPLATE_UPLOAD_OVF_WAITING_RESPONSE + when "disks_uploaded" + Test::Response::VAPP_TEMPLATE_UPLOAD_COMPLETE + when "disks_upload_failed" + Test::Response::VAPP_TEMPLATE_UPLOAD_FAILED + when "finalized" + Test::Response::VAPP_TEMPLATE_READY_RESPONSE + end + }, + Test::Response::FINALIZE_UPLOAD_TASK_LINK => lambda { + |url, headers| + case (finalize_vapp_task_state) + when "running" + finalize_vapp_task_state = "success" + current_vapp_state = "finalized" + Test::Response::FINALIZE_UPLOAD_TASK_RESPONSE + when "success" + Test::Response::FINALIZE_UPLOAD_TASK_DONE_RESPONSE + end + }, + Test::Response::VAPP_TEMPLATE_DELETE_TASK_LINK => lambda { + |url, headers| + case (delete_vapp_template_task_state) + when "running" + delete_vapp_template_task_state = "success" + Test::Response::VAPP_TEMPLATE_DELETE_RUNNING_TASK + when "success" + Test::Response::VAPP_TEMPLATE_DELETE_DONE_TASK + end + }, + Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_ITEM_LINK => + lambda { |url, headers| + Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_ITEM_RESPONSE + }, + Test::Response::EXISTING_VAPP_TEMPLATE_LINK => lambda { + |url, headers| + Test::Response::EXISTING_VAPP_TEMPLATE_READY_RESPONSE + }, + Test::Response::EXISTING_VAPP_TEMPLATE_INSTANTIATE_TASK_LINK => + lambda { |url, headers| + case (template_instantiate_state) + when "running" + template_instantiate_state = "success" + Test::Response:: + EXISTING_VAPP_TEMPLATE_INSTANTIATE_TASK_START_RESPONSE + when "success" + Test::Response:: + EXISTING_VAPP_TEMPLATE_INSTANTIATE_TASK_SUCCESS_RESPONSE + end + }, + Test::Response::INSTANTIATED_VAPP_LINK => lambda { + |url, headers| + case(vapp_power_state) + when "off" + Test::Response::INSTANTIAED_VAPP_RESPONSE + when "on" + Test::Response::INSTANTIAED_VAPP_ON_RESPONSE + when "powered-off" + Test::Response::INSTANTIAED_VAPP_POWERED_OFF_RESPONSE + when "suspended" + Test::Response::INSTANTIATED_SUSPENDED_VAPP_RESPONSE + end + }, + Test::Response::INSTANTIATED_VAPP_DELETE_TASK_LINK => lambda { + |url, headers| + case (delete_vapp_task_state) + when "running" + delete_vapp_task_state = "success" + Test::Response::INSTANTIATED_VAPP_DELETE_RUNNING_TASK + when "success" + Test::Response::INSTANTIATED_VAPP_DELETE_DONE_TASK + end + }, + Test::Response::INSTANTIATED_VM_LINK => lambda { |url, headers| + Test::Response::INSTANTIATED_VM_RESPONSE + }, + Test::Response::INSTANTIATED_VM_CPU_LINK => lambda { + |url, headers| Test::Response::INSTANTIATED_VM_CPU_RESPONSE + }, + Test::Response::INSTANTIATED_VM_MEMORY_LINK => lambda { + |url, headers| Test::Response::INSTANTIATED_VM_MEMORY_RESPONSE + }, + Test::Response::INSTANTIATED_VM_MODIFY_TASK_LINK => lambda { + |url, headers| + case(change_vm_task_state) + when "running" + change_vm_task_state = "success" + Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING + when "success" + Test::Response::INSTANTIATED_VM_MODIFY_TASK_SUCCESS + end + }, + Test::Response::EXISTING_VAPP_LINK => lambda { |url, headers| + Test::Response::INSTANTIAED_VAPP_RESPONSE + }, + Test::Response::INSTANTIATED_VAPP_POWER_ON_TASK_LINK => lambda { + |url, headers| + Test::Response::INSTANTED_VAPP_POWER_TASK_SUCCESS + }, + Test::Response::ORG_NETWORK_LINK => lambda { |url, headers| + Test::Response::ORG_NETWORK_RESPONSE + }, + Test::Response::INSTANTIATED_VAPP_NETWORK_CONFIG_SECTION_LINK => + lambda { |url, headers| + Test::Response:: + INSTANTIATED_VAPP_NETWORK_CONFIG_SECTION_RESPONSE + }, + Test::Response:: + INSTANTIATED_VAPP_NETWORK_CONFIG_ADD_NETWORK_TASK_LINK => + lambda { |url, headers| + Test::Response:: + INSTANTIATED_VAPP_NETWORK_CONFIG_MODIFY_NETWORK_TASK_SUCCESS + }, + Test::Response::INSTANTIATED_VM_NETWORK_SECTION_LINK => lambda { + |url, headers| + Test::Response::INSTANTIATED_VM_NETWORK_SECTION_RESPONSE + }, + Test::Response::MEDIA_LINK => lambda { |url, headers| + Test::Response::MEDIA_UPLOAD_PENDING_RESPONSE + }, + Test::Response::EXISTING_MEDIA_CATALOG_ITEM_LINK => lambda { + |url, headers| Test::Response::EXISTING_MEDIA_CATALOG_ITEM + }, + Test::Response::EXISTING_MEDIA_LINK => lambda { |url, headers| + case(existing_media_state) + when "busy" + existing_media_state = "done" + Test::Response::EXISTING_MEDIA_BUSY_RESPONSE + when "done" + Test::Response::EXISTING_MEDIA_DONE_RESPONSE + end + }, + Test::Response::METADATA_SET_LINK => lambda { |url, headers| + metadata_xml + }, + Test::Response::INDY_DISK_URL => lambda { |url, headers| + Test::Response::INDY_DISK_RESPONSE + }, + Test::Response::EXISTING_VAPP_RESOLVER_URL => lambda { + |url,headers| Test::Response::EXISTING_VAPP_RESOLVER_RESPONSE + }, + Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_RESOLVER_URL => + lambda { |url,headers| + Test::Response:: + EXISTING_VAPP_TEMPLATE_CATALOG_RESOLVER_RESPONSE + }, + Test::Response::VAPP_TEMPLATE_CATALOG_RESOLVER_URL => lambda { + |url,headers| + Test::Response::VAPP_TEMPLATE_CATALOG_RESOLVER_RESPONSE + } + }, + :post => { + Test::Response::LOGIN_LINK => lambda { |url, data, headers| + session_object = Test::Response::SESSION + + def session_object.cookies + {"vcloud-token" => + VCloudSdk::Test::vcd_settings["testing"]["cookies"]} + end + + session_object + }, + Test::Response::VDC_VAPP_UPLOAD_LINK => lambda { + |url, data, headers| + current_vapp_state = "nothing" + Test::Response::VAPP_TEMPLATE_UPLOAD_OVF_WAITING_RESPONSE + }, + Test::Response::CATALOG_ADD_ITEM_LINK => lambda { + |url, data, headers| + case(Xml::WrapperFactory.wrap_document(data)) + when Xml::WrapperFactory.wrap_document( + Test::Response::CATALOG_ADD_VAPP_REQUEST) + catalog_state = "added" + Test::Response::CATALOG_ADD_ITEM_RESPONSE + when Xml::WrapperFactory.wrap_document( + Test::Response::MEDIA_ADD_TO_CATALOG_REQUEST) + catalog_state = "added" + Test::Response::MEDIA_ADD_TO_CATALOG_RESPONSE + else + Config.logger.error("Response mapping not found for " + + "POST and #{url} and #{data}") + raise "Response mapping not found." + end + }, + Test::Response::VAPP_TEMPLATE_INSTANTIATE_LINK => lambda { + |url, data, headers| + Test::Response::EXISTING_VAPP_TEMPLATE_INSTANTIATE_RESPONSE + }, + Test::Response::RECONFIGURE_VM_LINK => lambda { + |url, data, headers| + Test::Response::RECONFIGURE_VM_TASK + }, + Test::Response::INSTANTIATED_VAPP_POWER_ON_LINK => lambda { + |url, data, headers| + vapp_power_state = "on" + Test::Response::INSTANTED_VAPP_POWER_TASK_RUNNING + }, + Test::Response::INSTANTIATED_VAPP_POWER_OFF_LINK => lambda { + |url, data, headers| + vapp_power_state = "powered-off" + Test::Response::INSTANTED_VAPP_POWER_TASK_RUNNING + }, + Test::Response::INSTANTIATED_VAPP_POWER_REBOOT_LINK => lambda { + |url, data, headers| + vapp_power_state = "on" + Test::Response::INSTANTED_VAPP_POWER_TASK_RUNNING + }, + Test::Response::INSTANTIATED_VAPP_UNDEPLOY_LINK => lambda { + |url, data, headers| + vapp_power_state = "off" + Test::Response::INSTANTED_VAPP_POWER_TASK_RUNNING + }, + Test::Response::INSTANTIATED_VAPP_DISCARD_STATE_LINK => lambda { + |url, data, headers| + vapp_power_state = "off" + Test::Response::INSTANTED_VAPP_POWER_TASK_RUNNING + }, + Test::Response::MEDIA_UPLOAD_LINK => lambda { + |url, data, headers| + Test::Response::MEDIA_UPLOAD_PENDING_RESPONSE + }, + Test::Response::INSTANTIATED_VM_INSERT_MEDIA_LINK => lambda { + |url, data, headers| + Test::Response::INSTANTIATED_VM_INSERT_MEDIA_TASK_DONE + }, + Test::Response::VDC_INDY_DISKS_LINK => lambda { + |url, data, headers| + Test::Response::INDY_DISK_CREATE_RESPONSE + }, + Test::Response::INSTANTIATED_VM_ATTACH_DISK_LINK => lambda { + |url, data, headers| + Test::Response::INDY_DISK_ATTACH_TASK + } + }, + :put => { + Test::Response::VAPP_TEMPLATE_UPLOAD_OVF_LINK => lambda { + |url, data, headers| + current_vapp_state = "ovf_uploaded" + "" + }, + Test::Response::INSTANTIATED_VM_CPU_LINK => lambda { + |url, data, headers| + change_vm_task_state = "running" + Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING + }, + Test::Response::INSTANTIATED_VM_MEMORY_LINK => lambda { + |url, data, headers| + change_vm_task_state = "running" + Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING + }, + Test::Response::INSTANTIATED_VM_LINK => lambda { + |url, data, headers| + change_vm_task_state = "running" + Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING + }, + Test::Response::INSTANTIATED_VM_HARDWARE_SECTION_LINK => + lambda { |url, data, headers| + change_vm_task_state = "running" + Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING + }, + Test::Response::INSTANTIATED_VAPP_NETWORK_CONFIG_SECTION_LINK => + lambda { |url, data, headers| + Test::Response:: + INSTANTIATED_VAPP_NETWORK_CONFIG_MODIFY_NETWORK_TASK_SUCCESS + }, + Test::Response::INSTANTIATED_VM_NETWORK_SECTION_LINK => lambda { + |url, data, headers| + change_vm_task_state = "running" + Test::Response::INSTANTIATED_VM_MODIFY_TASK_RUNNING + }, + Test::Response::METADATA_SET_LINK => lambda { + |url, data, headers| + received = Xml::WrapperFactory.wrap_document(data) + metadata_value = received.value + metadata_xml = data + Test::Response::METADATA_SET_TASK_DONE + } + }, + :delete => { + Test::Response::VAPP_TEMPLATE_LINK => lambda { |url, headers| + Test::Response::VAPP_TEMPLATE_DELETE_RUNNING_TASK + }, + Test::Response::INSTANTIATED_VAPP_LINK => lambda { + |url, headers| + Test::Response::INSTANTIATED_VAPP_DELETE_RUNNING_TASK + }, + Test::Response::CATALOG_ITEM_VAPP_LINK => lambda { + |url, headers| + nil + }, + Test::Response::EXISTING_MEDIA_LINK => lambda { |url, headers| + Test::Response::EXISTING_MEDIA_DELETE_TASK_DONE + }, + Test::Response::EXISTING_MEDIA_CATALOG_ITEM_LINK => lambda { + |url, headers| + nil + }, + Test::Response::MEDIA_LINK => lambda { |url, headers| + Test::Response::MEDIA_DELETE_TASK_DONE + }, + Test::Response::INDY_DISK_URL => lambda { |url, headers| + Test::Response::INDY_DISK_DELETE_TASK + }, + } + } + + #Working around Ruby 1.8"s lack of define_singleton_method + metaclass = class << response_mapping; + self; + end + + metaclass.send :define_method, :get_mapping do |http_method, url| + mapping = self[http_method][url] + if mapping.nil? + Config.logger.error("Response mapping not found for " + + "#{http_method} and #{url}") + # string substitution doesn"t work here for some reason + raise "Response mapping not found." + else + mapping + end + end + + rest_client.stub(:get) do |headers| + response_mapping.get_mapping(:get, build_url).call(build_url, headers) + end + rest_client.stub(:post) do |data, headers| + response_mapping.get_mapping(:post, build_url).call(build_url, data, + headers) + end + rest_client.stub(:put) do |data, headers| + response_mapping.get_mapping(:put, build_url).call(build_url, data, + headers) + end + rest_client.stub(:delete) do |headers| + response_mapping.get_mapping(:delete, build_url).call(build_url, + headers) + end + rest_client.stub(:vapp_state=) do |value| + current_vapp_state = value + end + rest_client.stub(:vapp_state) do + current_vapp_state + end + rest_client.stub(:response_mapping) do + response_mapping + end + rest_client.stub(:vapp_power_state=) do |value| + vapp_power_state = value + end + rest_client.stub(:[]) do |value| + @resource = value + @rest_connection + end + + rest_client + end + + def build_url + url + @resource + end + + def nested(url) + URI.parse(url).path + end + + class MockRestClient + class << self + attr_accessor :log + end + end + + def create_mock_client + @rest_connection = mock_rest_connection() + file_uploader = double("File Uploader") + file_uploader.stub(:upload) do + @rest_connection.vapp_state = @upload_file_state == "success" ? + "disks_uploaded" : "disks_upload_failed" + end + conn = Connection::Connection.new(url, entities["organization"], + control["time_limit_sec"]["http_request"], MockRestClient, + @rest_connection, file_uploader) + conn.stub(:file_uploader) do + file_uploader + end + conn.stub(:rest_connection) do + @rest_connection + end + conn + end + + def create_mock_ovf_directory(string_io) + directory = double("Directory") + # Actual content of the OVF is irrelevant as long as the client gives + # back the same one given to it + ovf_string = "ovf_string" + ovf_string_io = StringIO.new(ovf_string) + directory.stub(:ovf_file_path) { "ovf_file" } + directory.stub(:ovf_file) { + ovf_string_io + } + directory.stub(:vmdk_file_path) do |file_name| + file_name + end + directory.stub(:vmdk_file) do |file_name| + string_io + end + directory + end + + def create_mock_media_file() + media_string = Test::Response::MEDIA_CONTENT + string_io = StringIO.new(media_string) + string_io.stub(:path) { "bogus/bogus.iso" } + string_io.stub(:stat) { + o = Object.new + o.stub(:size) { media_string.length } + o + } + string_io + end + + + describe "VCD Adapter client", :positive, :min, :all do + it "logs into organization with usename and password and get the " + + "organization VDC" do + conn = double("Connection") + root_session = Xml::WrapperFactory.wrap_document( + Test::Response::SESSION) + vcloud_response = Xml::WrapperFactory.wrap_document( + Test::Response::VCLOUD_RESPONSE) + admin_org_response = Xml::WrapperFactory.wrap_document( + Test::Response::ADMIN_ORG_RESPONSE) + vdc_response = Xml::WrapperFactory.wrap_document( + Test::Response::VDC_RESPONSE) + conn.should_receive(:connect).with(username, password).and_return( + root_session) + conn.should_receive(:get).with(root_session.admin_root).and_return( + vcloud_response) + conn.should_receive(:get).with(vcloud_response.organization( + entities["organization"])).and_return(admin_org_response) + Client.new(nil, username, password, entities, control, conn) + end + end + + describe "VCD Adapter client", :upload, :all do + it "uploads an OVF to the OVDC", :positive do + vmdk_string = "vmdk" + vmdk_string_io = StringIO.new(vmdk_string) + directory = create_mock_ovf_directory(vmdk_string_io) + + conn = create_mock_client + + vapp_name = Test::Response::VAPP_TEMPLATE_NAME + + client = Client.new(nil, username, password, entities, control, conn) + + conn.file_uploader.should_receive(:upload).with( + Test::Response::VAPP_TEMPLATE_DISK_UPLOAD_1, vmdk_string_io, + auth_cookies) + catalog_item = client.upload_vapp_template(vapp_name, directory) + # Since the wrapper classes are used to hide away the raw XML, + # compare the wrapped versions + catalog_item.should eq(Xml::WrapperFactory.wrap_document( + Test::Response::CATALOG_ADD_ITEM_RESPONSE)) + end + + it "reports an exception upon error in transferring an OVF file", + :negative do + vmdk_string = "vmdk" + vmdk_string_io = StringIO.new(vmdk_string) + directory = create_mock_ovf_directory(vmdk_string_io) + + conn = create_mock_client + @upload_file_state = "failed" + vapp_name = Test::Response::VAPP_TEMPLATE_NAME + client = Client.new(nil, username, password, entities, control, conn) + + conn.file_uploader.should_receive(:upload).with( + Test::Response::VAPP_TEMPLATE_DISK_UPLOAD_1, vmdk_string_io, + auth_cookies) + expect { + client.upload_vapp_template(vapp_name, directory) + }.to raise_exception("Error uploading vApp template") + @upload_file_state = "success" + end + + it "deletes the vApp template if there's an error uploading it to " + + "the catalog", :negative do + vmdk_string = "vmdk" + vmdk_string_io = StringIO.new(vmdk_string) + directory = create_mock_ovf_directory(vmdk_string_io) + + conn = create_mock_client + conn.rest_connection.response_mapping[:post][ + Test::Response::CATALOG_ADD_ITEM_LINK] = + lambda { |url, data, headers| + raise ApiError, "Bogus add to catalog error" + } + vapp_name = Test::Response::VAPP_TEMPLATE_NAME + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::VAPP_TEMPLATE_LINK)) + conn.rest_connection.should_receive(:delete).with(anything()) + client = Client.new(nil, username, password, entities, control, conn) + expect { + client.upload_vapp_template(vapp_name, directory) + }.to raise_exception("Bogus add to catalog error") + end + + end + + describe "VCD Adapter client", :delete, :all do + it "deletes a vApp template from the catalog", :positive do + vmdk_string = "vmdk" + vmdk_string_io = StringIO.new(vmdk_string) + directory = create_mock_ovf_directory(vmdk_string_io) + + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + vapp_name = Test::Response::VAPP_TEMPLATE_NAME + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::VAPP_TEMPLATE_LINK)) + conn.rest_connection.should_receive(:delete).with(anything()) + catalog_item = client.upload_vapp_template(vapp_name, directory) + client.delete_catalog_vapp(catalog_item.urn) + end + + it "no exception for delete catalog vapp if the vApp does not exist", + :negative do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + client.delete_catalog_vapp("bogus") + end + + it "raise exception for upload media to catalog if the catalog does " + + "not exist", :negative do + conn = create_mock_client + ent_clone = entities.clone + ent_clone["media_catalog"] = "bogus" + media_file = create_mock_media_file() + client = Client.new(nil, username, password, ent_clone, control, conn) + media_name = Test::Response::MEDIA_NAME + + conn.file_uploader.stub(:upload) do |href, data, headers| + href.should eq(Test::Response::MEDIA_ISO_LINK) + data.read.should eq(Test::Response::MEDIA_CONTENT) + end + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::MEDIA_LINK)) + conn.rest_connection.should_receive(:delete).with(anything()) + expect { + client.upload_catalog_media(media_name, media_file) + }.to raise_exception(/.+ catalog .+ not found\./) + end + + it "should not raise if underlying vApp no longer exists when " + + "deleting a vApp from the catalog", :negative do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + conn.rest_connection.response_mapping[:delete][ + Test::Response::VAPP_TEMPLATE_LINK] = lambda { |urls, headers| + raise RestClient::ResourceNotFound + } + client.delete_catalog_vapp(Test::Response::VAPP_TEMPLATE_NAME) + end + end + + describe "VCD Adapter client", :instantiate_template, :all do + xit "instantiates a vApp from the catalog", :positive do + # marked pending because it failed on our local dev environment, + # and it's unclear if BOSH team is going to maintain ownership + # of this gem + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN + vapp_name = Test::Response::VAPP_NAME + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::VAPP_TEMPLATE_INSTANTIATE_LINK)) + conn.rest_connection.should_receive(:post).with( + Test::Response::EXISTING_VAPP_TEMPLATE_INSTANTIATE_REQUEST, + anything()) + client.instantiate_vapp_template(catalog_vapp_id, vapp_name) + end + + it "instantiates with locality a vApp from the catalog", :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN + vapp_name = Test::Response::VAPP_NAME + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::VAPP_TEMPLATE_INSTANTIATE_LINK)) + conn.rest_connection.should_receive(:post).with( + Test::Response:: + EXISTING_VAPP_TEMPLATE_INSTANTIATE_WITH_LOCALITY_REQUEST, + anything()) + client.instantiate_vapp_template(catalog_vapp_id, vapp_name, "desc", + [ Test::Response::INDY_DISK_URL ]) + end + + it "instantiates raises an exception if the task fails", :negative do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN + vapp_name = Test::Response::VAPP_NAME + conn.rest_connection.response_mapping[:get][ + Test::Response::EXISTING_VAPP_TEMPLATE_INSTANTIATE_TASK_LINK] = + lambda { |url, headers| + Test::Response:: + EXISTING_VAPP_TEMPLATE_INSTANTIATE_TASK_ERROR_RESPONSE + } + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_LINK)) + conn.rest_connection.should_receive(:delete).with(anything()) + expect { + client.instantiate_vapp_template(catalog_vapp_id, vapp_name) + }.to raise_exception(/.+Creating Virtual.+did not complete success.+/) + end + + end + + describe "VCD Adapter client", :modify_vm, :all do + it "reconfigures a VM", :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN + vapp_name = Test::Response::VAPP_NAME + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::RECONFIGURE_VM_LINK)) + conn.rest_connection.should_receive(:post).with( + Test::Response::RECONFIGURE_VM_REQUEST, anything()) + vapp = client.instantiate_vapp_template(catalog_vapp_id, vapp_name) + vm = vapp.vms.first + client.reconfigure_vm(vm) do |v| + vm.name = Test::Response::CHANGED_VM_NAME + vm.description = Test::Response::CHANGED_VM_DESCRIPTION + v.change_cpu_count(Test::Response::CHANGED_VM_CPU) + v.change_memory(Test::Response::CHANGED_VM_MEMORY) + v.add_hard_disk(Test::Response::CHANGED_VM_DISK) + v.delete_nic(*vm.hardware_section.nics) + end + end + + xit "gets information on HD", :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + vm = vapp.vms.first + vm.hardware_section.hard_disks.each do |h| + h.capacity_mb.should_not be_nil + h.disk_id.should_not be_nil + h.bus_sub_type.should_not be_nil + h.bus_type.should_not be_nil + end + + vm.hardware_section.nics.each do |n| + n.mac_address.should_not be_nil + end + + end + + end + + describe "VCD Adapter client", :power_vapp, :all do + it "finds and powers on a vApp and powers it off" , :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_POWER_ON_LINK)) + conn.rest_connection.should_receive(:post).with( + anything(), anything()) + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_POWER_OFF_LINK)) + conn.rest_connection.should_receive(:post).with( + anything(), anything()) + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + client.power_on_vapp(vapp) + client.power_off_vapp(vapp, false) + end + + it "finds and powers on a powered-on vApp" , :negative do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + client.power_on_vapp(vapp) + conn.rest_connection.should_not_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_POWER_ON_LINK)) + conn.rest_connection.should_not_receive(:post).with( + anything(), anything()) + client.power_on_vapp(vapp) + end + + it "finds and powers off a powered-off vApp" , :negative do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + client.power_off_vapp(vapp, false) + conn.rest_connection.should_not_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_POWER_OFF_LINK)) + conn.rest_connection.should_not_receive(:post).with( + anything(), anything()) + client.power_off_vapp(vapp, false) + end + + it "finds and powers on a vApp and undeploys it" , :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_POWER_ON_LINK)) + conn.rest_connection.should_receive(:post).with( + anything(), anything()) + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_UNDEPLOY_LINK)) + conn.rest_connection.should_receive(:post).with( + anything(), anything()) + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + client.power_on_vapp(vapp) + client.power_off_vapp(vapp) + end + + it "finds and undeploys an undeployed vapp" , :negative do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + client.power_off_vapp(vapp) + conn.rest_connection.should_not_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_UNDEPLOY_LINK)) + conn.rest_connection.should_not_receive(:post).with( + anything(), anything()) + client.power_off_vapp(vapp) + end + + it "finds and undeploys a powered-off vapp" , :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + client.power_on_vapp(vapp) + client.power_off_vapp(vapp, false) + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_UNDEPLOY_LINK)) + conn.rest_connection.should_receive(:post).with( + Test::Response::UNDEPLOY_PARAMS, anything()) + client.power_off_vapp(vapp) + end + + it "discards state of a suspended vapp" , :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + conn.rest_connection.response_mapping[:get][ + Test::Response::EXISTING_VAPP_LINK] = lambda { |url, headers| + Test::Response::INSTANTIATED_SUSPENDED_VAPP_RESPONSE } + conn.rest_connection.vapp_power_state = "suspended" + + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_DISCARD_STATE_LINK)) + conn.rest_connection.should_receive(:post).with( + anything(), anything()) + + client.discard_suspended_state_vapp(vapp) + end + + it "finds and powers on a vApp and reboots it" , :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_POWER_ON_LINK)) + conn.rest_connection.should_receive(:post).with( + anything(), anything()) + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_POWER_REBOOT_LINK)) + conn.rest_connection.should_receive(:post).with( + anything(), anything()) + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + client.power_on_vapp(vapp) + client.reboot_vapp(vapp) + end + + it "reboot of a powered-off vApp raises an exception" , :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + expect { + client.reboot_vapp(vapp) + }.to raise_exception(VappPoweredOffError) + end + + it "reboot of a suspended vApp raises an exception" , :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + conn.rest_connection.vapp_power_state = "suspended" + + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + expect { + client.reboot_vapp(vapp) + }.to raise_exception(VappSuspendedError) + end + end + + describe "VCD Adapter client", :delete_vapp, :all do + it "finds a vApp and deletes it without undeploying", :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + conn.rest_connection.should_not_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_UNDEPLOY_LINK)) + conn.rest_connection.should_not_receive(:post).with( + anything(), anything()) + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INSTANTIATED_VAPP_LINK)) + conn.rest_connection.should_receive(:delete).with(anything()) + client.delete_vapp(vapp) + end + + it "finds and powers on a vApp and fails on delete vApp", :negative do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + client.power_on_vapp(vapp) + conn.rest_connection.should_receive(:[]).once.with( + nested(Test::Response::INSTANTIATED_VAPP_LINK)) + conn.rest_connection.should_not_receive(:delete).with(anything()) + expect { + client.delete_vapp(vapp) + }.to raise_exception(/vApp .+ powered on, power-off before deleting./) + end + end + + describe "VCD Adapter client", :add_vapp_network, :all do + it "finds a network and vapp and adds the network to the vApp", + :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + network_name = Test::Response::ORG_NETWORK_NAME + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + network = client.get_ovdc.available_networks.find { + |n| n["name"] == network_name } + conn.rest_connection.should_receive(:[]).with(nested( + Test::Response::INSTANTIATED_VAPP_NETWORK_CONFIG_SECTION_LINK)) + conn.rest_connection.should_receive(:put).with(Test::Response:: + INSTANTIATED_VAPP_NETWORK_CONFIG_ADD_NETWORK_REQUEST, + anything()) + client.add_network(vapp, network) + end + + it "removes all networks", :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + + conn.rest_connection.response_mapping[:put][ + Test::Response::INSTANTIATED_VAPP_NETWORK_CONFIG_SECTION_LINK] = + lambda { |url, data, headers| + expected = Xml::WrapperFactory.wrap_document( + Test::Response:: + INSTANTIATED_VAPP_NETWORK_CONFIG_REMOVE_NETWORK_REQUEST) + received = Xml::WrapperFactory.wrap_document(data) + received.should eq(expected) + Test::Response:: + INSTANTIATED_VAPP_NETWORK_CONFIG_MODIFY_NETWORK_TASK_SUCCESS + } + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + client.delete_networks(vapp) + end + end + + describe "VCD Adapter client", :add_remove_insert_media, :all do + it "uploads media to VDC and adds it to the catalog", :positive do + conn = create_mock_client + media_file = create_mock_media_file() + client = Client.new(nil, username, password, entities, control, conn) + media_name = Test::Response::MEDIA_NAME + + conn.file_uploader.stub(:upload) do |href, data, headers| + href.should eq(Test::Response::MEDIA_ISO_LINK) + data.read.should eq(Test::Response::MEDIA_CONTENT) + end + + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::CATALOG_ADD_ITEM_LINK)) + conn.rest_connection.should_receive(:post).with( + Test::Response::MEDIA_ADD_TO_CATALOG_REQUEST, anything()) + + client.upload_catalog_media(media_name, media_file) + + end + + it "deletes a media from catalog and the VDC", :positive do + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + media_name = Test::Response::EXISTING_MEDIA_NAME + + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::EXISTING_MEDIA_LINK)) + conn.rest_connection.should_receive(:delete).with(anything()) + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::EXISTING_MEDIA_CATALOG_ITEM_LINK)) + conn.rest_connection.should_receive(:delete).with(anything()) + client.delete_catalog_media(media_name) + + end + + it "uploads media to VDC, fails to add to catalog, and rolls back", + :negative do + conn = create_mock_client + media_file = create_mock_media_file() + client = Client.new(nil, username, password, entities, control, conn) + media_name = Test::Response::MEDIA_NAME + + conn.rest_connection.response_mapping[:post][ + Test::Response::CATALOG_ADD_ITEM_LINK] = lambda { + |url, data, headers| + raise ApiError, "bogus error" + } + + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::MEDIA_LINK)) + conn.rest_connection.should_receive(:delete).with(anything()) + expect { + client.upload_catalog_media(media_name, media_file) + }.to raise_exception(/bogus error/) + + end + + it "inserts a media into a VM", :positive do + conn = create_mock_client + + client = Client.new(nil, username, password, entities, control, conn) + media_name = Test::Response::EXISTING_MEDIA_NAME + + catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN + vapp_name = Test::Response::VAPP_NAME + + vapp = client.instantiate_vapp_template(catalog_vapp_id, vapp_name) + vm = vapp.vms.first + client.insert_catalog_media(vm, media_name) + + end + + end + + describe "VCD Adapter client", :metadata, :all do + it "sets and gets metadata on a VM", :positive do + conn = create_mock_client + + client = Client.new(nil, username, password, entities, control, conn) + + metadata_key = Test::Response::METADATA_KEY + metadata_value = Test::Response::METADATA_VALUE + vapp = client.get_vapp(Test::Response::EXISTING_VAPP_URN) + vm = vapp.vms.first + client.set_metadata(vm, metadata_key, metadata_value) + value = client.get_metadata(vm, metadata_key) + value.should eq metadata_value + end + + end + + describe "VCD Adapter client", :indy_disk, :all do + it "creates an indepdent disk", :positive do + disk_name = Test::Response::INDY_DISK_NAME + disk_size = Test::Response::INDY_DISK_SIZE + + conn = create_mock_client + + client = Client.new(nil, username, password, entities, control, conn) + + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::VDC_INDY_DISKS_LINK)) + conn.rest_connection.should_receive(:post).with( + Test::Response::INDY_DISK_CREATE_REQUEST, anything()) + + client.create_disk(disk_name, disk_size) + + end + + it "creates an indepdent disk but hits an error", :negative do + disk_name = Test::Response::INDY_DISK_NAME + disk_size = Test::Response::INDY_DISK_SIZE + + conn = create_mock_client + + client = Client.new(nil, username, password, entities, control, conn) + + conn.rest_connection.response_mapping[:post][ + Test::Response::VDC_INDY_DISKS_LINK] = lambda { + |href, data, headers| + Test::Response::INDY_DISK_CREATE_ERROR + } + expect { + client.create_disk(disk_name, disk_size) + }.to raise_exception(ApiRequestError) + + end + + it "attaches an indepdent disk to a VM and then detaches", :positive do + disk_name = Test::Response::INDY_DISK_NAME + + conn = create_mock_client + + client = Client.new(nil, username, password, entities, control, conn) + + catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN + vapp_name = Test::Response::VAPP_NAME + + vapp = client.instantiate_vapp_template(catalog_vapp_id, vapp_name) + vm = vapp.vms.first + + vdc = client.get_ovdc() + disk = vdc.disks(disk_name).first + + conn.rest_connection.response_mapping[:post][ + Test::Response::INSTANTIATED_VM_ATTACH_DISK_LINK] = lambda { + |href, data, headers| + data_xml = Nokogiri::XML(data) + expected_xml = Nokogiri::XML( + Test::Response::INDY_DISK_ATTACH_REQUEST) + equality = VCloudSdk::Test::compare_xml(data_xml, expected_xml) + equality.should == true + Test::Response::INDY_DISK_ATTACH_TASK + } + + client.attach_disk(disk, vm) + + conn.rest_connection.response_mapping[:post][ + Test::Response::INSTANTIATED_VM_DETACH_DISK_LINK] = lambda { + |href, data, headers| + data_xml = Nokogiri::XML(data) + expected_xml = Nokogiri::XML( + Test::Response::INDY_DISK_DETACH_REQUEST) + equality = VCloudSdk::Test::compare_xml(data_xml, expected_xml) + equality.should == true + Test::Response::INDY_DISK_DETACH_TASK + } + + client.detach_disk(disk, vm) + + + end + + it "attaches an indepdent disk to a VM but has an error", :negative do + disk_name = Test::Response::INDY_DISK_NAME + + conn = create_mock_client + + client = Client.new(nil, username, password, entities, control, conn) + + catalog_vapp_id = Test::Response::EXISTING_VAPP_TEMPLATE_CATALOG_URN + vapp_name = Test::Response::VAPP_NAME + + vapp = client.instantiate_vapp_template(catalog_vapp_id, vapp_name) + vm = vapp.vms.first + + vdc = client.get_ovdc() + disk = vdc.disks(disk_name).first + + conn.rest_connection.response_mapping[:post][ + Test::Response::INSTANTIATED_VM_ATTACH_DISK_LINK] = lambda { + |href, data, headers| + Test::Response::INDY_DISK_ATTACH_TASK_ERROR + } + + expect { + client.attach_disk(disk, vm) + }.to raise_exception(/.+ Attaching Disk .+ complete successfully\./) + + end + + it "deletes an independent disk", :positive do + disk_name = Test::Response::INDY_DISK_NAME + + conn = create_mock_client + client = Client.new(nil, username, password, entities, control, conn) + vdc = client.get_ovdc() + disk = vdc.disks(disk_name).first + conn.rest_connection.should_receive(:[]).with( + nested(Test::Response::INDY_DISK_URL)) + conn.rest_connection.should_receive(:delete).with(anything()) + client.delete_disk(disk) + end + + end + + end +end From 7602f1de36f9e6ed4f1cc8af525caa399d0b4a99 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Mon, 14 Oct 2013 18:07:17 -0700 Subject: [PATCH 2/7] Move unused methods into the private block --- lib/ruby_vcloud_sdk/client.rb | 1692 ++++++++++++++++----------------- 1 file changed, 845 insertions(+), 847 deletions(-) diff --git a/lib/ruby_vcloud_sdk/client.rb b/lib/ruby_vcloud_sdk/client.rb index 8ac2952..2b51856 100644 --- a/lib/ruby_vcloud_sdk/client.rb +++ b/lib/ruby_vcloud_sdk/client.rb @@ -59,855 +59,853 @@ def initialize(url, username, password, option = {}, logger = nil) @logger.info('Successfully connected.') end - # def get_catalog_vapp(id) - # resolve_entity(id) - # end - # - # def get_vapp(obj) - # if obj.is_a?(Xml::VApp) - # obj - # elsif obj.is_a?(String) - # resolve_entity(obj) - # else - # raise CloudError, "Expecting Xml::VApp or String, got #{obj.inspect}." - # end - # end - # - # def upload_vapp_template(vapp_name, directory) - # ovdc = get_ovdc - # @logger.info("Uploading VM #{vapp_name} to #{ovdc["name"]} in " + - # "organization #{@organization}") - # # if directory behaves like an OVFDirectory, then use it - # is_ovf_directory = [:ovf_file, :ovf_file_path, :vmdk_file, - # :vmdk_file_path].reduce(true) do |present, name| - # present && directory.respond_to?(name) - # end - # ovf_directory = is_ovf_directory ? directory : - # OVFDirectory.new(directory) - # upload_params = Xml::WrapperFactory.create_instance( - # "UploadVAppTemplateParams") - # upload_params.name = vapp_name - # vapp_template = @connection.post(ovdc.upload_link, upload_params) - # catalog_name = @vapp_catalog_name - # vapp_template = upload_vapp_files(vapp_template, ovf_directory) - # raise ObjectNotFoundError, "Error uploading vApp template" unless - # vapp_template - # @logger.info("#{vapp_template.name} has tasks in progress. " + - # "Waiting until done.") - # vapp_template.running_tasks.each do |task| - # monitor_task(task, @time_limit["process_descriptor_vapp_template"]) - # end - # err_tasks = @connection.get(vapp_template).tasks.find_all { - # |t| t.status != Xml::TASK_STATUS[:SUCCESS] } - # unless err_tasks.empty? - # @logger.error("Error uploading vApp template. " + - # "Non-successful tasks:#{err_tasks}.") - # raise CloudError, "Error uploading vApp template" - # end - # @logger.info("vApp #{vapp_name} uploaded, adding to " + - # "catalog #{catalog_name}") - # catalog_item = add_catalog_item(vapp_template, catalog_name) - # @logger.info("vApp #{vapp_name} added to catalog " + - # "#{catalog_name} #{catalog_item.to_s}") - # catalog_item - # rescue ApiError => e - # log_exception(e, "Error in uploading vApp template #{vapp_name}") - # rollback_upload_vapp(vapp_template) - # raise e - # end - # - # def insert_catalog_media(vm, catalog_media_name) - # catalog_media = get_catalog_media(catalog_media_name) - # media = @connection.get(catalog_media.entity) - # current_vm = @connection.get(vm) - # insert_media(current_vm, media) - # end - # - # def eject_catalog_media(vm, catalog_media_name) - # catalog_media = get_catalog_media(catalog_media_name) - # unless catalog_media - # raise CatalogMediaNotFoundError, - # "Catalog media #{catalog_media_name} not found." - # end - # media = @connection.get(catalog_media.entity) - # current_vm = @connection.get(vm) - # eject_media(current_vm, media) - # end - # - # def upload_catalog_media(media_name, file, storage_profile = nil, - # image_type = "iso") - # ovdc = get_ovdc - # @logger.info("Uploading media #{media_name} to #{storage_profile}/" + - # "#{ovdc["name"]} in organization #{@organization}") - # catalog_name = @media_catalog_name - # upload_params = Xml::WrapperFactory.create_instance("Media") - # upload_params.name = media_name - # media_file = file.is_a?(String) ? File.new(file, "rb") : file - # upload_params.size = media_file.stat.size - # upload_params.image_type = image_type - # upload_params.storage_profile = storage_profile - # media = @connection.post(ovdc.upload_media_link, upload_params) - # incomplete_file = media.incomplete_files.pop - # @connection.put_file(incomplete_file.upload_link, media_file) - # media = @connection.get(media) - # add_catalog_item(media, catalog_name) - # rescue ArgumentError, ApiError => e - # log_exception(e, "Error uploading media #{media_name}" + - # "to catalog #{catalog_name}. #{e.message}") - # delete_media(media) if media - # raise e - # end - # - # def delete_catalog_media(name) - # raise ArgumentError, "Media name cannot be nil." unless name - # catalog_media = get_catalog_media(name) - # if catalog_media - # media = @connection.get(catalog_media.entity) - # delete_media(media) - # @connection.delete(catalog_media) - # end - # rescue RestClient::ResourceNotFound => e - # # Media might be deleted already - # @logger.debug("Catalog media #{name} no longer exists.") - # end - # - # def delete_catalog_vapp(id) - # raise ArgumentError, "Catalog ID cannot be nil." unless id - # catalog_vapp = get_catalog_vapp(id) - # if catalog_vapp - # vapp = @connection.get(catalog_vapp.entity) - # delete_vapp_template(vapp) - # @connection.delete(catalog_vapp) - # end - # rescue => e - # # vApp template might be deleted already - # @logger.debug("Catalog vApp #{id} no longer exists.") - # end - # - # def delete_vapp(vapp) - # @logger.info("Deleting vApp #{vapp.name}.") - # current_vapp = @connection.get(vapp) - # if is_vapp_status(current_vapp, :POWERED_ON) - # raise CloudError, - # "vApp #{vapp.name} is powered on, power-off before deleting." - # end - # delete_vapp_or_template(current_vapp, @retries["default"], - # @time_limit["delete_vapp"], "vApp") - # end - # - # def instantiate_vapp_template(source_template_id, vapp_name, - # description = nil, disk_locality = nil) - # catalog_item = get_catalog_vapp(source_template_id) - # unless catalog_item - # @logger.error("Catalog item with ID #{source_template_id} not " + - # "found in catalog #{@vapp_catalog_name}.") - # raise ObjectNotFoundError, "Item with ID #{source_template_id} " + - # "not found in catalog #{@vapp_catalog_name}." - # end - # src_vapp_template = @connection.get(catalog_item.entity) - # instantiate_vapp_params = Xml::WrapperFactory.create_instance( - # "InstantiateVAppTemplateParams") - # instantiate_vapp_params.name = vapp_name - # instantiate_vapp_params.description = description - # instantiate_vapp_params.source = src_vapp_template - # instantiate_vapp_params.all_eulas_accepted = true - # instantiate_vapp_params.linked_clone = false - # instantiate_vapp_params.set_locality = locality_spec(src_vapp_template, - # disk_locality) - # vdc = get_ovdc - # vapp = @connection.post(vdc.instantiate_vapp_template_link, - # instantiate_vapp_params) - # vapp.running_tasks.each do |task| - # begin - # monitor_task(task, @time_limit["instantiate_vapp_template"]) - # rescue ApiError => e - # log_exception(e, "Instantiate vApp template #{vapp_name} failed." + - # " Task #{task.operation} did not complete successfully.") - # delete_vapp(vapp) - # raise e - # end - # end - # @connection.get(vapp) - # end - # - # def reconfigure_vm(vm, &b) - # b.call(vm) - # monitor_task(@connection.post("#{vm.reconfigure_link.href}", vm, - # Xml::MEDIA_TYPE[:VM])) - # end - # - # def get_metadata(entity, key) - # metadata = @connection.get(generate_metadata_href(entity, key)) - # metadata.value - # end - # - # def set_metadata(entity, key, value) - # metadata = Xml::WrapperFactory.create_instance("MetadataValue") - # metadata.value = value - # task = @connection.put(generate_metadata_href(entity, key), metadata, - # Xml::MEDIA_TYPE[:METADATA_ITEM_VALUE]) - # monitor_task(task) - # end - # - # def delete_networks(vapp, exclude_nets = []) - # current_vapp = get_vapp(vapp) - # raise ObjectNotFoundError, "Cannot delete nets, vApp #{vapp.name} no " + - # "longer exists" unless current_vapp - # current = current_vapp.network_config_section.network_configs.map { - # |n| n.network_name } - # nets = current - exclude_nets - # @logger.debug("nets:: current:#{current}, exclude:#{exclude_nets}, " + - # "to delete:#{nets}") - # return if nets.nil? || nets.length == 0 - # delete_network(current_vapp, *nets) - # end - # - # def add_network(vapp, network, vapp_net_name = nil, - # fence_mode = Xml::FENCE_MODES[:BRIDGED]) - # current_network = @connection.get(network) - # raise ObjectNotFoundError, "Cannot add network to vApp #{vapp.name}. " + - # "The network #{network.name} no longer exists." unless current_network - # current_vapp = get_vapp(vapp) - # raise ObjectNotFoundError, "Cannot add network to vApp #{vapp.name}. " + - # "The vApp #{vapp.name} no longer exists." unless current_vapp - # network_config = Xml::WrapperFactory.create_instance("NetworkConfig") - # new_vapp_net_name = vapp_net_name.nil? ? - # current_network["name"] : vapp_net_name - # copy_network_settings(current_network, network_config, - # new_vapp_net_name, fence_mode) - # current_vapp.network_config_section.add_network_config(network_config) - # task = @connection.put(current_vapp.network_config_section, - # current_vapp.network_config_section, - # Xml::MEDIA_TYPE[:NETWORK_CONFIG_SECTION]) - # monitor_task(task) - # end - # - # # There must be no NICs on the network when it is deleted. Otherwise the - # # task will fail. Use set_nic_network to move NICs onto other network or - # # the NONE network prior to deleting the network from the vApp. - # def delete_network(vapp, *network_names) - # raise ArgumentError, "Must specify a network name to delete." if - # network_names.nil? || network_names.length == 0 - # unique_network_names = network_names.uniq - # @logger.info("Delete networks(s) #{unique_network_names.join(" ")} " + - # "from vApp #{vapp.name}") - # current_vapp = get_vapp(vapp) - # unique_network_names.each do |n| - # current_vapp.network_config_section.delete_network_config(n) - # end - # task = @connection.put(current_vapp.network_config_section, - # current_vapp.network_config_section, - # Xml::MEDIA_TYPE[:NETWORK_CONFIG_SECTION]) - # monitor_task(task) - # end - # - # # Size at creation is in bytes - # # We currently assumes the disk is SCSI and bus sub type LSILOGIC - # def create_disk(name, size_mb, vm = nil, retries = @retries["default"]) - # new_disk = Xml::WrapperFactory.create_instance("DiskCreateParams") - # new_disk.name = name - # new_disk.size_bytes = size_mb * 1024 * 1024 # VCD expects bytes - # new_disk.bus_type = Xml::HARDWARE_TYPE[:SCSI_CONTROLLER] - # new_disk.bus_sub_type = Xml::BUS_SUB_TYPE[:LSILOGIC] - # new_disk.add_locality(vm) if vm - # vdc = get_ovdc - # @logger.info("Creating independent disk #{name} of #{size_mb}MB.") - # @logger.info("Disk locality ist set to #{vm.name} #{vm.urn}.") if vm - # disk = @connection.post(vdc.add_disk_link, new_disk, - # Xml::MEDIA_TYPE[:DISK_CREATE_PARAMS]) - # raise ApiRequestError unless disk.respond_to?(:running_tasks) - # # Creating a disk returns a disk with tasks inside - # retries.times do |try| - # return disk if disk.running_tasks.nil? || disk.running_tasks.empty? - # @logger.info("Disk #{disk.urn} has running tasks. Waiting for " + - # "tasks to finish. Try: #{try}/#{retries} ." ) - # disk.running_tasks.each do |t| - # monitor_task(t) - # end - # disk = @connection.get(disk) - # end - # end - # - # def delete_disk(disk) - # current_disk = @connection.get(disk) - # unless current_disk - # @logger.warn("Disk #{disk.name} #{disk.urn} no longer exists.") - # return - # end - # task = @connection.delete(current_disk.delete_link) - # monitor_task(task) do |t| - # @logger.info("Deleted disk #{current_disk.name} #{current_disk.urn}") - # t - # end - # end - # - # def attach_disk(disk, vm) - # current_vm = @connection.get(vm) - # raise ObjectNotFoundError, "VM #{vm.name} not found." unless current_vm - # - # current_disk = @connection.get(disk) - # unless current_disk - # raise ObjectNotFoundError, "Disk #{disk.name} not found." - # end - # - # params = Xml::WrapperFactory.create_instance("DiskAttachOrDetachParams") - # params.disk_href = current_disk.href - # task = @connection.post(current_vm.attach_disk_link, params, - # Xml::MEDIA_TYPE[:DISK_ATTACH_DETACH_PARAMS]) - # monitor_task(task) do |t| - # @logger.info("Attached disk #{current_disk.name} to VM " + - # "#{current_vm.name}.") - # t - # end - # end - # - # def detach_disk(disk, vm) - # current_vm = @connection.get(vm) - # raise ObjectNotFoundError, "VM #{vm.name} not found." unless current_vm - # - # current_disk = @connection.get(disk) - # unless current_disk - # raise ObjectNotFoundError, "Disk #{disk.name} not found." - # end - # - # disk_href = current_disk.href - # - # if is_vapp_status(current_vm, :SUSPENDED) - # @logger.debug("vApp #{current_vm.name} suspended, discard state " + - # "before detaching disk.") - # raise VmSuspendedError, "discard state first" - # end - # - # begin - # get_disk_id(current_vm, disk_href) - # rescue DiskNotFoundError - # @logger.warn("Disk #{current_disk.name} not found on VM " + - # "#{current_vm.name}. No need to detach.") - # return - # end - # params = Xml::WrapperFactory.create_instance("DiskAttachOrDetachParams") - # params.disk_href = disk_href - # task = @connection.post(current_vm.detach_disk_link, params, - # Xml::MEDIA_TYPE[:DISK_ATTACH_DETACH_PARAMS]) - # monitor_task(task) do |t| - # @logger.info("Detached disk #{current_disk.name} from VM " + - # "#{current_vm.name}.") - # t - # end - # end - # - # def get_disk(disk_id) - # resolve_entity(disk_id) - # end - # - # def power_on_vapp(vapp) - # @logger.info("Powering on vApp #{vapp.name} .") - # current_vapp = @connection.get(vapp) - # unless current_vapp - # raise ObjectNotFoundError, "vApp #{vapp.name} not found." - # end - # @logger.debug("vApp status: #{current_vapp["status"]}") - # if is_vapp_status(current_vapp, :POWERED_ON) - # @logger.info("vApp #{vapp.name} already powered-on.") - # return - # end - # unless current_vapp.power_on_link - # raise CloudError, "vApp #{vapp.name} not in a state to be " + - # "powered on." - # end - # task = @connection.post(current_vapp.power_on_link, nil) - # monitor_task(task, @time_limit["power_on"]) - # @logger.info("vApp #{current_vapp.name} powered on.") - # task - # end - # - # def power_off_vapp(vapp, undeploy = true) - # @logger.info("Powering off vApp #{vapp.name} .") - # @logger.info("Undeploying vApp #{vapp.name} .") if undeploy - # current_vapp = @connection.get(vapp) - # unless current_vapp - # raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." - # end - # @logger.debug("vApp status: #{current_vapp["status"]}") - # - # if is_vapp_status(current_vapp, :SUSPENDED) - # @logger.debug("vApp #{current_vapp.name} suspended, discard state " + - # "before powering off.") - # raise VappSuspendedError, "discard state first" - # end - # - # if undeploy - # # Since we do not apparently differentiate between powered-off and - # # undeployed in our status, we should check if the undeploy link is - # # available first. If undeploy is not available and status is - # # powered_off then it is undeployed. - # unless current_vapp.undeploy_link - # if is_vapp_status(current_vapp, :POWERED_OFF) - # @logger.info("vApp #{vapp.name} already powered-off, undeployed.") - # return - # end - # raise CloudError, "vApp #{vapp.name} not in a state be " + - # "powered-off, undeployed." - # end - # params = Xml::WrapperFactory.create_instance("UndeployVAppParams") - # task = @connection.post(current_vapp.undeploy_link, params) - # monitor_task(task, @time_limit["undeploy"]) - # @logger.info("vApp #{current_vapp.name} powered-off, undeployed.") - # task - # else - # unless current_vapp.power_off_link - # if is_vapp_status(current_vapp, :POWERED_OFF) - # @logger.info("vApp #{vapp.name} already powered off.") - # return - # end - # raise CloudError, "vApp #{vapp.name} not in a state be powered off." - # end - # task = @connection.post(current_vapp.power_off_link, nil) - # monitor_task(task, @time_limit["power_off"]) - # @logger.info("vApp #{current_vapp.name} powered off.") - # task - # end - # end - # - # def discard_suspended_state_vapp(vapp) - # @logger.info("Discarding suspended state of vApp #{vapp.name}.") - # current_vapp = @connection.get(vapp) - # unless current_vapp - # raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." - # end - # @logger.debug("vApp status: #{current_vapp["status"]}") - # - # return unless is_vapp_status(current_vapp, :SUSPENDED) - # - # @logger.info("Discarding suspended state of vApp #{current_vapp.name}.") - # task = @connection.post(current_vapp.discard_state, nil) - # monitor_task(task, @time_limit["undeploy"]) - # current_vapp = @connection.get(current_vapp) - # @logger.info("vApp #{current_vapp.name} suspended state discarded.") - # task - # end - # - # def reboot_vapp(vapp) - # @logger.info("Rebooting vApp #{vapp.name}.") - # current_vapp = @connection.get(vapp) - # unless current_vapp - # raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." - # end - # @logger.debug("vApp status: #{current_vapp["status"]}") - # - # if is_vapp_status(current_vapp, :SUSPENDED) - # @logger.debug("vApp #{current_vapp.name} suspended.") - # raise VappSuspendedError, "vapp suspended" - # end - # if is_vapp_status(current_vapp, :POWERED_OFF) - # @logger.debug("vApp #{current_vapp.name} powered off.") - # raise VappPoweredOffError, "vapp powered off" - # end - # - # @logger.info("Rebooting vApp #{current_vapp.name}.") - # task = @connection.post(current_vapp.reboot_link, nil) - # monitor_task(task) - # current_vapp = @connection.get(current_vapp) - # @logger.info("vApp #{current_vapp.name} rebooted.") - # task - # end - # - # def get_ovdc - # vdc = @admin_org.vdc(@ovdc_name) - # raise ObjectNotFoundError, "VDC #{@ovdc_name} not found." unless vdc - # @connection.get(vdc) - # end - # - # def get_catalog(name) - # catalog = @connection.get(@admin_org.catalog(name)) - # end - private - # ERROR_STATUSES = [Xml::TASK_STATUS[:ABORTED], Xml::TASK_STATUS[:ERROR], - # Xml::TASK_STATUS[:CANCELED]] - # SUCCESS_STATUS = [Xml::TASK_STATUS[:SUCCESS]] - # - # def resolve_entity(id) - # url = "#{@entity_resolver_link}#{id}" - # entity = @connection.get(url) - # raise ObjectNotFoundError, "Unable to get entity" unless entity - # @connection.get(entity.link) - # end - # - # def get_disk_id(vm, disk_href) - # hardware_section = vm.hardware_section - # disk = hardware_section.hard_disks.find do |d| - # d.host_resource["disk"] == disk_href - # end - # unless disk - # raise DiskNotFoundError, "Disk with href #{disk_href} not attached " + - # "to VM #{vm.name}." - # end - # disk.disk_id - # end - # - # def log_exception(e, message = nil) - # @logger.error(message) if message - # @logger.error(e.message) - # @logger.error(e.backtrace.join("\n\r")) - # end - # - # def copy_network_settings(network, network_config, vapp_net_name, - # fence_mode) - # config_ip_scope = network_config.ip_scope - # net_ip_scope = network.ip_scope - # config_ip_scope.is_inherited = net_ip_scope.is_inherited? - # config_ip_scope.gateway= net_ip_scope.gateway - # config_ip_scope.netmask = net_ip_scope.netmask - # if net_ip_scope.start_address - # config_ip_scope.start_address = net_ip_scope.start_address - # end - # if net_ip_scope.end_address - # config_ip_scope.end_address = net_ip_scope.end_address - # end - # network_config.fence_mode = fence_mode - # network_config.parent_network["name"] = network["name"] - # network_config.parent_network["href"] = network["href"] - # network_config["networkName"] = vapp_net_name - # end - # - # def delete_vapp_template(vapp_template) - # delete_vapp_or_template(vapp_template, @retries["default"], - # @time_limit["delete_vapp_template"], "vApp Template") - # end - # - # def check_vapp_for_remove_link(vapp) - # current_vapp = @connection.get(vapp) - # unless current_vapp.remove_link - # raise ObjectNotFoundError, "No link available to delete vApp." - # end - # return current_vapp - # end - # - # def delete_vapp_or_template(vapp, retries, time_limit, type_name) - # retries.times do |try| - # @logger.info("Deleting #{type_name} #{vapp.name}") - # current_vapp = @connection.get(vapp) - # if (current_vapp.running_tasks.empty?) - # Util.retry_operation(current_vapp, @retries["default"], - # @control["backoff"]) do - # current_vapp = check_vapp_for_remove_link(current_vapp) - # end - # Util.retry_operation(current_vapp.remove_link, @retries["default"], - # @control["backoff"]) do - # monitor_task(@connection.delete(current_vapp.remove_link), - # time_limit) do |task| - # @logger.info("#{type_name} #{current_vapp.name} deleted.") - # return task - # end - # end - # else - # @logger.info("#{vapp.name} has tasks in progress, wait until done.") - # current_vapp.running_tasks.each do |task| - # monitor_task(task) - # end - # sleep (@control["backoff"] ** try) - # end - # end - # raise ApiRequestError, - # "Unable to delete #{type_name} after #{retries} attempts" - # end - # - # def insert_media(vm, media, retries = @retries["default"]) - # params = Xml::WrapperFactory.create_instance("MediaInsertOrEjectParams") - # params.media_href = media.href - # - # # Wait for media to be ready - # retries.times do |try| - # @logger.info("Inserting media #{media.name} into VM #{vm.name}.") - # current_media = @connection.get(media) - # if (current_media.running_tasks.empty?) - # Util.retry_operation(vm.insert_media_link, @retries["default"], - # @control["backoff"]) do - # task = @connection.post(vm.insert_media_link, params, - # Xml::MEDIA_TYPE[:MEDIA_INSERT_EJECT_PARAMS]) - # monitor_task(task) do |t| - # raise CloudError, "Error inserting media #{media.name} " + - # "into VM #{vm.name}." if t.status != "success" - # @logger.info("Inserted media #{media.name} into VM #{vm.name}.") - # return t - # end - # end - # else - # @logger.info("#{current_media.name} has tasks in progress, " + - # "waiting until done.") - # current_media.running_tasks.each do |task| - # monitor_task(task) - # end - # sleep (@control["backoff"] ** try) - # end - # end - # raise ApiRequestError, "Unable to insert media #{media.name} into " + - # "VM #{vm.name} after #{retries} attempts" - # end - # - # def eject_media(vm, media, retries = @retries["default"]) - # params = Xml::WrapperFactory.create_instance("MediaInsertOrEjectParams") - # params.media_href = media.href - # - # #Wait for media to be ready - # retries.times do |try| - # @logger.info("Ejecting media #{media.name} from VM #{vm.name}.") - # current_media = @connection.get(media) - # if (current_media.running_tasks.empty?) - # return eject_media_task(vm, params, media) - # else - # @logger.info("#{current_media.name} has tasks in progress, " + - # "waiting until done.") - # current_media.running_tasks.each do |task| - # monitor_task(task) - # end - # sleep (@control["backoff"] ** try) - # end - # end - # raise ApiRequestError, "Unable to eject media #{media.name} from " + - # "VM #{vm.name} after #{retries} attempts" - # end - # - # def delete_media(media, retries = @retries["default"], - # time_limit = @time_limit["delete_media"]) - # retries.times do |try| - # @logger.info("Deleting media #{media.name}") - # current_media = @connection.get(media) - # if (current_media.running_tasks.empty?) - # Util.retry_operation(current_media.delete_link, @retries["default"], - # @control["backoff"]) do - # monitor_task(@connection.delete(current_media.delete_link), - # time_limit) do |task| - # @logger.info("Media #{current_media.name} deleted.") - # return task - # end - # end - # else - # @logger.info("#{current_media.name} has tasks in progress, " + - # "waiting until done.") - # current_media.running_tasks.each do |task| - # monitor_task(task) - # end - # sleep (@control["backoff"] ** try) - # end - # end - # raise ApiRequestError, "Unable to delete #{type_name} after " - # "#{retries} attempts" - # end - # - # def get_catalog_media(name) - # get_catalog_item(name, Xml::MEDIA_TYPE[:MEDIA], @media_catalog_name) - # end - # - # # Get catalog item from catalog by name and type. - # # Raises an exception if catalog is not found. - # # Returns nil if an item matching the name and type is not found. - # # Otherwise, returns the catalog item. - # # The catalog item is not the uderlying object itself, i.e. vApp template. - # def get_catalog_item(name, item_type, catalog_name) - # raise ObjectNotFoundError, "Catalog item name cannot be nil" unless name - # unless @admin_org.catalog(catalog_name) - # raise ObjectNotFoundError, "Catalog #{catalog_name} not found." - # end - # # For some reason, if the catalog no longer exists, - # # VCD throws a Forbidden exception when getting - # catalog = @connection.get(@admin_org.catalog(catalog_name)) - # items = catalog.catalog_items(name) - # if items.nil? || items.empty? - # @logger.debug("Item #{name} does not exist in catalog #{catalog_name}") - # return nil - # end - # items.each do |i| - # entity = @connection.get(i) - # # Return the entity node. Another get on that node is necessary to - # # get the actual object itself - # return entity if entity.entity["type"] == item_type - # end - # nil - # end - # - # def get_vm_network_connections(vm) - # current_vm = @connection.get(vm) - # unless current_vm - # raise ObjectNotFoundError, "VM #{vm.name} no longer exists." - # end - # @connection.get(current_vm.network_connection_section) - # end - # - # def task_progressed?(current_task, prev_progress, prev_status) - # (current_task.progress && (current_task.progress != prev_progress)) || - # (current_task.status && (current_task.status != prev_status)) - # end - # - # def task_is_success(current_task, success = SUCCESS_STATUS) - # success.map { |s| s.downcase }.find { - # |s| s == current_task.status.downcase } - # end - # - # def task_has_error(current_task, error_statuses = ERROR_STATUSES) - # error_statuses.map { |s| s.downcase }.find { - # |s| s == current_task.status.downcase } - # end - # - # def monitor_task(task, time_limit = @time_limit["default"], - # error_statuses = ERROR_STATUSES, success = SUCCESS_STATUS, - # delay = @control["delay"], &b) - # iterations = time_limit / delay - # i = 0 - # prev_progress = task.progress - # prev_status = task.status - # current_task = task - # while (i < iterations) - # @logger.debug("#{current_task.urn} #{current_task.operation} is " + - # "#{current_task.status}") - # if task_is_success(current_task, success) - # if b - # return b.call(current_task) - # else - # return current_task - # end - # elsif task_has_error(current_task, error_statuses) - # raise ApiRequestError, "Task #{task.urn} #{task.operation} did " + - # "not complete successfully." - # elsif task_progressed?(current_task, prev_progress, prev_status) - # @logger.debug("task status #{prev_status} => " + - # "#{current_task.status}, progress #{prev_progress}%" + - # " => #{current_task.progress}%, timer #{i} reset.") - # prev_progress = current_task.progress - # prev_status = current_task.status - # i = 0 #reset clock if status changes or running task makes progress - # sleep(delay) - # else - # @logger.debug("Approximately #{i * delay}s elapsed waiting for " + - # "#{current_task.operation} to reach " + - # "#{success.join("/")}/#{error_statuses.join("/")}." + - # " Checking again in #{delay} seconds.") - # @logger.debug("Task #{task.urn} progress: " + - # "#{current_task.progress} %.") if current_task.progress - # sleep(delay) - # end - # current_task = @connection.get(task) - # i += 1 - # end - # raise ApiTimeoutError, "Task #{task.operation} did not complete " + - # "within limit of #{time_limit} seconds." - # end - # - # - # # TODO use times.upload_vapp_files - # def upload_vapp_files(vapp, ovf_directory, - # tries = @retries["upload_vapp_files"], try = 0) - # current_vapp = @connection.get(vapp) - # return current_vapp if !current_vapp.files || current_vapp.files.empty? - # - # @logger.debug("vapp files left to upload #{current_vapp.files}.") - # @logger.debug("vapp incomplete files left to upload " + - # "#{current_vapp.incomplete_files}.") - # raise ApiTimeoutError, "Unable to finish uploading vApp after " + - # "#{tries} tries #{current_vapp.files}." if tries == try - # - # current_vapp.incomplete_files.each do |f| - # # switch on extension - # case f.name.split(".").pop.downcase - # when "ovf" - # @logger.info("Uploading OVF file: " + - # "#{ovf_directory.ovf_file_path} for #{vapp.name}") - # @connection.put(f.upload_link, ovf_directory.ovf_file.read, - # Xml::MEDIA_TYPE[:OVF]) - # when "vmdk" - # @logger.info("Uploading VMDK file " + - # "#{ovf_directory.vmdk_file_path(f.name)} for #{vapp.name}") - # @connection.put_file(f.upload_link, - # ovf_directory.vmdk_file(f.name)) - # end - # end - # #repeat - # sleep (2 ** try) - # upload_vapp_files(current_vapp, ovf_directory, tries, try + 1) - # end - # - # def add_catalog_item(item, catalog_name) - # unless @admin_org.catalog(catalog_name) - # raise ArgumentError, - # "Error adding #{item.name}, catalog #{catalog_name} not found." - # end - # catalog = @connection.get(@admin_org.catalog(catalog_name)) - # raise ObjectNotFoundError, "Error adding #{item.name}, catalog " + - # "#{catalog_name} not available." unless catalog - # catalog_item = Xml::WrapperFactory.create_instance("CatalogItem") - # catalog_item.name = item.name - # catalog_item.entity = item - # @logger.info("Adding #{catalog_item.name} to catalog #{catalog_name}") - # @connection.post(catalog.add_item_link, catalog_item, - # Xml::ADMIN_MEDIA_TYPE[:CATALOG_ITEM]) - # end - # - # def generate_metadata_href(entity, key) - # raise ObjectNotFoundError, "Entity #{entity.name} does not expose a " + - # "metadata link method." if !entity.respond_to?(:metadata_link) - # "#{entity.metadata_link.href}/#{key}" - # end - # - # def get_vapp_by_name(name) - # @logger.debug("Getting vApp #{name}") - # vdc = get_ovdc - # node = vdc.get_vapp(name) - # raise ObjectNotFoundError, "vApp #{name} does not exist." unless node - # vapp = @connection.get(node) - # raise ObjectNotFoundError, "vApp #{name} does not exist." unless vapp - # vapp - # end - # - # def locality_spec(src_vapp_template, disk_locality) - # disk_locality ||= [] - # locality = {} - # disk_locality.each do |disk| - # current_disk = @connection.get(disk) - # unless current_disk - # @logger.warn("Disk #{disk.name} no longer exists.") - # next - # end - # src_vapp_template.vms.each do |vm| - # locality[vm] = current_disk - # end - # end - # locality - # end - # - # def is_vapp_status(current_vapp, status) - # current_vapp["status"] == Xml::RESOURCE_ENTITY_STATUS[status].to_s - # end - # - # def rollback_upload_vapp(vapp_template) - # @logger.error("Rolling back changes.") - # begin - # delete_vapp_template(vapp_template) if vapp_template - # rescue => rollbackex - # log_exception(rollbackex, "Error in rolling back failed vApp " + - # "template #{vapp_name}.") - # end - # end - # - # def eject_media_task(vm, params, media) - # Util.retry_operation(vm.eject_media_link, @retries["default"], - # @control["backoff"]) do - # task = @connection.post(vm.eject_media_link, params, - # Xml::MEDIA_TYPE[:MEDIA_INSERT_EJECT_PARAMS]) - # monitor_task(task) do |t| - # if t.status != "success" - # raise CloudError, "Error ejecting media #{media.name} from " + - # "VM #{vm.name}." - # end - # @logger.info("Ejected media #{media.name} from VM #{vm.name}.") - # return t - # end - # end - # end - # - - # TODO: move to connection.rb + def get_catalog_vapp(id) + resolve_entity(id) + end + + def get_vapp(obj) + if obj.is_a?(Xml::VApp) + obj + elsif obj.is_a?(String) + resolve_entity(obj) + else + raise CloudError, "Expecting Xml::VApp or String, got #{obj.inspect}." + end + end + + def upload_vapp_template(vapp_name, directory) + ovdc = get_ovdc + @logger.info("Uploading VM #{vapp_name} to #{ovdc["name"]} in " + + "organization #{@organization}") + # if directory behaves like an OVFDirectory, then use it + is_ovf_directory = [:ovf_file, :ovf_file_path, :vmdk_file, + :vmdk_file_path].reduce(true) do |present, name| + present && directory.respond_to?(name) + end + ovf_directory = is_ovf_directory ? directory : + OVFDirectory.new(directory) + upload_params = Xml::WrapperFactory.create_instance( + "UploadVAppTemplateParams") + upload_params.name = vapp_name + vapp_template = @connection.post(ovdc.upload_link, upload_params) + catalog_name = @vapp_catalog_name + vapp_template = upload_vapp_files(vapp_template, ovf_directory) + raise ObjectNotFoundError, "Error uploading vApp template" unless + vapp_template + @logger.info("#{vapp_template.name} has tasks in progress. " + + "Waiting until done.") + vapp_template.running_tasks.each do |task| + monitor_task(task, @time_limit["process_descriptor_vapp_template"]) + end + err_tasks = @connection.get(vapp_template).tasks.find_all { + |t| t.status != Xml::TASK_STATUS[:SUCCESS] } + unless err_tasks.empty? + @logger.error("Error uploading vApp template. " + + "Non-successful tasks:#{err_tasks}.") + raise CloudError, "Error uploading vApp template" + end + @logger.info("vApp #{vapp_name} uploaded, adding to " + + "catalog #{catalog_name}") + catalog_item = add_catalog_item(vapp_template, catalog_name) + @logger.info("vApp #{vapp_name} added to catalog " + + "#{catalog_name} #{catalog_item.to_s}") + catalog_item + rescue ApiError => e + log_exception(e, "Error in uploading vApp template #{vapp_name}") + rollback_upload_vapp(vapp_template) + raise e + end + + def insert_catalog_media(vm, catalog_media_name) + catalog_media = get_catalog_media(catalog_media_name) + media = @connection.get(catalog_media.entity) + current_vm = @connection.get(vm) + insert_media(current_vm, media) + end + + def eject_catalog_media(vm, catalog_media_name) + catalog_media = get_catalog_media(catalog_media_name) + unless catalog_media + raise CatalogMediaNotFoundError, + "Catalog media #{catalog_media_name} not found." + end + media = @connection.get(catalog_media.entity) + current_vm = @connection.get(vm) + eject_media(current_vm, media) + end + + def upload_catalog_media(media_name, file, storage_profile = nil, + image_type = "iso") + ovdc = get_ovdc + @logger.info("Uploading media #{media_name} to #{storage_profile}/" + + "#{ovdc["name"]} in organization #{@organization}") + catalog_name = @media_catalog_name + upload_params = Xml::WrapperFactory.create_instance("Media") + upload_params.name = media_name + media_file = file.is_a?(String) ? File.new(file, "rb") : file + upload_params.size = media_file.stat.size + upload_params.image_type = image_type + upload_params.storage_profile = storage_profile + media = @connection.post(ovdc.upload_media_link, upload_params) + incomplete_file = media.incomplete_files.pop + @connection.put_file(incomplete_file.upload_link, media_file) + media = @connection.get(media) + add_catalog_item(media, catalog_name) + rescue ArgumentError, ApiError => e + log_exception(e, "Error uploading media #{media_name}" + + "to catalog #{catalog_name}. #{e.message}") + delete_media(media) if media + raise e + end + + def delete_catalog_media(name) + raise ArgumentError, "Media name cannot be nil." unless name + catalog_media = get_catalog_media(name) + if catalog_media + media = @connection.get(catalog_media.entity) + delete_media(media) + @connection.delete(catalog_media) + end + rescue RestClient::ResourceNotFound => e + # Media might be deleted already + @logger.debug("Catalog media #{name} no longer exists.") + end + + def delete_catalog_vapp(id) + raise ArgumentError, "Catalog ID cannot be nil." unless id + catalog_vapp = get_catalog_vapp(id) + if catalog_vapp + vapp = @connection.get(catalog_vapp.entity) + delete_vapp_template(vapp) + @connection.delete(catalog_vapp) + end + rescue => e + # vApp template might be deleted already + @logger.debug("Catalog vApp #{id} no longer exists.") + end + + def delete_vapp(vapp) + @logger.info("Deleting vApp #{vapp.name}.") + current_vapp = @connection.get(vapp) + if is_vapp_status(current_vapp, :POWERED_ON) + raise CloudError, + "vApp #{vapp.name} is powered on, power-off before deleting." + end + delete_vapp_or_template(current_vapp, @retries["default"], + @time_limit["delete_vapp"], "vApp") + end + + def instantiate_vapp_template(source_template_id, vapp_name, + description = nil, disk_locality = nil) + catalog_item = get_catalog_vapp(source_template_id) + unless catalog_item + @logger.error("Catalog item with ID #{source_template_id} not " + + "found in catalog #{@vapp_catalog_name}.") + raise ObjectNotFoundError, "Item with ID #{source_template_id} " + + "not found in catalog #{@vapp_catalog_name}." + end + src_vapp_template = @connection.get(catalog_item.entity) + instantiate_vapp_params = Xml::WrapperFactory.create_instance( + "InstantiateVAppTemplateParams") + instantiate_vapp_params.name = vapp_name + instantiate_vapp_params.description = description + instantiate_vapp_params.source = src_vapp_template + instantiate_vapp_params.all_eulas_accepted = true + instantiate_vapp_params.linked_clone = false + instantiate_vapp_params.set_locality = locality_spec(src_vapp_template, + disk_locality) + vdc = get_ovdc + vapp = @connection.post(vdc.instantiate_vapp_template_link, + instantiate_vapp_params) + vapp.running_tasks.each do |task| + begin + monitor_task(task, @time_limit["instantiate_vapp_template"]) + rescue ApiError => e + log_exception(e, "Instantiate vApp template #{vapp_name} failed." + + " Task #{task.operation} did not complete successfully.") + delete_vapp(vapp) + raise e + end + end + @connection.get(vapp) + end + + def reconfigure_vm(vm, &b) + b.call(vm) + monitor_task(@connection.post("#{vm.reconfigure_link.href}", vm, + Xml::MEDIA_TYPE[:VM])) + end + + def get_metadata(entity, key) + metadata = @connection.get(generate_metadata_href(entity, key)) + metadata.value + end + + def set_metadata(entity, key, value) + metadata = Xml::WrapperFactory.create_instance("MetadataValue") + metadata.value = value + task = @connection.put(generate_metadata_href(entity, key), metadata, + Xml::MEDIA_TYPE[:METADATA_ITEM_VALUE]) + monitor_task(task) + end + + def delete_networks(vapp, exclude_nets = []) + current_vapp = get_vapp(vapp) + raise ObjectNotFoundError, "Cannot delete nets, vApp #{vapp.name} no " + + "longer exists" unless current_vapp + current = current_vapp.network_config_section.network_configs.map { + |n| n.network_name } + nets = current - exclude_nets + @logger.debug("nets:: current:#{current}, exclude:#{exclude_nets}, " + + "to delete:#{nets}") + return if nets.nil? || nets.length == 0 + delete_network(current_vapp, *nets) + end + + def add_network(vapp, network, vapp_net_name = nil, + fence_mode = Xml::FENCE_MODES[:BRIDGED]) + current_network = @connection.get(network) + raise ObjectNotFoundError, "Cannot add network to vApp #{vapp.name}. " + + "The network #{network.name} no longer exists." unless current_network + current_vapp = get_vapp(vapp) + raise ObjectNotFoundError, "Cannot add network to vApp #{vapp.name}. " + + "The vApp #{vapp.name} no longer exists." unless current_vapp + network_config = Xml::WrapperFactory.create_instance("NetworkConfig") + new_vapp_net_name = vapp_net_name.nil? ? + current_network["name"] : vapp_net_name + copy_network_settings(current_network, network_config, + new_vapp_net_name, fence_mode) + current_vapp.network_config_section.add_network_config(network_config) + task = @connection.put(current_vapp.network_config_section, + current_vapp.network_config_section, + Xml::MEDIA_TYPE[:NETWORK_CONFIG_SECTION]) + monitor_task(task) + end + + # There must be no NICs on the network when it is deleted. Otherwise the + # task will fail. Use set_nic_network to move NICs onto other network or + # the NONE network prior to deleting the network from the vApp. + def delete_network(vapp, *network_names) + raise ArgumentError, "Must specify a network name to delete." if + network_names.nil? || network_names.length == 0 + unique_network_names = network_names.uniq + @logger.info("Delete networks(s) #{unique_network_names.join(" ")} " + + "from vApp #{vapp.name}") + current_vapp = get_vapp(vapp) + unique_network_names.each do |n| + current_vapp.network_config_section.delete_network_config(n) + end + task = @connection.put(current_vapp.network_config_section, + current_vapp.network_config_section, + Xml::MEDIA_TYPE[:NETWORK_CONFIG_SECTION]) + monitor_task(task) + end + + # Size at creation is in bytes + # We currently assumes the disk is SCSI and bus sub type LSILOGIC + def create_disk(name, size_mb, vm = nil, retries = @retries["default"]) + new_disk = Xml::WrapperFactory.create_instance("DiskCreateParams") + new_disk.name = name + new_disk.size_bytes = size_mb * 1024 * 1024 # VCD expects bytes + new_disk.bus_type = Xml::HARDWARE_TYPE[:SCSI_CONTROLLER] + new_disk.bus_sub_type = Xml::BUS_SUB_TYPE[:LSILOGIC] + new_disk.add_locality(vm) if vm + vdc = get_ovdc + @logger.info("Creating independent disk #{name} of #{size_mb}MB.") + @logger.info("Disk locality ist set to #{vm.name} #{vm.urn}.") if vm + disk = @connection.post(vdc.add_disk_link, new_disk, + Xml::MEDIA_TYPE[:DISK_CREATE_PARAMS]) + raise ApiRequestError unless disk.respond_to?(:running_tasks) + # Creating a disk returns a disk with tasks inside + retries.times do |try| + return disk if disk.running_tasks.nil? || disk.running_tasks.empty? + @logger.info("Disk #{disk.urn} has running tasks. Waiting for " + + "tasks to finish. Try: #{try}/#{retries} ." ) + disk.running_tasks.each do |t| + monitor_task(t) + end + disk = @connection.get(disk) + end + end + + def delete_disk(disk) + current_disk = @connection.get(disk) + unless current_disk + @logger.warn("Disk #{disk.name} #{disk.urn} no longer exists.") + return + end + task = @connection.delete(current_disk.delete_link) + monitor_task(task) do |t| + @logger.info("Deleted disk #{current_disk.name} #{current_disk.urn}") + t + end + end + + def attach_disk(disk, vm) + current_vm = @connection.get(vm) + raise ObjectNotFoundError, "VM #{vm.name} not found." unless current_vm + + current_disk = @connection.get(disk) + unless current_disk + raise ObjectNotFoundError, "Disk #{disk.name} not found." + end + + params = Xml::WrapperFactory.create_instance("DiskAttachOrDetachParams") + params.disk_href = current_disk.href + task = @connection.post(current_vm.attach_disk_link, params, + Xml::MEDIA_TYPE[:DISK_ATTACH_DETACH_PARAMS]) + monitor_task(task) do |t| + @logger.info("Attached disk #{current_disk.name} to VM " + + "#{current_vm.name}.") + t + end + end + + def detach_disk(disk, vm) + current_vm = @connection.get(vm) + raise ObjectNotFoundError, "VM #{vm.name} not found." unless current_vm + + current_disk = @connection.get(disk) + unless current_disk + raise ObjectNotFoundError, "Disk #{disk.name} not found." + end + + disk_href = current_disk.href + + if is_vapp_status(current_vm, :SUSPENDED) + @logger.debug("vApp #{current_vm.name} suspended, discard state " + + "before detaching disk.") + raise VmSuspendedError, "discard state first" + end + + begin + get_disk_id(current_vm, disk_href) + rescue DiskNotFoundError + @logger.warn("Disk #{current_disk.name} not found on VM " + + "#{current_vm.name}. No need to detach.") + return + end + params = Xml::WrapperFactory.create_instance("DiskAttachOrDetachParams") + params.disk_href = disk_href + task = @connection.post(current_vm.detach_disk_link, params, + Xml::MEDIA_TYPE[:DISK_ATTACH_DETACH_PARAMS]) + monitor_task(task) do |t| + @logger.info("Detached disk #{current_disk.name} from VM " + + "#{current_vm.name}.") + t + end + end + + def get_disk(disk_id) + resolve_entity(disk_id) + end + + def power_on_vapp(vapp) + @logger.info("Powering on vApp #{vapp.name} .") + current_vapp = @connection.get(vapp) + unless current_vapp + raise ObjectNotFoundError, "vApp #{vapp.name} not found." + end + @logger.debug("vApp status: #{current_vapp["status"]}") + if is_vapp_status(current_vapp, :POWERED_ON) + @logger.info("vApp #{vapp.name} already powered-on.") + return + end + unless current_vapp.power_on_link + raise CloudError, "vApp #{vapp.name} not in a state to be " + + "powered on." + end + task = @connection.post(current_vapp.power_on_link, nil) + monitor_task(task, @time_limit["power_on"]) + @logger.info("vApp #{current_vapp.name} powered on.") + task + end + + def power_off_vapp(vapp, undeploy = true) + @logger.info("Powering off vApp #{vapp.name} .") + @logger.info("Undeploying vApp #{vapp.name} .") if undeploy + current_vapp = @connection.get(vapp) + unless current_vapp + raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." + end + @logger.debug("vApp status: #{current_vapp["status"]}") + + if is_vapp_status(current_vapp, :SUSPENDED) + @logger.debug("vApp #{current_vapp.name} suspended, discard state " + + "before powering off.") + raise VappSuspendedError, "discard state first" + end + + if undeploy + # Since we do not apparently differentiate between powered-off and + # undeployed in our status, we should check if the undeploy link is + # available first. If undeploy is not available and status is + # powered_off then it is undeployed. + unless current_vapp.undeploy_link + if is_vapp_status(current_vapp, :POWERED_OFF) + @logger.info("vApp #{vapp.name} already powered-off, undeployed.") + return + end + raise CloudError, "vApp #{vapp.name} not in a state be " + + "powered-off, undeployed." + end + params = Xml::WrapperFactory.create_instance("UndeployVAppParams") + task = @connection.post(current_vapp.undeploy_link, params) + monitor_task(task, @time_limit["undeploy"]) + @logger.info("vApp #{current_vapp.name} powered-off, undeployed.") + task + else + unless current_vapp.power_off_link + if is_vapp_status(current_vapp, :POWERED_OFF) + @logger.info("vApp #{vapp.name} already powered off.") + return + end + raise CloudError, "vApp #{vapp.name} not in a state be powered off." + end + task = @connection.post(current_vapp.power_off_link, nil) + monitor_task(task, @time_limit["power_off"]) + @logger.info("vApp #{current_vapp.name} powered off.") + task + end + end + + def discard_suspended_state_vapp(vapp) + @logger.info("Discarding suspended state of vApp #{vapp.name}.") + current_vapp = @connection.get(vapp) + unless current_vapp + raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." + end + @logger.debug("vApp status: #{current_vapp["status"]}") + + return unless is_vapp_status(current_vapp, :SUSPENDED) + + @logger.info("Discarding suspended state of vApp #{current_vapp.name}.") + task = @connection.post(current_vapp.discard_state, nil) + monitor_task(task, @time_limit["undeploy"]) + current_vapp = @connection.get(current_vapp) + @logger.info("vApp #{current_vapp.name} suspended state discarded.") + task + end + + def reboot_vapp(vapp) + @logger.info("Rebooting vApp #{vapp.name}.") + current_vapp = @connection.get(vapp) + unless current_vapp + raise ObjectNotFoundError, "vApp #{vapp.name} no longer exists." + end + @logger.debug("vApp status: #{current_vapp["status"]}") + + if is_vapp_status(current_vapp, :SUSPENDED) + @logger.debug("vApp #{current_vapp.name} suspended.") + raise VappSuspendedError, "vapp suspended" + end + if is_vapp_status(current_vapp, :POWERED_OFF) + @logger.debug("vApp #{current_vapp.name} powered off.") + raise VappPoweredOffError, "vapp powered off" + end + + @logger.info("Rebooting vApp #{current_vapp.name}.") + task = @connection.post(current_vapp.reboot_link, nil) + monitor_task(task) + current_vapp = @connection.get(current_vapp) + @logger.info("vApp #{current_vapp.name} rebooted.") + task + end + + def get_ovdc + vdc = @admin_org.vdc(@ovdc_name) + raise ObjectNotFoundError, "VDC #{@ovdc_name} not found." unless vdc + @connection.get(vdc) + end + + def get_catalog(name) + catalog = @connection.get(@admin_org.catalog(name)) + end + + ERROR_STATUSES = [Xml::TASK_STATUS[:ABORTED], Xml::TASK_STATUS[:ERROR], + Xml::TASK_STATUS[:CANCELED]] + SUCCESS_STATUS = [Xml::TASK_STATUS[:SUCCESS]] + + def resolve_entity(id) + url = "#{@entity_resolver_link}#{id}" + entity = @connection.get(url) + raise ObjectNotFoundError, "Unable to get entity" unless entity + @connection.get(entity.link) + end + + def get_disk_id(vm, disk_href) + hardware_section = vm.hardware_section + disk = hardware_section.hard_disks.find do |d| + d.host_resource["disk"] == disk_href + end + unless disk + raise DiskNotFoundError, "Disk with href #{disk_href} not attached " + + "to VM #{vm.name}." + end + disk.disk_id + end + + def log_exception(e, message = nil) + @logger.error(message) if message + @logger.error(e.message) + @logger.error(e.backtrace.join("\n\r")) + end + + def copy_network_settings(network, network_config, vapp_net_name, + fence_mode) + config_ip_scope = network_config.ip_scope + net_ip_scope = network.ip_scope + config_ip_scope.is_inherited = net_ip_scope.is_inherited? + config_ip_scope.gateway= net_ip_scope.gateway + config_ip_scope.netmask = net_ip_scope.netmask + if net_ip_scope.start_address + config_ip_scope.start_address = net_ip_scope.start_address + end + if net_ip_scope.end_address + config_ip_scope.end_address = net_ip_scope.end_address + end + network_config.fence_mode = fence_mode + network_config.parent_network["name"] = network["name"] + network_config.parent_network["href"] = network["href"] + network_config["networkName"] = vapp_net_name + end + + def delete_vapp_template(vapp_template) + delete_vapp_or_template(vapp_template, @retries["default"], + @time_limit["delete_vapp_template"], "vApp Template") + end + + def check_vapp_for_remove_link(vapp) + current_vapp = @connection.get(vapp) + unless current_vapp.remove_link + raise ObjectNotFoundError, "No link available to delete vApp." + end + return current_vapp + end + + def delete_vapp_or_template(vapp, retries, time_limit, type_name) + retries.times do |try| + @logger.info("Deleting #{type_name} #{vapp.name}") + current_vapp = @connection.get(vapp) + if (current_vapp.running_tasks.empty?) + Util.retry_operation(current_vapp, @retries["default"], + @control["backoff"]) do + current_vapp = check_vapp_for_remove_link(current_vapp) + end + Util.retry_operation(current_vapp.remove_link, @retries["default"], + @control["backoff"]) do + monitor_task(@connection.delete(current_vapp.remove_link), + time_limit) do |task| + @logger.info("#{type_name} #{current_vapp.name} deleted.") + return task + end + end + else + @logger.info("#{vapp.name} has tasks in progress, wait until done.") + current_vapp.running_tasks.each do |task| + monitor_task(task) + end + sleep (@control["backoff"] ** try) + end + end + raise ApiRequestError, + "Unable to delete #{type_name} after #{retries} attempts" + end + + def insert_media(vm, media, retries = @retries["default"]) + params = Xml::WrapperFactory.create_instance("MediaInsertOrEjectParams") + params.media_href = media.href + + # Wait for media to be ready + retries.times do |try| + @logger.info("Inserting media #{media.name} into VM #{vm.name}.") + current_media = @connection.get(media) + if (current_media.running_tasks.empty?) + Util.retry_operation(vm.insert_media_link, @retries["default"], + @control["backoff"]) do + task = @connection.post(vm.insert_media_link, params, + Xml::MEDIA_TYPE[:MEDIA_INSERT_EJECT_PARAMS]) + monitor_task(task) do |t| + raise CloudError, "Error inserting media #{media.name} " + + "into VM #{vm.name}." if t.status != "success" + @logger.info("Inserted media #{media.name} into VM #{vm.name}.") + return t + end + end + else + @logger.info("#{current_media.name} has tasks in progress, " + + "waiting until done.") + current_media.running_tasks.each do |task| + monitor_task(task) + end + sleep (@control["backoff"] ** try) + end + end + raise ApiRequestError, "Unable to insert media #{media.name} into " + + "VM #{vm.name} after #{retries} attempts" + end + + def eject_media(vm, media, retries = @retries["default"]) + params = Xml::WrapperFactory.create_instance("MediaInsertOrEjectParams") + params.media_href = media.href + + #Wait for media to be ready + retries.times do |try| + @logger.info("Ejecting media #{media.name} from VM #{vm.name}.") + current_media = @connection.get(media) + if (current_media.running_tasks.empty?) + return eject_media_task(vm, params, media) + else + @logger.info("#{current_media.name} has tasks in progress, " + + "waiting until done.") + current_media.running_tasks.each do |task| + monitor_task(task) + end + sleep (@control["backoff"] ** try) + end + end + raise ApiRequestError, "Unable to eject media #{media.name} from " + + "VM #{vm.name} after #{retries} attempts" + end + + def delete_media(media, retries = @retries["default"], + time_limit = @time_limit["delete_media"]) + retries.times do |try| + @logger.info("Deleting media #{media.name}") + current_media = @connection.get(media) + if (current_media.running_tasks.empty?) + Util.retry_operation(current_media.delete_link, @retries["default"], + @control["backoff"]) do + monitor_task(@connection.delete(current_media.delete_link), + time_limit) do |task| + @logger.info("Media #{current_media.name} deleted.") + return task + end + end + else + @logger.info("#{current_media.name} has tasks in progress, " + + "waiting until done.") + current_media.running_tasks.each do |task| + monitor_task(task) + end + sleep (@control["backoff"] ** try) + end + end + raise ApiRequestError, "Unable to delete #{type_name} after " + "#{retries} attempts" + end + + def get_catalog_media(name) + get_catalog_item(name, Xml::MEDIA_TYPE[:MEDIA], @media_catalog_name) + end + + # Get catalog item from catalog by name and type. + # Raises an exception if catalog is not found. + # Returns nil if an item matching the name and type is not found. + # Otherwise, returns the catalog item. + # The catalog item is not the uderlying object itself, i.e. vApp template. + def get_catalog_item(name, item_type, catalog_name) + raise ObjectNotFoundError, "Catalog item name cannot be nil" unless name + unless @admin_org.catalog(catalog_name) + raise ObjectNotFoundError, "Catalog #{catalog_name} not found." + end + # For some reason, if the catalog no longer exists, + # VCD throws a Forbidden exception when getting + catalog = @connection.get(@admin_org.catalog(catalog_name)) + items = catalog.catalog_items(name) + if items.nil? || items.empty? + @logger.debug("Item #{name} does not exist in catalog #{catalog_name}") + return nil + end + items.each do |i| + entity = @connection.get(i) + # Return the entity node. Another get on that node is necessary to + # get the actual object itself + return entity if entity.entity["type"] == item_type + end + nil + end + + def get_vm_network_connections(vm) + current_vm = @connection.get(vm) + unless current_vm + raise ObjectNotFoundError, "VM #{vm.name} no longer exists." + end + @connection.get(current_vm.network_connection_section) + end + + def task_progressed?(current_task, prev_progress, prev_status) + (current_task.progress && (current_task.progress != prev_progress)) || + (current_task.status && (current_task.status != prev_status)) + end + + def task_is_success(current_task, success = SUCCESS_STATUS) + success.map { |s| s.downcase }.find { + |s| s == current_task.status.downcase } + end + + def task_has_error(current_task, error_statuses = ERROR_STATUSES) + error_statuses.map { |s| s.downcase }.find { + |s| s == current_task.status.downcase } + end + + def monitor_task(task, time_limit = @time_limit["default"], + error_statuses = ERROR_STATUSES, success = SUCCESS_STATUS, + delay = @control["delay"], &b) + iterations = time_limit / delay + i = 0 + prev_progress = task.progress + prev_status = task.status + current_task = task + while (i < iterations) + @logger.debug("#{current_task.urn} #{current_task.operation} is " + + "#{current_task.status}") + if task_is_success(current_task, success) + if b + return b.call(current_task) + else + return current_task + end + elsif task_has_error(current_task, error_statuses) + raise ApiRequestError, "Task #{task.urn} #{task.operation} did " + + "not complete successfully." + elsif task_progressed?(current_task, prev_progress, prev_status) + @logger.debug("task status #{prev_status} => " + + "#{current_task.status}, progress #{prev_progress}%" + + " => #{current_task.progress}%, timer #{i} reset.") + prev_progress = current_task.progress + prev_status = current_task.status + i = 0 #reset clock if status changes or running task makes progress + sleep(delay) + else + @logger.debug("Approximately #{i * delay}s elapsed waiting for " + + "#{current_task.operation} to reach " + + "#{success.join("/")}/#{error_statuses.join("/")}." + + " Checking again in #{delay} seconds.") + @logger.debug("Task #{task.urn} progress: " + + "#{current_task.progress} %.") if current_task.progress + sleep(delay) + end + current_task = @connection.get(task) + i += 1 + end + raise ApiTimeoutError, "Task #{task.operation} did not complete " + + "within limit of #{time_limit} seconds." + end + + + # TODO use times.upload_vapp_files + def upload_vapp_files(vapp, ovf_directory, + tries = @retries["upload_vapp_files"], try = 0) + current_vapp = @connection.get(vapp) + return current_vapp if !current_vapp.files || current_vapp.files.empty? + + @logger.debug("vapp files left to upload #{current_vapp.files}.") + @logger.debug("vapp incomplete files left to upload " + + "#{current_vapp.incomplete_files}.") + raise ApiTimeoutError, "Unable to finish uploading vApp after " + + "#{tries} tries #{current_vapp.files}." if tries == try + + current_vapp.incomplete_files.each do |f| + # switch on extension + case f.name.split(".").pop.downcase + when "ovf" + @logger.info("Uploading OVF file: " + + "#{ovf_directory.ovf_file_path} for #{vapp.name}") + @connection.put(f.upload_link, ovf_directory.ovf_file.read, + Xml::MEDIA_TYPE[:OVF]) + when "vmdk" + @logger.info("Uploading VMDK file " + + "#{ovf_directory.vmdk_file_path(f.name)} for #{vapp.name}") + @connection.put_file(f.upload_link, + ovf_directory.vmdk_file(f.name)) + end + end + #repeat + sleep (2 ** try) + upload_vapp_files(current_vapp, ovf_directory, tries, try + 1) + end + + def add_catalog_item(item, catalog_name) + unless @admin_org.catalog(catalog_name) + raise ArgumentError, + "Error adding #{item.name}, catalog #{catalog_name} not found." + end + catalog = @connection.get(@admin_org.catalog(catalog_name)) + raise ObjectNotFoundError, "Error adding #{item.name}, catalog " + + "#{catalog_name} not available." unless catalog + catalog_item = Xml::WrapperFactory.create_instance("CatalogItem") + catalog_item.name = item.name + catalog_item.entity = item + @logger.info("Adding #{catalog_item.name} to catalog #{catalog_name}") + @connection.post(catalog.add_item_link, catalog_item, + Xml::ADMIN_MEDIA_TYPE[:CATALOG_ITEM]) + end + + def generate_metadata_href(entity, key) + raise ObjectNotFoundError, "Entity #{entity.name} does not expose a " + + "metadata link method." if !entity.respond_to?(:metadata_link) + "#{entity.metadata_link.href}/#{key}" + end + + def get_vapp_by_name(name) + @logger.debug("Getting vApp #{name}") + vdc = get_ovdc + node = vdc.get_vapp(name) + raise ObjectNotFoundError, "vApp #{name} does not exist." unless node + vapp = @connection.get(node) + raise ObjectNotFoundError, "vApp #{name} does not exist." unless vapp + vapp + end + + def locality_spec(src_vapp_template, disk_locality) + disk_locality ||= [] + locality = {} + disk_locality.each do |disk| + current_disk = @connection.get(disk) + unless current_disk + @logger.warn("Disk #{disk.name} no longer exists.") + next + end + src_vapp_template.vms.each do |vm| + locality[vm] = current_disk + end + end + locality + end + + def is_vapp_status(current_vapp, status) + current_vapp["status"] == Xml::RESOURCE_ENTITY_STATUS[status].to_s + end + + def rollback_upload_vapp(vapp_template) + @logger.error("Rolling back changes.") + begin + delete_vapp_template(vapp_template) if vapp_template + rescue => rollbackex + log_exception(rollbackex, "Error in rolling back failed vApp " + + "template #{vapp_name}.") + end + end + + def eject_media_task(vm, params, media) + Util.retry_operation(vm.eject_media_link, @retries["default"], + @control["backoff"]) do + task = @connection.post(vm.eject_media_link, params, + Xml::MEDIA_TYPE[:MEDIA_INSERT_EJECT_PARAMS]) + monitor_task(task) do |t| + if t.status != "success" + raise CloudError, "Error ejecting media #{media.name} from " + + "VM #{vm.name}." + end + @logger.info("Ejected media #{media.name} from VM #{vm.name}.") + return t + end + end + end + def construct_rest_logger @logger.debug('constructing rest_logger') rest_log_filename = File.join( From 92b5be9f012273374b7272c1bf802554f1a07b11 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Tue, 15 Oct 2013 10:23:22 -0700 Subject: [PATCH 3/7] Move rest_logger from client.rb to connection.rb --- lib/ruby_vcloud_sdk/client.rb | 26 ++------------- lib/ruby_vcloud_sdk/connection/connection.rb | 34 ++++++++++++++------ 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/lib/ruby_vcloud_sdk/client.rb b/lib/ruby_vcloud_sdk/client.rb index 2b51856..f656488 100644 --- a/lib/ruby_vcloud_sdk/client.rb +++ b/lib/ruby_vcloud_sdk/client.rb @@ -35,17 +35,10 @@ class Client private_constant :RETRIES, :TIME_LIMIT_SEC, :REST_THROTTLE - def initialize(url, username, password, option = {}, logger = nil) + def initialize(url, username, password, options = {}, logger = nil) @logger = logger || Logger.new(STDOUT) - @retries = option[:retries] || RETRIES - @time_limit = option[:time_limit_sec] || TIME_LIMIT_SEC - - construct_rest_logger - Config.configure( - { - rest_logger: @rest_logger, - rest_throttle: option[:rest_throttle] || REST_THROTTLE - }) + @retries = options[:retries] || RETRIES + @time_limit = options[:time_limit_sec] || TIME_LIMIT_SEC @connection = Connection::Connection.new( @url, @@ -905,19 +898,6 @@ def eject_media_task(vm, params, media) end end end - - def construct_rest_logger - @logger.debug('constructing rest_logger') - rest_log_filename = File.join( - File.dirname(@logger.instance_eval { @logdev }.dev.path), - 'rest') - log_file = File.open(rest_log_filename, 'w') - log_file.sync = true - - @rest_logger = Logger.new(log_file || STDOUT) - @rest_logger.level = @logger.level - @rest_logger.formatter = @logger.formatter - end end end diff --git a/lib/ruby_vcloud_sdk/connection/connection.rb b/lib/ruby_vcloud_sdk/connection/connection.rb index 0f9456d..a4d5f95 100644 --- a/lib/ruby_vcloud_sdk/connection/connection.rb +++ b/lib/ruby_vcloud_sdk/connection/connection.rb @@ -10,8 +10,11 @@ class Connection def initialize(url, request_timeout = nil, rest_client = nil, site = nil, file_uploader = nil) @logger = Config.logger - @rest_logger = Config.rest_logger @rest_throttle = Config.rest_throttle + + construct_rest_logger + Config.configure(rest_logger: @rest_logger) + rest_client = RestClient unless rest_client rest_client.log = @rest_logger request_timeout = 60 unless request_timeout @@ -26,7 +29,7 @@ def connect(username, password) auth_header_value = "Basic #{Base64.encode64(login_password)}" # TODO: call 'api/versions' first response = @site["/api/sessions"].post( - { Authorization: auth_header_value, Accept: ACCEPT }) + Authorization: auth_header_value, Accept: ACCEPT) @logger.debug(response) @cookies = response.cookies unless @cookies["vcloud-token"].gsub!("+", "%2B").nil? @@ -40,10 +43,9 @@ def get(destination) @rest_logger.info "#{__method__.to_s.upcase} #{delay}\t " + "#{self.class.get_href(destination)}" sleep(delay) - response = @site[get_nested_resource(destination)].get({ - :Accept=>ACCEPT, - :cookies=>@cookies - }) + response = @site[get_nested_resource(destination)].get( + Accept: ACCEPT, + cookies: @cookies) @rest_logger.debug(response) Xml::WrapperFactory.wrap_document(response) end @@ -58,9 +60,9 @@ def post(destination, data, content_type = '*/*') end @rest_logger.info("#{__method__.to_s.upcase} data:#{data.to_s}") response = @site[get_nested_resource(destination)].post(data.to_s, { - :Accept=>ACCEPT, - :cookies=>@cookies, - :content_type=>content_type + Accept: ACCEPT, + cookies: @cookies, + content_type: content_type }) raise ApiRequestError if http_error?(response) @rest_logger.debug(response) @@ -119,6 +121,20 @@ def put_file(destination, file) end private + + def construct_rest_logger + @logger.debug('constructing rest_logger') + rest_log_filename = File.join( + File.dirname(@logger.instance_eval { @logdev }.dev.path), + 'rest') + log_file = File.open(rest_log_filename, 'w') + log_file.sync = true + + @rest_logger = Logger.new(log_file || STDOUT) + @rest_logger.level = @logger.level + @rest_logger.formatter = @logger.formatter + end + def log_exceptions(e) if e.is_a? RestClient::Exception @logger.error("HTTP Code: #{e.http_code}") From 904d785ee1e3df14374e8f24f2799b2f7d459f88 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Tue, 15 Oct 2013 10:41:51 -0700 Subject: [PATCH 4/7] Refactor response_mapping into a separate class --- spec/unit/client_spec.rb | 50 ++++-------------------- spec/unit/{ => mocks}/client_response.rb | 0 spec/unit/mocks/response_mapping.rb | 43 ++++++++++++++++++++ 3 files changed, 50 insertions(+), 43 deletions(-) rename spec/unit/{ => mocks}/client_response.rb (100%) create mode 100644 spec/unit/mocks/response_mapping.rb diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index c75ad28..0aceec3 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -1,49 +1,12 @@ require 'spec_helper' -require_relative 'client_response' +require_relative 'mocks/client_response' +require_relative 'mocks/response_mapping' module VCloudSdk logger = Config.logger Config.configure( - { logger: logger, - rest_throttle: { min: 0, max: 1 } - }) - - response_mapping = { - get: { - Test::Response::ADMIN_VCLOUD_LINK => - lambda do |url, headers| - Test::Response::VCLOUD_RESPONSE - end, - Test::Response::ADMIN_ORG_LINK => - lambda do |url, headers| - Test::Response::ADMIN_ORG_RESPONSE - end - }, - post: { - Test::Response::LOGIN_LINK => - lambda do |url, data, headers| - session_object = Test::Response::SESSION - - def session_object.cookies - { 'vcloud-token' => 'fake-cookie' } - end - - session_object - end - } - } - - def response_mapping.get_mapping(http_method, url) - mapping = self[http_method][url] - if mapping.nil? - err_msg = "Response mapping not found for #{http_method} and #{url}" - Config.logger.error(err_msg) - raise err_msg - end - - mapping - end + rest_throttle: { min: 0, max: 1 }) describe Client, :min, :all do @@ -59,11 +22,12 @@ def build_url def mock_rest_connection rest_client = double('Rest Client') rest_client.stub(:get) do |headers| - response_mapping.get_mapping(:get, build_url).call(build_url, headers) + Test::ResponseMapping.get_mapping(:get, build_url).call(build_url, + headers) end rest_client.stub(:post) do |data, headers| - response_mapping.get_mapping(:post, build_url).call(build_url, data, - headers) + Test::ResponseMapping.get_mapping(:post, build_url).call(build_url, + data, headers) end rest_client.stub(:[]) do |value| @resource = value diff --git a/spec/unit/client_response.rb b/spec/unit/mocks/client_response.rb similarity index 100% rename from spec/unit/client_response.rb rename to spec/unit/mocks/client_response.rb diff --git a/spec/unit/mocks/response_mapping.rb b/spec/unit/mocks/response_mapping.rb new file mode 100644 index 0000000..4ea91d0 --- /dev/null +++ b/spec/unit/mocks/response_mapping.rb @@ -0,0 +1,43 @@ +module VCloudSdk + module Test + class ResponseMapping + LINK_TO_RESPONSE = { + get: { + Test::Response::ADMIN_VCLOUD_LINK => + lambda do |url, headers| + Test::Response::VCLOUD_RESPONSE + end, + Test::Response::ADMIN_ORG_LINK => + lambda do |url, headers| + Test::Response::ADMIN_ORG_RESPONSE + end + }, + post: { + Test::Response::LOGIN_LINK => + lambda do |url, data, headers| + session_object = Test::Response::SESSION + + def session_object.cookies + { 'vcloud-token' => 'fake-cookie' } + end + + session_object + end + } + } + + private_constant :LINK_TO_RESPONSE + + def self.get_mapping(http_method, url) + mapping = LINK_TO_RESPONSE[http_method][url] + if mapping.nil? + err_msg = "Response mapping not found for #{http_method} and #{url}" + Config.logger.error(err_msg) + fail err_msg + end + + mapping + end + end + end +end From bf172fb89f7204aa3a9c4c78f7e850d543a7a592 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Tue, 15 Oct 2013 14:23:52 -0700 Subject: [PATCH 5/7] Add unit test for client.rb to check reading settings from argument or default --- lib/ruby_vcloud_sdk/client.rb | 5 +- lib/ruby_vcloud_sdk/connection/connection.rb | 20 +-- spec/spec_helper.rb | 8 +- spec/unit/client_spec.rb | 152 ++++++++++++++----- 4 files changed, 138 insertions(+), 47 deletions(-) diff --git a/lib/ruby_vcloud_sdk/client.rb b/lib/ruby_vcloud_sdk/client.rb index f656488..f39d68f 100644 --- a/lib/ruby_vcloud_sdk/client.rb +++ b/lib/ruby_vcloud_sdk/client.rb @@ -1,5 +1,5 @@ -require 'rest_client' # Need this for the exception classes -require 'set' +require "rest_client" # Need this for the exception classes +require "set" module VCloudSdk @@ -39,6 +39,7 @@ def initialize(url, username, password, options = {}, logger = nil) @logger = logger || Logger.new(STDOUT) @retries = options[:retries] || RETRIES @time_limit = options[:time_limit_sec] || TIME_LIMIT_SEC + Config.configure(rest_throttle: options[:rest_throttle] || REST_THROTTLE) @connection = Connection::Connection.new( @url, diff --git a/lib/ruby_vcloud_sdk/connection/connection.rb b/lib/ruby_vcloud_sdk/connection/connection.rb index a4d5f95..cfcae3d 100644 --- a/lib/ruby_vcloud_sdk/connection/connection.rb +++ b/lib/ruby_vcloud_sdk/connection/connection.rb @@ -1,11 +1,13 @@ -require 'base64' -require 'rest_client' +require "base64" +require "rest_client" module VCloudSdk module Connection class Connection - ACCEPT = 'application/*+xml;version=5.1' + ACCEPT = "application/*+xml;version=5.1" + + private_constant :ACCEPT def initialize(url, request_timeout = nil, rest_client = nil, site = nil, file_uploader = nil) @@ -27,7 +29,7 @@ def initialize(url, request_timeout = nil, def connect(username, password) login_password = "#{username}:#{password}" auth_header_value = "Basic #{Base64.encode64(login_password)}" - # TODO: call 'api/versions' first + # TODO: call "api/versions" first response = @site["/api/sessions"].post( Authorization: auth_header_value, Accept: ACCEPT) @logger.debug(response) @@ -50,7 +52,7 @@ def get(destination) Xml::WrapperFactory.wrap_document(response) end - def post(destination, data, content_type = '*/*') + def post(destination, data, content_type = "*/*") @rest_logger.info "#{__method__.to_s.upcase} #{delay}\t " + "#{self.class.get_href(destination)}" sleep(delay) @@ -64,7 +66,7 @@ def post(destination, data, content_type = '*/*') cookies: @cookies, content_type: content_type }) - raise ApiRequestError if http_error?(response) + fail ApiRequestError if http_error?(response) @rest_logger.debug(response) Xml::WrapperFactory.wrap_document(response) end @@ -123,11 +125,11 @@ def put_file(destination, file) private def construct_rest_logger - @logger.debug('constructing rest_logger') + @logger.debug("constructing rest_logger") rest_log_filename = File.join( File.dirname(@logger.instance_eval { @logdev }.dev.path), - 'rest') - log_file = File.open(rest_log_filename, 'w') + "rest") + log_file = File.open(rest_log_filename, "w") log_file.sync = true @rest_logger = Logger.new(log_file || STDOUT) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6f1f309..b63e4a8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -60,8 +60,14 @@ def rest_logger.<<(str) end rest_logger end - end + def verify_settings(obj, settings) + settings.each do |instance_variable_name, target_value| + instance_variable = obj.instance_variable_get(instance_variable_name) + instance_variable.should == target_value + end + end + end end module Xml diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index 0aceec3..e428f12 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -1,47 +1,129 @@ -require 'spec_helper' -require_relative 'mocks/client_response' -require_relative 'mocks/response_mapping' +require "spec_helper" +require_relative "mocks/client_response" +require_relative "mocks/response_mapping" +require "nokogiri/diff" module VCloudSdk - logger = Config.logger - Config.configure( - logger: logger, - rest_throttle: { min: 0, max: 1 }) + module Test + logger = Config.logger - describe Client, :min, :all do + describe Client, :min, :all do - let(:url) { 'https://10.147.0.0:8443' } - let(:username) { 'cfadmin' } - let(:password) { 'akimbi' } - let(:response_mapping) { response_mapping } + let(:url) { "https://10.147.0.0:8443" } + let(:username) { "cfadmin" } + let(:password) { "akimbi" } + let(:response_mapping) { response_mapping } - def build_url - url + @resource - end - - def mock_rest_connection - rest_client = double('Rest Client') - rest_client.stub(:get) do |headers| - Test::ResponseMapping.get_mapping(:get, build_url).call(build_url, - headers) + def build_url + url + @resource end - rest_client.stub(:post) do |data, headers| - Test::ResponseMapping.get_mapping(:post, build_url).call(build_url, - data, headers) - end - rest_client.stub(:[]) do |value| - @resource = value - rest_client + + def mock_rest_connection + rest_client = double("Rest Client") + rest_client.stub(:get) do |headers| + ResponseMapping.get_mapping(:get, build_url).call(build_url, + headers) + end + rest_client.stub(:post) do |data, headers| + ResponseMapping.get_mapping(:post, build_url).call(build_url, + data, headers) + end + rest_client.stub(:[]) do |value| + @resource = value + rest_client + end + + conn = Connection::Connection.new(url, nil, nil, rest_client) end - conn = Connection::Connection.new(url, nil, nil, rest_client) - end + describe ".initialize" do + it "set up connection successfully" do + Config.configure( + logger: logger, + rest_throttle: { min: 0, max: 1 }) + + conn = mock_rest_connection + Connection::Connection.should_receive(:new).with(anything, anything).once.and_return conn + Client.new(url, username, password, {}, logger) + end + + it "use default settings if not specified in input arguments" do + conn = double("Connection") + root_session = Xml::WrapperFactory.wrap_document( + Response::SESSION) + vcloud_response = Xml::WrapperFactory.wrap_document( + Response::VCLOUD_RESPONSE) + admin_org_response = Xml::WrapperFactory.wrap_document( + Response::ADMIN_ORG_RESPONSE) + + Connection::Connection.should_receive(:new).with(anything, anything).once.and_return conn + conn.should_receive(:connect).with(username, password).once.ordered.and_return( + root_session) + conn.should_receive(:get).with(root_session.admin_root).once.ordered.and_return( + vcloud_response) + conn.should_receive(:get).with(vcloud_response.organization).once.ordered.and_return( + admin_org_response) + client = Client.new(nil, username, password, {}, logger) + Test.verify_settings client, + :@retries => Client.const_get(:RETRIES), + :@time_limit => Client.const_get(:TIME_LIMIT_SEC) + + Config.rest_throttle.should eq Client.const_get(:REST_THROTTLE) + end + + it "use settings in input arguments" do + conn = double("Connection") + root_session = Xml::WrapperFactory.wrap_document( + Response::SESSION) + vcloud_response = Xml::WrapperFactory.wrap_document( + Response::VCLOUD_RESPONSE) + admin_org_response = Xml::WrapperFactory.wrap_document( + Response::ADMIN_ORG_RESPONSE) + + Connection::Connection.should_receive(:new).with(anything, anything).once.and_return conn + conn.should_receive(:connect).with(username, password).once.ordered.and_return( + root_session) + conn.should_receive(:get).with(root_session.admin_root).once.ordered.and_return( + vcloud_response) + conn.should_receive(:get).with(vcloud_response.organization).once.ordered.and_return( + admin_org_response) + + retries = + { + default: 5, + upload_vapp_files: 7, + cpi: 1 + } + + time_limit_sec = + { + default: 120, + delete_vapp_template: 120, + delete_vapp: 120, + delete_media: 120, + instantiate_vapp_template: 300, + power_on: 600, + power_off: 600, + undeploy: 720, + process_descriptor_vapp_template: 300, + http_request: 240 + } + + rest_throttle = + { + min: 0, + max: 1 + } + + options = { retries: retries, time_limit_sec: time_limit_sec, rest_throttle: rest_throttle } + client = Client.new(nil, username, password, + options, logger) + Test.verify_settings client, + :@retries => retries, + :@time_limit => time_limit_sec - describe '.initialize' do - it 'set up connection successfully' do - conn = mock_rest_connection - Connection::Connection.stub(:new).with(anything, anything).and_return conn - Client.new(url, username, password, {}, logger) + Config.rest_throttle.should eq rest_throttle + end end end end From 40ad0cb01e80358693b66a754812be823b79bc91 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Tue, 15 Oct 2013 14:46:59 -0700 Subject: [PATCH 6/7] Add unit test for connection.rb --- spec/unit/client_spec.rb | 5 +++-- spec/unit/connection_spec.rb | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 spec/unit/connection_spec.rb diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index e428f12..7675613 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -20,6 +20,7 @@ def build_url def mock_rest_connection rest_client = double("Rest Client") + site = double("site") rest_client.stub(:get) do |headers| ResponseMapping.get_mapping(:get, build_url).call(build_url, headers) @@ -28,12 +29,12 @@ def mock_rest_connection ResponseMapping.get_mapping(:post, build_url).call(build_url, data, headers) end - rest_client.stub(:[]) do |value| + site.stub(:[]) do |value| @resource = value rest_client end - conn = Connection::Connection.new(url, nil, nil, rest_client) + conn = Connection::Connection.new(url, nil, nil, site) end describe ".initialize" do diff --git a/spec/unit/connection_spec.rb b/spec/unit/connection_spec.rb new file mode 100644 index 0000000..9a936f6 --- /dev/null +++ b/spec/unit/connection_spec.rb @@ -0,0 +1,21 @@ +require "spec_helper" + +module VCloudSdk + module Test + describe Connection, :min, :all do + let(:url) { "https://10.147.0.0:8443" } + + describe ".initialize" do + it "use settings in input arguments" do + site = double("site") + file_uploader = double("File Uploader") + conn = Connection::Connection.new(url, nil, nil, site, file_uploader) + + Test.verify_settings conn, + :@site => site, + :@file_uploader => file_uploader + end + end + end + end +end From 55bfca996a550da1bd209c070360e4bb976c1552 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Wed, 16 Oct 2013 09:50:24 -0700 Subject: [PATCH 7/7] Remove all namespaces & modules from spec files. --- spec/unit/client_spec.rb | 246 ++++++++++++------------ spec/unit/connection/connection_spec.rb | 17 ++ spec/unit/connection_spec.rb | 21 -- 3 files changed, 140 insertions(+), 144 deletions(-) create mode 100644 spec/unit/connection/connection_spec.rb delete mode 100644 spec/unit/connection_spec.rb diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb index 7675613..acbc608 100644 --- a/spec/unit/client_spec.rb +++ b/spec/unit/client_spec.rb @@ -3,129 +3,129 @@ require_relative "mocks/response_mapping" require "nokogiri/diff" -module VCloudSdk - module Test - logger = Config.logger - - describe Client, :min, :all do - - let(:url) { "https://10.147.0.0:8443" } - let(:username) { "cfadmin" } - let(:password) { "akimbi" } - let(:response_mapping) { response_mapping } - - def build_url - url + @resource - end - - def mock_rest_connection - rest_client = double("Rest Client") - site = double("site") - rest_client.stub(:get) do |headers| - ResponseMapping.get_mapping(:get, build_url).call(build_url, - headers) - end - rest_client.stub(:post) do |data, headers| - ResponseMapping.get_mapping(:post, build_url).call(build_url, - data, headers) - end - site.stub(:[]) do |value| - @resource = value - rest_client - end - - conn = Connection::Connection.new(url, nil, nil, site) - end - - describe ".initialize" do - it "set up connection successfully" do - Config.configure( - logger: logger, - rest_throttle: { min: 0, max: 1 }) - - conn = mock_rest_connection - Connection::Connection.should_receive(:new).with(anything, anything).once.and_return conn - Client.new(url, username, password, {}, logger) - end - - it "use default settings if not specified in input arguments" do - conn = double("Connection") - root_session = Xml::WrapperFactory.wrap_document( - Response::SESSION) - vcloud_response = Xml::WrapperFactory.wrap_document( - Response::VCLOUD_RESPONSE) - admin_org_response = Xml::WrapperFactory.wrap_document( - Response::ADMIN_ORG_RESPONSE) - - Connection::Connection.should_receive(:new).with(anything, anything).once.and_return conn - conn.should_receive(:connect).with(username, password).once.ordered.and_return( - root_session) - conn.should_receive(:get).with(root_session.admin_root).once.ordered.and_return( - vcloud_response) - conn.should_receive(:get).with(vcloud_response.organization).once.ordered.and_return( - admin_org_response) - client = Client.new(nil, username, password, {}, logger) - Test.verify_settings client, - :@retries => Client.const_get(:RETRIES), - :@time_limit => Client.const_get(:TIME_LIMIT_SEC) - - Config.rest_throttle.should eq Client.const_get(:REST_THROTTLE) - end - - it "use settings in input arguments" do - conn = double("Connection") - root_session = Xml::WrapperFactory.wrap_document( - Response::SESSION) - vcloud_response = Xml::WrapperFactory.wrap_document( - Response::VCLOUD_RESPONSE) - admin_org_response = Xml::WrapperFactory.wrap_document( - Response::ADMIN_ORG_RESPONSE) - - Connection::Connection.should_receive(:new).with(anything, anything).once.and_return conn - conn.should_receive(:connect).with(username, password).once.ordered.and_return( - root_session) - conn.should_receive(:get).with(root_session.admin_root).once.ordered.and_return( - vcloud_response) - conn.should_receive(:get).with(vcloud_response.organization).once.ordered.and_return( - admin_org_response) - - retries = - { - default: 5, - upload_vapp_files: 7, - cpi: 1 - } - - time_limit_sec = - { - default: 120, - delete_vapp_template: 120, - delete_vapp: 120, - delete_media: 120, - instantiate_vapp_template: 300, - power_on: 600, - power_off: 600, - undeploy: 720, - process_descriptor_vapp_template: 300, - http_request: 240 - } - - rest_throttle = - { - min: 0, - max: 1 - } - - options = { retries: retries, time_limit_sec: time_limit_sec, rest_throttle: rest_throttle } - client = Client.new(nil, username, password, - options, logger) - Test.verify_settings client, - :@retries => retries, - :@time_limit => time_limit_sec - - Config.rest_throttle.should eq rest_throttle - end - end +logger = VCloudSdk::Config.logger + +describe VCloudSdk::Client, :min, :all do + + let(:url) { "https://10.147.0.0:8443" } + let(:username) { "cfadmin" } + let(:password) { "akimbi" } + let(:response_mapping) { response_mapping } + let(:conn) { double("Connection") } + let(:root_session) do + VCloudSdk::Xml::WrapperFactory + .wrap_document(VCloudSdk::Test::Response::SESSION) + end + + let(:vcloud_response) do + VCloudSdk::Xml::WrapperFactory + .wrap_document(VCloudSdk::Test::Response::VCLOUD_RESPONSE) + end + + let(:admin_org_response) do + VCloudSdk::Xml::WrapperFactory + .wrap_document(VCloudSdk::Test::Response::ADMIN_ORG_RESPONSE) + end + + def build_url + url + @resource + end + + def mock_rest_connection + rest_client = double("Rest Client") + site = double("site") + rest_client.stub(:get) do |headers| + VCloudSdk::Test::ResponseMapping + .get_mapping(:get, build_url).call(build_url, headers) + end + rest_client.stub(:post) do |data, headers| + VCloudSdk::Test::ResponseMapping + .get_mapping(:post, build_url).call(build_url, data, headers) + end + site.stub(:[]) do |value| + @resource = value + rest_client + end + + VCloudSdk::Connection::Connection.new(url, nil, nil, site) + end + + describe "#initialize" do + it "set up connection successfully" do + VCloudSdk::Config.configure( + logger: logger, + rest_throttle: { min: 0, max: 1 }) + + connection = mock_rest_connection + VCloudSdk::Connection::Connection.should_receive(:new) + .with(anything, anything).once.and_return connection + described_class.new(url, username, password, {}, logger) + end + + it "use default settings if not specified in input arguments" do + VCloudSdk::Connection::Connection.should_receive(:new) + .with(anything, anything).once.and_return conn + conn.should_receive(:connect).with(username, password) + .once.ordered.and_return(root_session) + conn.should_receive(:get).with(root_session.admin_root) + .once.ordered.and_return(vcloud_response) + conn.should_receive(:get).with(vcloud_response.organization) + .once.ordered.and_return(admin_org_response) + client = described_class.new(nil, username, password, {}, logger) + VCloudSdk::Test.verify_settings client, + :@retries => VCloudSdk::Client + .const_get(:RETRIES), + :@time_limit => VCloudSdk::Client + .const_get(:TIME_LIMIT_SEC) + + VCloudSdk::Config.rest_throttle.should eq VCloudSdk::Client.const_get(:REST_THROTTLE) + end + + it "use settings in input arguments" do + VCloudSdk::Connection::Connection.should_receive(:new) + .with(anything, anything).once.and_return conn + conn.should_receive(:connect).with(username, password) + .once.ordered.and_return(root_session) + conn.should_receive(:get).with(root_session.admin_root) + .once.ordered.and_return(vcloud_response) + conn.should_receive(:get).with(vcloud_response.organization) + .once.ordered.and_return(admin_org_response) + + retries = + { + default: 5, + upload_vapp_files: 7, + cpi: 1 + } + + time_limit_sec = + { + default: 120, + delete_vapp_template: 120, + delete_vapp: 120, + delete_media: 120, + instantiate_vapp_template: 300, + power_on: 600, + power_off: 600, + undeploy: 720, + process_descriptor_vapp_template: 300, + http_request: 240 + } + + rest_throttle = + { + min: 0, + max: 1 + } + + options = { retries: retries, time_limit_sec: time_limit_sec, rest_throttle: rest_throttle } + client = described_class.new(nil, username, password, + options, logger) + VCloudSdk::Test.verify_settings client, + :@retries => retries, + :@time_limit => time_limit_sec + + VCloudSdk::Config.rest_throttle.should eq rest_throttle end end end diff --git a/spec/unit/connection/connection_spec.rb b/spec/unit/connection/connection_spec.rb new file mode 100644 index 0000000..ce06dab --- /dev/null +++ b/spec/unit/connection/connection_spec.rb @@ -0,0 +1,17 @@ +require "spec_helper" + +describe VCloudSdk::Connection::Connection, :min, :all do + let(:url) { "https://10.147.0.0:8443" } + + describe "#initialize" do + it "use settings in input arguments" do + site = double("site") + file_uploader = double("File Uploader") + conn = described_class.new(url, nil, nil, site, file_uploader) + + VCloudSdk::Test.verify_settings conn, + :@site => site, + :@file_uploader => file_uploader + end + end +end diff --git a/spec/unit/connection_spec.rb b/spec/unit/connection_spec.rb deleted file mode 100644 index 9a936f6..0000000 --- a/spec/unit/connection_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require "spec_helper" - -module VCloudSdk - module Test - describe Connection, :min, :all do - let(:url) { "https://10.147.0.0:8443" } - - describe ".initialize" do - it "use settings in input arguments" do - site = double("site") - file_uploader = double("File Uploader") - conn = Connection::Connection.new(url, nil, nil, site, file_uploader) - - Test.verify_settings conn, - :@site => site, - :@file_uploader => file_uploader - end - end - end - end -end