# # Copyright 2012 Stefano Tortarolo # Copyright 2013 Fabio Rapposelli and Timo Sugliani # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require 'ruby-progressbar' require 'set' require 'netaddr' require 'uri' module VagrantPlugins module VCloud module Driver # Main class to access vCloud rest APIs class Version_5_1 < Base attr_reader :auth_key, :id ## # Init the driver with the Vagrantfile information def initialize(hostname, username, password, org_name) @logger = Log4r::Logger.new('vagrant::provider::vcloud::driver_5_1') uri = URI(hostname) @api_url = "#{uri.scheme}://#{uri.host}:#{uri.port}/api" @host_url = "#{uri.scheme}://#{uri.host}:#{uri.port}" @username = username @password = password @org_name = org_name @api_version = '5.1' @id = nil @cached_vapp_edge_public_ips = {} end ## # Authenticate against the specified server def login params = { 'method' => :post, 'command' => '/sessions' } _response, headers = send_request(params) if !headers.key?('x-vcloud-authorization') raise 'Failed to authenticate: ' \ 'missing x-vcloud-authorization header' end @auth_key = headers['x-vcloud-authorization'] end ## # Destroy the current session def logout params = { 'method' => :delete, 'command' => '/session' } _response, _headers = send_request(params) # reset auth key to nil @auth_key = nil end ## # Fetch existing organizations and their IDs def get_organizations params = { 'method' => :get, 'command' => '/org' } response, _headers = send_request(params) orgs = response.css('OrgList Org') results = {} orgs.each do |org| results[org['name']] = URI(org['href']).path.gsub('/api/org/', '') end results end ## # friendly helper method to fetch an Organization Id by name # - name (this isn't case sensitive) def get_organization_id_by_name(name) result = nil # Fetch all organizations organizations = get_organizations organizations.each do |organization| if organization[0].downcase == name.downcase result = organization[1] end end result end ## # friendly helper method to fetch an Organization by name # - name (this isn't case sensitive) def get_organization_by_name(name) result = nil # Fetch all organizations organizations = get_organizations organizations.each do |organization| if organization[0].downcase == name.downcase result = get_organization(organization[1]) end end result end ## # Fetch details about an organization: # - catalogs # - vdcs # - networks def get_organization(org_id) params = { 'method' => :get, 'command' => "/org/#{org_id}" } response, _headers = send_request(params) catalogs = {} response.css( "Link[type='application/vnd.vmware.vcloud.catalog+xml']" ).each do |item| catalogs[item['name']] = URI(item['href']).path.gsub( '/api/catalog/', '' ) end vdcs = {} response.css( "Link[type='application/vnd.vmware.vcloud.vdc+xml']" ).each do |item| vdcs[item['name']] = URI(item['href']).path.gsub( '/api/vdc/', '' ) end networks = {} response.css( "Link[type='application/vnd.vmware.vcloud.orgNetwork+xml']" ).each do |item| networks[item['name']] = URI(item['href']).path.gsub( '/api/network/', '' ) end tasklists = {} response.css( "Link[type='application/vnd.vmware.vcloud.tasksList+xml']" ).each do |item| tasklists[item['name']] = URI(item['href']).path.gsub( '/api/tasksList/', '' ) end { :catalogs => catalogs, :vdcs => vdcs, :networks => networks, :tasklists => tasklists } end ## # Fetch details about a given catalog def get_catalog(catalog_id) params = { 'method' => :get, 'command' => "/catalog/#{catalog_id}" } response, _headers = send_request(params) description = response.css('Description').first description = description.text unless description.nil? items = {} response.css( "CatalogItem[type='application/vnd.vmware.vcloud.catalogItem+xml']" ).each do |item| items[item['name']] = URI(item['href']).path.gsub( '/api/catalogItem/', '' ) end { :description => description, :items => items } end ## # Friendly helper method to fetch an catalog id by name # - organization hash (from get_organization/get_organization_by_name) # - catalog name def get_catalog_id_by_name(organization, catalog_name) result = nil organization[:catalogs].each do |catalog| if catalog[0].downcase == catalog_name.downcase result = catalog[1] end end if result.nil? # catalog not found, search in global catalogs as well # that are not listed in organization directly params = { 'method' => :get, 'command' => '/catalogs/query/', 'cacheable' => true } response, _headers = send_request(params) catalogs = {} response.css( 'CatalogRecord' ).each do |item| catalogs[item['name']] = URI(item['href']).path.gsub( '/api/catalog/', '' ) end catalogs.each do |catalog| if catalog[0].downcase == catalog_name.downcase result = catalog[1] end end end result end ## # Friendly helper method to fetch an catalog by name # - organization hash (from get_organization/get_organization_by_name) # - catalog name def get_catalog_by_name(organization, catalog_name) result = nil organization[:catalogs].each do |catalog| if catalog[0].downcase == catalog_name.downcase result = get_catalog(catalog[1]) end end result end ## # Fetch details about a given vdc: # - description # - vapps # - networks def get_vdc(vdc_id) params = { 'method' => :get, 'command' => "/vdc/#{vdc_id}" } response, _headers = send_request(params) description = response.css('Description').first description = description.text unless description.nil? vapps = {} response.css( "ResourceEntity[type='application/vnd.vmware.vcloud.vApp+xml']" ).each do |item| vapps[item['name']] = URI(item['href']).path.gsub( '/api/vApp/vapp-', '' ) end networks = {} response.css( "Network[type='application/vnd.vmware.vcloud.network+xml']" ).each do |item| networks[item['name']] = URI(item['href']).path.gsub( '/api/network/', '' ) end { :description => description, :vapps => vapps, :networks => networks } end ## # Friendly helper method to fetch a Organization VDC Id by name # - Organization object # - Organization VDC Name def get_vdc_id_by_name(organization, vdc_name) result = nil organization[:vdcs].each do |vdc| if vdc[0].downcase == vdc_name.downcase result = vdc[1] end end result end ## # Friendly helper method to fetch a Organization VDC by name # - Organization object # - Organization VDC Name def get_vdc_by_name(organization, vdc_name) result = nil organization[:vdcs].each do |vdc| if vdc[0].downcase == vdc_name.downcase result = get_vdc(vdc[1]) end end result end ## # Fetch details about a given catalog item: # - description # - vApp templates def get_catalog_item(catalog_item_id) params = { 'method' => :get, 'command' => "/catalogItem/#{catalog_item_id}" } response, _headers = send_request(params) description = response.css('Description').first description = description.text unless description.nil? items = {} response.css( "Entity[type='application/vnd.vmware.vcloud.vAppTemplate+xml']" ).each do |item| items[item['name']] = URI(item['href']).path.gsub( '/api/vAppTemplate/vappTemplate-', '' ) end { :description => description, :items => items } end ## # friendly helper method to fetch an catalogItem by name # - catalogId (use get_catalog_name(org, name)) # - catalagItemName def get_catalog_item_by_name(catalog_id, catalog_item_name) result = nil catalog_elems = get_catalog(catalog_id) catalog_elems[:items].each do |catalog_elem| catalog_item = get_catalog_item(catalog_elem[1]) if catalog_item[:items][catalog_item_name] # This is a vApp Catalog Item # fetch CatalogItemId catalog_item_id = catalog_item[:items][catalog_item_name] # Fetch the catalogItemId information params = { 'method' => :get, 'command' => "/vAppTemplate/vappTemplate-#{catalog_item_id}" } response, _headers = send_request(params) # VMs Hash for all the vApp VM entities vms_hash = {} response.css('/VAppTemplate/Children/Vm').each do |vm_elem| vm_name = vm_elem['name'] vm_id = URI(vm_elem['href']).path.gsub( '/api/vAppTemplate/vm-', '' ) # Add the VM name/id to the VMs Hash vms_hash[vm_name] = { :id => vm_id } end result = { catalog_item_name => catalog_item_id, :vms_hash => vms_hash } end end result end ## # Fetch details about a given vapp: # - name # - description # - status # - IP # - Children VMs: # -- IP addresses # -- status # -- ID def get_vapp(vapp_id) params = { 'method' => :get, 'command' => "/vApp/vapp-#{vapp_id}" } response, _headers = send_request(params) vapp_node = response.css('VApp').first if vapp_node name = vapp_node['name'] status = convert_vapp_status(vapp_node['status']) end description = response.css('Description').first description = description.text unless description.nil? ip = response.css('IpAddress').first ip = ip.text unless ip.nil? vms = response.css('Children Vm') vms_hash = {} # ipAddress could be namespaced or not: # see https://github.com/astratto/vcloud-rest/issues/3 vms.each do |vm| vapp_local_id = vm.css('VAppScopedLocalId') addresses = vm.css('rasd|Connection').collect { |n| n['vcloud:ipAddress'] || n['ipAddress'] } vms_hash[vm['name'].to_sym] = { :addresses => addresses, :status => convert_vapp_status(vm['status']), :id => URI(vm['href']).path.gsub('/api/vApp/vm-', ''), :vapp_scoped_local_id => vapp_local_id.text } end # TODO: EXPAND INFO FROM RESPONSE { :name => name, :description => description, :status => status, :ip => ip, :vms_hash => vms_hash } end ## # Delete a given vapp # NOTE: It doesn't verify that the vapp is shutdown def delete_vapp(vapp_id) params = { 'method' => :delete, 'command' => "/vApp/vapp-#{vapp_id}" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Shutdown a given vapp def poweroff_vapp(vapp_id) builder = Nokogiri::XML::Builder.new do |xml| xml.UndeployVAppParams( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5' ) { xml.UndeployPowerAction 'powerOff' } end params = { 'method' => :post, 'command' => "/vApp/vapp-#{vapp_id}/action/undeploy" } _response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.undeployVAppParams+xml' ) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Suspend a given vapp def suspend_vapp(vapp_id) params = { 'method' => :post, 'command' => "/vApp/vapp-#{vapp_id}/power/action/suspend" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # reboot a given vapp # This will basically initial a guest OS reboot, and will only work if # VMware-tools are installed on the underlying VMs. # vShield Edge devices are not affected def reboot_vapp(vapp_id) params = { 'method' => :post, 'command' => "/vApp/vapp-#{vapp_id}/power/action/reboot" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # reset a given vapp # This will basically reset the VMs within the vApp # vShield Edge devices are not affected. def reset_vapp(vapp_id) params = { 'method' => :post, 'command' => "/vApp/vapp-#{vapp_id}/power/action/reset" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Boot a given vapp def poweron_vapp(vapp_id) params = { 'method' => :post, 'command' => "/vApp/vapp-#{vapp_id}/power/action/powerOn" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end #### VM operations #### ## # Delete a given vm # NOTE: It doesn't verify that the vm is shutdown def delete_vm(vm_id) params = { 'method' => :delete, 'command' => "/vApp/vm-#{vm_id}" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Shutdown a given VM # Using undeploy as a REAL powerOff # Only poweroff will put the VM into a partially powered off state. def poweroff_vm(vm_id) builder = Nokogiri::XML::Builder.new do |xml| xml.UndeployVAppParams( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5' ) { xml.UndeployPowerAction 'powerOff' } end params = { 'method' => :post, 'command' => "/vApp/vm-#{vm_id}/action/undeploy" } _response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.undeployVAppParams+xml' ) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Suspend a given VM def suspend_vm(vm_id) builder = Nokogiri::XML::Builder.new do |xml| xml.UndeployVAppParams( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5' ) { xml.UndeployPowerAction 'suspend' } end params = { 'method' => :post, 'command' => "/vApp/vm-#{vm_id}/action/undeploy" } _response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.undeployVAppParams+xml' ) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # reboot a given VM # This will basically initial a guest OS reboot, and will only work if # VMware-tools are installed on the underlying VMs. # vShield Edge devices are not affected def reboot_vm(vm_id) params = { 'method' => :post, 'command' => "/vApp/vm-#{vm_id}/power/action/reboot" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # reset a given VM # This will basically reset the VMs within the vApp # vShield Edge devices are not affected. def reset_vm(vm_id) params = { 'method' => :post, 'command' => "/vApp/vm-#{vm_id}/power/action/reset" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Boot a given VM def poweron_vm(vm_id) params = { 'method' => :post, 'command' => "/vApp/vm-#{vm_id}/power/action/powerOn" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Create a catalog in an organization def create_catalog(org_id, catalog_name, catalog_description) builder = Nokogiri::XML::Builder.new do |xml| xml.AdminCatalog( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'name' => catalog_name ) { xml.Description catalog_description } end params = { 'method' => :post, 'command' => "/admin/org/#{org_id}/catalogs" } response, _headers = send_request( params, builder.to_xml, 'application/vnd.vmware.admin.catalog+xml' ) task_id = URI(response.css( "AdminCatalog Tasks Task[operationName='catalogCreateCatalog']" ).first[:href]).path.gsub('/api/task/', '') catalog_id = URI(response.css( "AdminCatalog Link [type='application/vnd.vmware.vcloud.catalog+xml']" ).first[:href]).path.gsub('/api/catalog/', '') { :task_id => task_id, :catalog_id => catalog_id } end ## # Create a vapp starting from a template # # Params: # - vdc: the associated VDC # - vapp_name: name of the target vapp # - vapp_description: description of the target vapp # - vapp_templateid: ID of the vapp template def create_vapp_from_template(vdc, vapp_name, vapp_description, vapp_template_id, poweron = false) builder = Nokogiri::XML::Builder.new do |xml| xml.InstantiateVAppTemplateParams( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1', 'name' => vapp_name, 'deploy' => 'true', 'powerOn' => poweron ) { xml.Description vapp_description xml.Source( 'href' => "#{@api_url}/vAppTemplate/#{vapp_template_id}" ) } end params = { 'method' => :post, 'command' => "/vdc/#{vdc}/action/instantiateVAppTemplate" } response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml' ) vapp_id = URI(headers['Location']).path.gsub('/api/vApp/vapp-', '') task = response.css( "VApp Task[operationName='vdcInstantiateVapp']" ).first task_id = URI(task['href']).path.gsub('/api/task/', '') { :vapp_id => vapp_id, :task_id => task_id } end ## # Compose a vapp using existing virtual machines # # Params: # - vdc: the associated VDC # - vapp_name: name of the target vapp # - vapp_description: description of the target vapp # - vm_list: hash with IDs of the VMs used in the composing process # - network_config: hash of the network configuration for the vapp def compose_vapp_from_vm(vdc, vapp_name, vapp_description, vm_list = {}, network_config = {}) builder = Nokogiri::XML::Builder.new do |xml| xml.ComposeVAppParams( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1', 'name' => vapp_name, 'deploy' => 'false', 'powerOn' => 'false') { xml.Description vapp_description xml.InstantiationParams { xml.NetworkConfigSection { xml['ovf'].Info 'Configuration parameters for logical networks' xml.NetworkConfig('networkName' => network_config[:name]) { xml.Configuration { if network_config[:fence_mode] != 'bridged' xml.IpScopes { xml.IpScope { xml.IsInherited(network_config[:is_inherited] || 'false') xml.Gateway network_config[:gateway] xml.Netmask network_config[:netmask] xml.Dns1 network_config[:dns1] if network_config[:dns1] xml.Dns2 network_config[:dns2] if network_config[:dns2] xml.DnsSuffix network_config[:dns_suffix] if network_config[:dns_suffix] xml.IpRanges { xml.IpRange { xml.StartAddress network_config[:start_address] xml.EndAddress network_config[:end_address] } } } } end xml.ParentNetwork("href" => "#{@api_url}/network/#{network_config[:parent_network]}") xml.FenceMode network_config[:fence_mode] if network_config[:fence_mode] != 'bridged' xml.Features { xml.FirewallService { xml.IsEnabled(network_config[:enable_firewall] || "false") } xml.NatService { xml.IsEnabled "true" xml.NatType "portForwarding" xml.Policy(network_config[:nat_policy_type] || "allowTraffic") } } end } } } } vm_list.each do |vm_name, vm_id| xml.SourcedItem { xml.Source('href' => "#{@api_url}/vAppTemplate/vm-#{vm_id}", 'name' => vm_name) xml.InstantiationParams { xml.NetworkConnectionSection( 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1', 'type' => 'application/vnd.vmware.vcloud.networkConnectionSection+xml', 'href' => "#{@api_url}/vAppTemplate/vm-#{vm_id}/networkConnectionSection/") { xml['ovf'].Info 'Network config for sourced item' xml.PrimaryNetworkConnectionIndex '0' xml.NetworkConnection('network' => network_config[:name]) { xml.NetworkConnectionIndex '0' xml.IsConnected 'true' xml.IpAddressAllocationMode(network_config[:ip_allocation_mode] || 'POOL') } } } xml.NetworkAssignment('containerNetwork' => network_config[:name], 'innerNetwork' => network_config[:name]) } end xml.AllEULAsAccepted 'true' } end params = { 'method' => :post, 'command' => "/vdc/#{vdc}/action/composeVApp" } response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.composeVAppParams+xml' ) vapp_id = URI(headers['Location']).path.gsub("/api/vApp/vapp-", '') task = response.css("VApp Task[operationName='vdcComposeVapp']").first task_id = URI(task['href']).path.gsub('/api/task/', '') { :vapp_id => vapp_id, :task_id => task_id } end ## # Recompose an existing vapp using existing virtual machines # # Params: # - vdc: the associated VDC # - vapp_name: name of the target vapp # - vapp_description: description of the target vapp # - vm_list: hash with IDs of the VMs to be used in the composing process # - network_config: hash of the network configuration for the vapp def recompose_vapp_from_vm(vapp_id, vm_list = {}, network_config = {}) original_vapp = get_vapp(vapp_id) builder = Nokogiri::XML::Builder.new do |xml| xml.RecomposeVAppParams( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1', 'name' => original_vapp[:name]) { xml.Description original_vapp[:description] xml.InstantiationParams {} vm_list.each do |vm_name, vm_id| xml.SourcedItem { xml.Source('href' => "#{@api_url}/vAppTemplate/vm-#{vm_id}", 'name' => vm_name) xml.InstantiationParams { xml.NetworkConnectionSection( 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1', 'type' => 'application/vnd.vmware.vcloud.networkConnectionSection+xml', 'href' => "#{@api_url}/vAppTemplate/vm-#{vm_id}/networkConnectionSection/") { xml['ovf'].Info 'Network config for sourced item' xml.PrimaryNetworkConnectionIndex '0' xml.NetworkConnection('network' => network_config[:name]) { xml.NetworkConnectionIndex '0' xml.IsConnected 'true' xml.IpAddressAllocationMode(network_config[:ip_allocation_mode] || 'POOL') } } } xml.NetworkAssignment('containerNetwork' => network_config[:name], 'innerNetwork' => network_config[:name]) } end xml.AllEULAsAccepted 'true' } end params = { 'method' => :post, 'command' => "/vApp/vapp-#{vapp_id}/action/recomposeVApp" } response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.recomposeVAppParams+xml' ) vapp_id = URI(headers['Location']).path.gsub('/api/vApp/vapp-', '') task = response.css("Task [operationName='vdcRecomposeVapp']").first task_id = URI(task['href']).path.gsub('/api/task/', '') { :vapp_id => vapp_id, :task_id => task_id } end # Fetch details about a given vapp template: # - name # - description # - Children VMs: # -- ID def get_vapp_template(vapp_id) params = { 'method' => :get, 'command' => "/vAppTemplate/vappTemplate-#{vapp_id}" } response, _headers = send_request(params) vapp_node = response.css('VAppTemplate').first if vapp_node name = vapp_node['name'] convert_vapp_status(vapp_node['status']) end description = response.css('Description').first description = description.text unless description.nil? # FIXME: What are those 2 lines for ? disabling for now (tsugliani) # ip = response.css('IpAddress').first # ip = ip.text unless ip.nil? vms = response.css('Children Vm') vms_hash = {} vms.each do |vm| vms_hash[vm['name']] = { :id => URI(vm['href']).path.gsub('/api/vAppTemplate/vm-', '') } end # TODO: EXPAND INFO FROM RESPONSE { :name => name, :description => description, :vms_hash => vms_hash } end ## # Set vApp port forwarding rules # # - vapp_id: id of the vapp to be modified # - network_name: name of the vapp network to be modified # - config: hash with network configuration specifications, must contain an array inside :nat_rules with the nat rules to be applied. def set_vapp_port_forwarding_rules(vapp_id, network_name, config = {}) builder = Nokogiri::XML::Builder.new do |xml| xml.NetworkConfigSection( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1') { xml['ovf'].Info 'Network configuration' xml.NetworkConfig('networkName' => network_name) { xml.Configuration { xml.ParentNetwork('href' => "#{@api_url}/network/#{config[:parent_network]}") xml.FenceMode(config[:fence_mode] || 'isolated') xml.Features { xml.NatService { xml.IsEnabled 'true' xml.NatType 'portForwarding' xml.Policy(config[:nat_policy_type] || 'allowTraffic') config[:nat_rules].each do |nat_rule| xml.NatRule { xml.VmRule { xml.ExternalPort nat_rule[:nat_external_port] xml.VAppScopedVmId nat_rule[:vapp_scoped_local_id] xml.VmNicId(nat_rule[:nat_vmnic_id] || '0') xml.InternalPort nat_rule[:nat_internal_port] xml.Protocol(nat_rule[:nat_protocol] || 'TCP') } } end } } } } } end params = { 'method' => :put, 'command' => "/vApp/vapp-#{vapp_id}/networkConfigSection" } _response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.networkConfigSection+xml' ) task_id = URI(headers['Location']).path.gsub("/api/task/", '') task_id end ## # Add vApp port forwarding rules # # - vapp_id: id of the vapp to be modified # - network_name: name of the vapp network to be modified # - config: hash with network configuration specifications, # must contain an array inside :nat_rules with the nat rules to add. # nat_rules << { # :nat_external_port => j.to_s, # :nat_internal_port => "22", # :nat_protocol => "TCP", # :vm_scoped_local_id => value[:vapp_scoped_local_id] # } def add_vapp_port_forwarding_rules(vapp_id, network_name, config = {}) builder = Nokogiri::XML::Builder.new do |xml| xml.NetworkConfigSection( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1') { xml['ovf'].Info 'Network configuration' xml.NetworkConfig('networkName' => network_name) { xml.Configuration { xml.ParentNetwork('href' => "#{@api_url}/network/#{config[:parent_network]}") xml.FenceMode(config[:fence_mode] || 'isolated') xml.Features { xml.NatService { xml.IsEnabled 'true' xml.NatType 'portForwarding' xml.Policy(config[:nat_policy_type] || 'allowTraffic') pre_existing = get_vapp_port_forwarding_rules(vapp_id) config[:nat_rules].concat(pre_existing) config[:nat_rules].each do |nat_rule| xml.NatRule { xml.VmRule { xml.ExternalPort nat_rule[:nat_external_port] xml.VAppScopedVmId nat_rule[:vapp_scoped_local_id] xml.VmNicId(nat_rule[:nat_vmnic_id] || '0') xml.InternalPort nat_rule[:nat_internal_port] xml.Protocol(nat_rule[:nat_protocol] || 'TCP') } } end } } } } } end params = { 'method' => :put, 'command' => "/vApp/vapp-#{vapp_id}/networkConfigSection" } _response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.networkConfigSection+xml' ) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Get vApp port forwarding rules # # - vapp_id: id of the vApp def get_vapp_port_forwarding_rules(vapp_id) params = { 'method' => :get, 'command' => "/vApp/vapp-#{vapp_id}/networkConfigSection" } response, _headers = send_request(params) # FIXME: this will return nil if the vApp uses multiple vApp Networks # with Edge devices in natRouted/portForwarding mode. config = response.css( 'NetworkConfigSection/NetworkConfig/Configuration' ) fence_mode = config.css('/FenceMode').text nat_type = config.css('/Features/NatService/NatType').text unless fence_mode == 'natRouted' raise InvalidStateError, 'Invalid request because FenceMode must be natRouted.' end unless nat_type == 'portForwarding' raise InvalidStateError, 'Invalid request because NatType must be portForwarding.' end nat_rules = [] config.css('/Features/NatService/NatRule').each do |rule| # portforwarding rules information vm_rule = rule.css('VmRule') nat_rules << { :nat_external_ip => vm_rule.css('ExternalIpAddress').text, :nat_external_port => vm_rule.css('ExternalPort').text, :vapp_scoped_local_id => vm_rule.css('VAppScopedVmId').text, :vm_nic_id => vm_rule.css('VmNicId').text, :nat_internal_port => vm_rule.css('InternalPort').text, :nat_protocol => vm_rule.css('Protocol').text } end nat_rules end ## # Find an edge gateway id from the edge name and vdc_id # # - edge_gateway_name: Name of the vSE # - vdc_id: virtual datacenter id # def find_edge_gateway_id(edge_gateway_name, vdc_id) params = { 'method' => :get, 'command' => '/query?type=edgeGateway&' \ 'format=records&' \ "filter=vdc==#{@api_url}/vdc/#{vdc_id}&" + "filter=name==#{edge_gateway_name}" } response, _headers = send_request(params) edge_gateway = response.css('EdgeGatewayRecord').first if edge_gateway return URI(edge_gateway['href']).path.gsub( '/api/admin/edgeGateway/', '' ) else return nil end end ## # Redeploy the vShield Edge Gateway VM, due to some knowns issues # where the current rules are not "applied" and the EdgeGW is in an # unmanageable state. # def redeploy_edge_gateway(edge_gateway_id) params = { 'method' => :post, 'command' => "/admin/edgeGateway/#{edge_gateway_id}/action/redeploy" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Find an edge gateway network from the edge name and vdc_id, and ip # # - edge_gateway_name: Name of the vSE # - vdc_id: virtual datacenter id # - edge_gateway_ip: public ip associated to that vSE # def find_edge_gateway_network(edge_gateway_name, vdc_id, edge_gateway_ip) params = { 'method' => :get, 'command' => '/query?type=edgeGateway&' \ 'format=records&' \ "filter=vdc==#{@api_url}/vdc/#{vdc_id}&" + "filter=name==#{edge_gateway_name}" } response, _headers = send_request(params) edge_gateway = response.css('EdgeGatewayRecord').first if edge_gateway edge_gateway_id = URI(edge_gateway['href']).path.gsub( '/api/admin/edgeGateway/', '' ) end params = { 'method' => :get, 'command' => "/admin/edgeGateway/#{edge_gateway_id}" } response, _headers = send_request(params) response.css( 'EdgeGateway Configuration GatewayInterfaces GatewayInterface' ).each do |gw| # Only check uplinks, avoid another check if gw.css('InterfaceType').text == 'uplink' # Loop on all sub-allocation pools gw.css('SubnetParticipation IpRanges IpRange').each do |cur_range| low_ip = cur_range.css('StartAddress').first.text high_ip = cur_range.css('EndAddress').first.text range_ip_low = NetAddr.ip_to_i(low_ip) range_ip_high = NetAddr.ip_to_i(high_ip) test_ip = NetAddr.ip_to_i(edge_gateway_ip) # FIXME: replace "===" (tsugliani) if (range_ip_low..range_ip_high) === test_ip return gw.css('Network').first[:href] end end end end end ## # Get Org Edge port forwarding and firewall rules # # - vapp_id: id of the vapp to be modified # - network_name: name of the vapp network to be modified # - config: hash with network configuration specifications, # must contain an array inside :nat_rules with the nat rules # to be applied. def get_edge_gateway_rules(edge_gateway_name, vdc_id) edge_gateway_id = find_edge_gateway_id(edge_gateway_name, vdc_id) params = { 'method' => :get, 'command' => "/admin/edgeGateway/#{edge_gateway_id}" } response, _headers = send_request(params) nat_fw_rules = [] interesting = response.css( 'EdgeGateway Configuration EdgeGatewayServiceConfiguration' ) interesting.css('NatService NatRule').each do |node| if node.css('RuleType').text == 'DNAT' gw_node = node.css('GatewayNatRule') nat_fw_rules << { :rule_type => 'DNAT', :original_ip => gw_node.css('OriginalIp').text, :original_port => gw_node.css('OriginalPort').text, :translated_ip => gw_node.css('TranslatedIp').text, :translated_port => gw_node.css('TranslatedPort').text, :protocol => gw_node.css('Protocol').text, :is_enabled => node.css('IsEnabled').text } end if node.css('RuleType').text == 'SNAT' gw_node = node.css('GatewayNatRule') nat_fw_rules << { :rule_type => 'SNAT', :interface_name => gw_node.css('Interface').first['name'], :original_ip => gw_node.css('OriginalIp').text, :translated_ip => gw_node.css('TranslatedIp').text, :is_enabled => node.css('IsEnabled').text } end end interesting.css('FirewallService FirewallRule').each do |node| if node.css('Port').text == '-1' nat_fw_rules << { :rule_type => 'Firewall', :id => node.css('Id').text, :policy => node.css('Policy').text, :description => node.css('Description').text, :destination_ip => node.css('DestinationIp').text, :destination_portrange => node.css('DestinationPortRange').text, :source_ip => node.css('SourceIp').text, :source_portrange => node.css('SourcePortRange').text, :is_enabled => node.css('IsEnabled').text } end end nat_fw_rules end ## # Remove NAT/FW rules from a edge gateway device # # - edge_gateway_name: Name of the vSE # - vdc_id: virtual datacenter id # - edge_gateway_ip: public ip associated the vSE # - vapp_id: vApp identifier to correlate with the vApp Edge def remove_edge_gateway_rules(edge_gateway_name, vdc_id, edge_gateway_ip, vapp_id) edge_vapp_ip = get_vapp_edge_public_ip(vapp_id) edge_gateway_id = find_edge_gateway_id(edge_gateway_name, vdc_id) params = { 'method' => :get, 'command' => "/admin/edgeGateway/#{edge_gateway_id}" } response, _headers = send_request(params) interesting = response.css( 'EdgeGateway Configuration EdgeGatewayServiceConfiguration' ) interesting.css('NatService NatRule').each do |node| if node.css('RuleType').text == 'DNAT' && node.css('GatewayNatRule/OriginalIp').text == edge_gateway_ip && node.css('GatewayNatRule/TranslatedIp').text == edge_vapp_ip node.remove end if node.css('RuleType').text == 'SNAT' && node.css('GatewayNatRule/OriginalIp').text == edge_vapp_ip && node.css('GatewayNatRule/TranslatedIp').text == edge_gateway_ip node.remove end end interesting.css('FirewallService FirewallRule').each do |node| if node.css('Port').text == '-1' && node.css('DestinationIp').text == edge_gateway_ip && node.css('DestinationPortRange').text == 'Any' node.remove end end builder = Nokogiri::XML::Builder.new builder << interesting remove_edge_rules = Nokogiri::XML(builder.to_xml) xml = remove_edge_rules.at_css 'EdgeGatewayServiceConfiguration' xml['xmlns'] = 'http://www.vmware.com/vcloud/v1.5' params = { 'method' => :post, 'command' => "/admin/edgeGateway/#{edge_gateway_id}/action/" + 'configureServices' } _response, headers = send_request( params, remove_edge_rules.to_xml, 'application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml' ) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end # # Add Org Edge port forwarding and firewall rules # # - vapp_id: id of the vapp to be modified # - network_name: name of the vapp network to be modified # - ports: array with port numbers to forward 1:1 to vApp. def add_edge_gateway_rules(edge_gateway_name, vdc_id, edge_gateway_ip, vapp_id, ports) edge_vapp_ip = get_vapp_edge_public_ip(vapp_id) edge_network_id = find_edge_gateway_network( edge_gateway_name, vdc_id, edge_gateway_ip ) edge_gateway_id = find_edge_gateway_id(edge_gateway_name, vdc_id) ### FIXME: tsugliani # We need to check the previous variables, especially (edge_*) # which can fail in some *weird* situations. params = { 'method' => :get, 'command' => "/admin/edgeGateway/#{edge_gateway_id}" } response, _headers = send_request(params) interesting = response.css( 'EdgeGateway Configuration EdgeGatewayServiceConfiguration' ) add_snat_rule = true interesting.css('NatService NatRule').each do |node| if node.css('RuleType').text == 'DNAT' && node.css('GatewayNatRule/OriginalIp').text == edge_gateway_ip && node.css('GatewayNatRule/TranslatedIp').text == edge_vapp_ip && node.css('GatewayNatRule/OriginalPort').text == 'any' # remove old DNAT rule any -> any from older vagrant-vcloud versions node.remove end if node.css('RuleType').text == 'SNAT' && node.css('GatewayNatRule/OriginalIp').text == edge_vapp_ip && node.css('GatewayNatRule/TranslatedIp').text == edge_gateway_ip add_snat_rule = false end end add_firewall_rule = true interesting.css('FirewallService FirewallRule').each do |node| if node.css('Port').text == '-1' && node.css('DestinationIp').text == edge_gateway_ip && node.css('DestinationPortRange').text == 'Any' add_firewall_rule = false end end builder = Nokogiri::XML::Builder.new builder << interesting set_edge_rules = Nokogiri::XML(builder.to_xml) do |config| config.default_xml.noblanks end nat_rules = set_edge_rules.at_css('NatService') # Add all DNAT port rules edge -> vApp for the given list ports.each do |port| nat_rule = Nokogiri::XML::Builder.new do |xml| xml.NatRule { xml.RuleType 'DNAT' xml.IsEnabled 'true' xml.GatewayNatRule { xml.Interface('href' => edge_network_id ) xml.OriginalIp edge_gateway_ip xml.OriginalPort port xml.TranslatedIp edge_vapp_ip xml.TranslatedPort port xml.Protocol 'tcpudp' } } end nat_rules << nat_rule.doc.root.to_xml end if (add_snat_rule) snat_rule = Nokogiri::XML::Builder.new do |xml| xml.NatRule { xml.RuleType 'SNAT' xml.IsEnabled 'true' xml.GatewayNatRule { xml.Interface('href' => edge_network_id ) xml.OriginalIp edge_vapp_ip xml.TranslatedIp edge_gateway_ip xml.Protocol 'any' } } end nat_rules << snat_rule.doc.root.to_xml end if (add_firewall_rule) firewall_rule_1 = Nokogiri::XML::Builder.new do |xml| xml.FirewallRule { xml.IsEnabled 'true' xml.Description 'Allow Vagrant Communications' xml.Policy 'allow' xml.Protocols { xml.Any 'true' } xml.DestinationPortRange 'Any' xml.DestinationIp edge_gateway_ip xml.SourcePortRange 'Any' xml.SourceIp 'Any' xml.EnableLogging 'false' } end fw_rules = set_edge_rules.at_css('FirewallService') fw_rules << firewall_rule_1.doc.root.to_xml end xml = set_edge_rules.at_css 'EdgeGatewayServiceConfiguration' xml['xmlns'] = 'http://www.vmware.com/vcloud/v1.5' params = { 'method' => :post, 'command' => "/admin/edgeGateway/#{edge_gateway_id}/action/" + 'configureServices' } _response, headers = send_request( params, set_edge_rules.to_xml, 'application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml' ) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # get vApp edge public IP from the vApp ID # Only works when: # - vApp needs to be poweredOn # - FenceMode is set to "natRouted" # - NatType" is set to "portForwarding # This will be required to know how to connect to VMs behind the Edge # device. def get_vapp_edge_public_ip(vapp_id) return @cached_vapp_edge_public_ips[vapp_id] unless @cached_vapp_edge_public_ips[vapp_id].nil? # Check the network configuration section params = { 'method' => :get, 'command' => "/vApp/vapp-#{vapp_id}/networkConfigSection" } response, _headers = send_request(params) # FIXME: this will return nil if the vApp uses multiple vApp Networks # with Edge devices in natRouted/portForwarding mode. config = response.css( 'NetworkConfigSection/NetworkConfig/Configuration' ) fence_mode = config.css('/FenceMode').text nat_type = config.css('/Features/NatService/NatType').text unless fence_mode == 'natRouted' raise InvalidStateError, 'Invalid request because FenceMode must be natRouted.' end unless nat_type == 'portForwarding' raise InvalidStateError, 'Invalid request because NatType must be portForwarding.' end # Check the routerInfo configuration where the global external IP # is defined edge_ip = config.css('/RouterInfo/ExternalIp').text if edge_ip == '' return nil else @cached_vapp_edge_public_ips[vapp_id] = edge_ip return edge_ip end end ## # Upload an OVF package # - vdc_id # - vappName # - vappDescription # - ovfFile # - catalogId # - uploadOptions {} def upload_ovf(vdc_id, vapp_name, vapp_description, ovf_file, catalog_id, upload_options = {}) # if send_manifest is not set, setting it true if upload_options[:send_manifest].nil? || upload_options[:send_manifest] upload_manifest = 'true' else upload_manifest = 'false' end builder = Nokogiri::XML::Builder.new do |xml| xml.UploadVAppTemplateParams( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1', 'manifestRequired' => upload_manifest, 'name' => vapp_name) { xml.Description vapp_description } end params = { 'method' => :post, 'command' => "/vdc/#{vdc_id}/action/uploadVAppTemplate" } @logger.debug('Sending uploadVAppTemplate request...') response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.uploadVAppTemplateParams+xml' ) # Get vAppTemplate Link from location vapp_template = URI(headers['Location']).path.gsub( '/api/vAppTemplate/vappTemplate-', '' ) @logger.debug("Getting vAppTemplate ID: #{vapp_template}") descriptor_upload = URI(response.css( "Files Link [rel='upload:default']" ).first[:href]).path.gsub('/transfer/', '') transfer_guid = descriptor_upload.gsub('/descriptor.ovf', '') ovf_file_basename = File.basename(ovf_file, '.ovf') ovf_dir = File.dirname(ovf_file) # Send OVF Descriptor @logger.debug('Sending OVF Descriptor...') upload_url = "/transfer/#{descriptor_upload}" upload_filename = "#{ovf_dir}/#{ovf_file_basename}.ovf" upload_file( upload_url, upload_filename, vapp_template, upload_options ) # Begin the catch for upload interruption begin params = { 'method' => :get, 'command' => "/vAppTemplate/vappTemplate-#{vapp_template}" } response, _headers = send_request(params) task = response.css( "VAppTemplate Task[operationName='vdcUploadOvfContents']" ).first task_id = URI(task['href']).path.gsub('/api/task/', '') # Loop to wait for the upload links to show up in the vAppTemplate # we just created @logger.debug( 'Waiting for the upload links to show up in the vAppTemplate ' \ 'we just created.' ) while true response, _headers = send_request(params) @logger.debug('Request...') break unless response.css("Files Link [rel='upload:default']").count == 1 sleep 1 end if upload_manifest == 'true' upload_url = "/transfer/#{transfer_guid}/descriptor.mf" upload_filename = "#{ovf_dir}/#{ovf_file_basename}.mf" upload_file( upload_url, upload_filename, vapp_template, upload_options ) end # Start uploading OVF VMDK files params = { 'method' => :get, 'command' => "/vAppTemplate/vappTemplate-#{vapp_template}" } response, _headers = send_request(params) response.css( "Files File [bytesTransferred='0'] Link [rel='upload:default']" ).each do |file| file_name = URI(file[:href]).path.gsub("/transfer/#{transfer_guid}/", '') upload_filename = "#{ovf_dir}/#{file_name}" upload_url = "/transfer/#{transfer_guid}/#{file_name}" upload_file( upload_url, upload_filename, vapp_template, upload_options ) end # Add item to the catalog catalog_id builder = Nokogiri::XML::Builder.new do |xml| xml.CatalogItem( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'type' => 'application/vnd.vmware.vcloud.catalogItem+xml', 'name' => vapp_name) { xml.Description vapp_description xml.Entity( 'href' => "#{@api_url}/vAppTemplate/" + "vappTemplate-#{vapp_template}" ) } end params = { 'method' => :post, 'command' => "/catalog/#{catalog_id}/catalogItems" } # No debug here (tsugliani) _response, _headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.catalogItem+xml' ) task_id ###### rescue Exception => e puts "Exception detected: #{e.message}." puts 'Aborting task...' # Get vAppTemplate Task params = { 'method' => :get, 'command' => "/vAppTemplate/vappTemplate-#{vapp_template}" } response, _headers = send_request(params) # Cancel Task cancel_hook = URI(response.css( "Tasks Task Link [rel='task:cancel']" ).first[:href]).path.gsub('/api', '') params = { 'method' => :post, 'command' => cancel_hook } # No debug here (tsugliani) _response, _headers = send_request(params) raise end end ## # Fetch information for a given task def get_task(task_id) params = { 'method' => :get, 'command' => "/task/#{task_id}" } response, _headers = send_request(params) task = response.css('Task').first status = task['status'] start_time = task['startTime'] end_time = task['endTime'] { :status => status, :start_time => start_time, :end_time => end_time, :response => response } end ## # Poll a given task until completion def wait_task_completion(task_id) task, errormsg = nil loop do task = get_task(task_id) # @logger.debug( # "Evaluating taskid: #{task_id}, current status #{task[:status]}" # ) break if !['queued','preRunning','running'].include?(task[:status]) sleep 5 end if task[:status] == 'error' @logger.debug('Task Error') errormsg = task[:response].css('Error').first @logger.debug( "Task Error Message #{errormsg['majorErrorCode']} - " + "#{errormsg['message']}" ) errormsg = "Error code #{errormsg['majorErrorCode']} - #{errormsg['message']}" end { :status => task[:status], :errormsg => errormsg, :start_time => task[:start_time], :end_time => task[:end_time] } end ## # Set vApp Network Config def set_vapp_network_config(vapp_id, network_name, config = {}) builder = Nokogiri::XML::Builder.new do |xml| xml.NetworkConfigSection( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1' ) { xml['ovf'].Info 'Network configuration' xml.NetworkConfig('networkName' => network_name) { xml.Configuration { xml.FenceMode(config[:fence_mode] || 'isolated') xml.RetainNetInfoAcrossDeployments(config[:retain_net] || false) xml.ParentNetwork('href' => config[:parent_network]) } } } end params = { 'method' => :put, 'command' => "/vApp/vapp-#{vapp_id}/networkConfigSection" } _response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.networkConfigSection+xml' ) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Set VM Network Config def set_vm_network_config(vm_id, network_name, config = {}) builder = Nokogiri::XML::Builder.new do |xml| xml.NetworkConnectionSection( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1') { xml['ovf'].Info 'VM Network configuration' xml.PrimaryNetworkConnectionIndex(config[:primary_index] || 0) xml.NetworkConnection( 'network' => network_name, 'needsCustomization' => true ) { xml.NetworkConnectionIndex(config[:network_index] || 0) xml.IpAddress config[:ip] if config[:ip] xml.IsConnected(config[:is_connected] || true) xml.IpAddressAllocationMode config[:ip_allocation_mode] if config[:ip_allocation_mode] } } end params = { 'method' => :put, 'command' => "/vApp/vm-#{vm_id}/networkConnectionSection" } _response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.networkConnectionSection+xml' ) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Set VM Guest Customization Config def set_vm_guest_customization(vm_id, computer_name, config = {}) builder = Nokogiri::XML::Builder.new do |xml| xml.GuestCustomizationSection( 'xmlns' => 'http://www.vmware.com/vcloud/v1.5', 'xmlns:ovf' => 'http://schemas.dmtf.org/ovf/envelope/1') { xml['ovf'].Info 'VM Guest Customization configuration' xml.Enabled config[:enabled] if config[:enabled] xml.AdminPasswordEnabled config[:admin_passwd_enabled] if config[:admin_passwd_enabled] xml.AdminPassword config[:admin_passwd] if config[:admin_passwd] xml.ComputerName computer_name } end params = { 'method' => :put, 'command' => "/vApp/vm-#{vm_id}/guestCustomizationSection" } _response, headers = send_request( params, builder.to_xml, 'application/vnd.vmware.vcloud.guestCustomizationSection+xml' ) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end # Enable VM Nested Hardware-Assisted Virtualization def set_vm_nested_hypervisor(vm_id, enable) action = enable ? "enable" : "disable" params = { 'method' => :post, 'command' => "/vApp/vm-#{vm_id}/action/#{action}NestedHypervisor" } _response, headers = send_request(params) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id end ## # Set memory and number of cpus in virtualHardwareSection of a given vm # returns task_id or nil if there is no task to wait for def set_vm_hardware(vm_id, cfg) params = { 'method' => :get, 'command' => "/vApp/vm-#{vm_id}/virtualHardwareSection" } changed = false response, _headers = send_request(params) response.css('ovf|Item').each do |item| type = item.css('rasd|ResourceType').first if type.content == '3' # cpus if cfg.cpus if item.at_css('rasd|VirtualQuantity').content != cfg.cpus.to_s item.at_css('rasd|VirtualQuantity').content = cfg.cpus item.at_css('rasd|ElementName').content = "#{cfg.cpus} virtual CPU(s)" changed = true end end elsif type.content == '4' # memory if cfg.memory if item.at_css('rasd|VirtualQuantity').content != cfg.memory.to_s item.at_css('rasd|VirtualQuantity').content = cfg.memory item.at_css('rasd|ElementName').content = "#{cfg.memory} MB of memory" changed = true end end end end if changed params = { 'method' => :put, 'command' => "/vApp/vm-#{vm_id}/virtualHardwareSection" } _response, headers = send_request( params, response.to_xml, 'application/vnd.vmware.vcloud.virtualhardwaresection+xml' ) task_id = URI(headers['Location']).path.gsub('/api/task/', '') task_id else return nil end end ## # Fetch details about a given VM def get_vm(vm_id) params = { 'method' => :get, 'command' => "/vApp/vm-#{vm_id}" } response, _headers = send_request(params) os_desc = response.css( 'ovf|OperatingSystemSection ovf|Description' ).first.text networks = {} response.css('NetworkConnection').each do |network| ip = network.css('IpAddress').first ip = ip.text if ip networks[network['network']] = { :index => network.css( 'NetworkConnectionIndex' ).first.text, :ip => ip, :is_connected => network.css( 'IsConnected' ).first.text, :mac_address => network.css( 'MACAddress' ).first.text, :ip_allocation_mode => network.css( 'IpAddressAllocationMode' ).first.text } end admin_password = response.css( 'GuestCustomizationSection AdminPassword' ).first admin_password = admin_password.text if admin_password # make the lines shorter by adjusting the nokogiri css namespace guest_css = response.css('GuestCustomizationSection') guest_customizations = { :enabled => guest_css.css('Enabled').first.text, :admin_passwd_enabled => guest_css.css( 'AdminPasswordEnabled' ).first.text, :admin_passwd_auto => guest_css.css( 'AdminPasswordAuto' ).first.text, :admin_passwd => admin_password, :reset_passwd_required => guest_css.css( 'ResetPasswordRequired' ).first.text, :computer_name => guest_css.css('ComputerName').first.text } { :os_desc => os_desc, :networks => networks, :guest_customizations => guest_customizations } end end # Class Version 5.1 end # Module Driver end # Module VCloud end # Module VagrantPlugins