<commit_before>#
#  Copyright 2012 <NAME>
#  Copyright 2013 <NAME> and <NAME>
#
#  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'

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(host, username, password, org_name)
          @logger = Log4r::Logger.new('vagrant::provider::vcloud::driver_5_1')

          @host = host
          @api_url = "#{host}/api"
          @host_url = "#{host}"
          @username = username
          @password = password
          @org_name = org_name
          @api_version = '5.1'
          @id = nil
        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']] = org['href'].gsub("#{@api_url}/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']] = item['href'].gsub(
              "#{@api_url}/catalog/", ''
            )
          end

          vdcs = {}
          response.css(
            "Link[type='application/vnd.vmware.vcloud.vdc+xml']"
          ).each do |item|
            vdcs[item['name']] = item['href'].gsub(
              "#{@api_url}/vdc/", ''
            )
          end

          networks = {}
          response.css(
            "Link[type='application/vnd.vmware.vcloud.orgNetwork+xml']"
          ).each do |item|
            networks[item['name']] = item['href'].gsub(
              "#{@api_url}/network/", ''
            )
          end

          tasklists = {}
          response.css(
            "Link[type='application/vnd.vmware.vcloud.tasksList+xml']"
          ).each do |item|
            tasklists[item['name']] = item['href'].gsub(
              "#{@api_url}/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']] = item['href'].gsub(
              "#{@api_url}/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

          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']] = item['href'].gsub(
              "#{@api_url}/vApp/vapp-", ''
            )
          end

          networks = {}
          response.css(
            "Network[type='application/vnd.vmware.vcloud.network+xml']"
          ).each do |item|
            networks[item['name']] = item['href'].gsub(
              "#{@api_url}/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']] = item['href'].gsub(
              "#{@api_url}/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 = vm_elem['href'].gsub("#{@api_url}/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                   => vm['href'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = response.css(
            "AdminCatalog Tasks Task[operationName='catalogCreateCatalog']"
          ).first[:href].gsub("#{@api_url}/task/", '')

          catalog_id = response.css(
            "AdminCatalog Link [type='application/vnd.vmware.vcloud.catalog+xml']"
            ).first[:href].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/vApp/vapp-", '')

          task = response.css(
            "VApp Task[operationName='vdcInstantiateVapp']"
          ).first

          task_id = task['href'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/vApp/vapp-", '')

          task = response.css("VApp Task[operationName='vdcComposeVapp']").first
          task_id = task['href'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/vApp/vapp-", '')

          task = response.css("Task [operationName='vdcRecomposeVapp']").first
          task_id = task['href'].gsub("#{@api_url}/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 => vm['href'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 edge_gateway['href'].gsub(
              "#{@api_url}/admin/edgeGateway/", ''
            )
          else
            return nil
          end
        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 = edge_gateway['href'].gsub(
              "#{@api_url}/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

        ##
        # Set 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 set_edge_gateway_rules(edge_gateway_name, vdc_id, edge_gateway_ip, vapp_id)
          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'
          )

          nat_rule_1 = Nokogiri::XML::Node.new 'NatRule', response
          rule_type = Nokogiri::XML::Node.new 'RuleType', response
          rule_type.content = 'DNAT'
          nat_rule_1.add_child rule_type

          is_enabled = Nokogiri::XML::Node.new 'IsEnabled', response
          is_enabled.content = 'true'
          nat_rule_1.add_child is_enabled

          gateway_nat_rule = Nokogiri::XML::Node.new 'GatewayNatRule',
                                                     response
          nat_rule_1.add_child gateway_nat_rule

          interface = Nokogiri::XML::Node.new 'Interface', response
          interface['href'] = edge_network_id

          gateway_nat_rule.add_child interface

          original_ip = Nokogiri::XML::Node.new 'OriginalIp', response
          original_ip.content = edge_gateway_ip
          gateway_nat_rule.add_child original_ip

          original_port = Nokogiri::XML::Node.new 'OriginalPort', response
          original_port.content = 'any'
          gateway_nat_rule.add_child original_port

          translated_ip = Nokogiri::XML::Node.new 'TranslatedIp', response
          translated_ip.content = edge_vapp_ip
          gateway_nat_rule.add_child translated_ip

          translated_port = Nokogiri::XML::Node.new 'TranslatedPort',
                                                    response
          translated_port.content = 'any'
          gateway_nat_rule.add_child translated_port

          protocol = Nokogiri::XML::Node.new 'Protocol', response
          protocol.content = 'any'
          gateway_nat_rule.add_child protocol

          # FIXME: frapposelli/tsugliani we should be able to remove this
          # FIXME: test this against a vCloud Director 5.1.x installation
          # icmpSubType = Nokogiri::XML::Node.new 'IcmpSubType', response
          # icmpSubType.content = "any"
          # gatewayNatRule.add_child icmpSubType

          nat_rule_2 = Nokogiri::XML::Node.new 'NatRule', response

          rule_type = Nokogiri::XML::Node.new 'RuleType', response
          rule_type.content = 'SNAT'
          nat_rule_2.add_child rule_type

          is_enabled = Nokogiri::XML::Node.new 'IsEnabled', response
          is_enabled.content = 'true'
          nat_rule_2.add_child is_enabled

          gateway_nat_rule = Nokogiri::XML::Node.new 'GatewayNatRule',
                                                     response
          nat_rule_2.add_child gateway_nat_rule

          interface = Nokogiri::XML::Node.new 'Interface', response
          interface['href'] = edge_network_id

          gateway_nat_rule.add_child interface

          original_ip = Nokogiri::XML::Node.new 'OriginalIp', response
          original_ip.content = edge_vapp_ip
          gateway_nat_rule.add_child original_ip

          translated_ip = Nokogiri::XML::Node.new 'TranslatedIp', response
          translated_ip.content = edge_gateway_ip
          gateway_nat_rule.add_child translated_ip

          protocol = Nokogiri::XML::Node.new 'Protocol', response
          protocol.content = 'any'
          gateway_nat_rule.add_child protocol

          firewall_rule_1 = Nokogiri::XML::Node.new 'FirewallRule', response

          is_enabled = Nokogiri::XML::Node.new 'IsEnabled', response
          is_enabled.content = 'true'
          firewall_rule_1.add_child is_enabled

          description = Nokogiri::XML::Node.new 'Description', response
          description.content = 'Allow Vagrant Communications'
          firewall_rule_1.add_child description

          policy = Nokogiri::XML::Node.new 'Policy', response
          policy.content = 'allow'
          firewall_rule_1.add_child policy

          protocols = Nokogiri::XML::Node.new 'Protocols', response
          firewall_rule_1.add_child protocols

          any = Nokogiri::XML::Node.new 'Any', response
          any.content = 'true'
          protocols.add_child any

          destination_port_range =
          Nokogiri::XML::Node.new 'DestinationPortRange', response
          destination_port_range.content = 'Any'
          firewall_rule_1.add_child destination_port_range

          destination_ip = Nokogiri::XML::Node.new 'DestinationIp', response
          destination_ip.content = edge_gateway_ip
          firewall_rule_1.add_child destination_ip
          source_port_range = Nokogiri::XML::Node.new 'SourcePortRange',

                                                      response
          source_port_range.content = 'Any'
          firewall_rule_1.add_child source_port_range

          source_ip = Nokogiri::XML::Node.new 'SourceIp', response
          source_ip.content = 'Any'
          firewall_rule_1.add_child source_ip

          enable_logging = Nokogiri::XML::Node.new 'EnableLogging', response
          enable_logging.content = 'false'
          firewall_rule_1.add_child enable_logging

          # FIXME: Think about adding an outgoing rule
          #        (which could not be available)
          #        by default in all cases vapp_edge_ip -> any
          #        Especially for provisionners using apt-get, internet, etc...
          #        (tsugliani)

          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')
          nat_rules << nat_rule_1
          nat_rules << nat_rule_2

          fw_rules = set_edge_rules.at_css('FirewallService')
          fw_rules << firewall_rule_1

          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 = headers['Location'].gsub("#{@api_url}/task/", '')
          task_id
        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',<commit_msg>removed set_edge_gateway_rules, no longer needed<commit_after>#
#  Copyright 2012 <NAME>
#  Copyright 2013 <NAME> and <NAME>
#
#  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'

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(host, username, password, org_name)
          @logger = Log4r::Logger.new('vagrant::provider::vcloud::driver_5_1')

          @host = host
          @api_url = "#{host}/api"
          @host_url = "#{host}"
          @username = username
          @password = password
          @org_name = org_name
          @api_version = '5.1'
          @id = nil
        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']] = org['href'].gsub("#{@api_url}/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']] = item['href'].gsub(
              "#{@api_url}/catalog/", ''
            )
          end

          vdcs = {}
          response.css(
            "Link[type='application/vnd.vmware.vcloud.vdc+xml']"
          ).each do |item|
            vdcs[item['name']] = item['href'].gsub(
              "#{@api_url}/vdc/", ''
            )
          end

          networks = {}
          response.css(
            "Link[type='application/vnd.vmware.vcloud.orgNetwork+xml']"
          ).each do |item|
            networks[item['name']] = item['href'].gsub(
              "#{@api_url}/network/", ''
            )
          end

          tasklists = {}
          response.css(
            "Link[type='application/vnd.vmware.vcloud.tasksList+xml']"
          ).each do |item|
            tasklists[item['name']] = item['href'].gsub(
              "#{@api_url}/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']] = item['href'].gsub(
              "#{@api_url}/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

          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']] = item['href'].gsub(
              "#{@api_url}/vApp/vapp-", ''
            )
          end

          networks = {}
          response.css(
            "Network[type='application/vnd.vmware.vcloud.network+xml']"
          ).each do |item|
            networks[item['name']] = item['href'].gsub(
              "#{@api_url}/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']] = item['href'].gsub(
              "#{@api_url}/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 = vm_elem['href'].gsub("#{@api_url}/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                   => vm['href'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = response.css(
            "AdminCatalog Tasks Task[operationName='catalogCreateCatalog']"
          ).first[:href].gsub("#{@api_url}/task/", '')

          catalog_id = response.css(
            "AdminCatalog Link [type='application/vnd.vmware.vcloud.catalog+xml']"
            ).first[:href].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/vApp/vapp-", '')

          task = response.css(
            "VApp Task[operationName='vdcInstantiateVapp']"
          ).first

          task_id = task['href'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/vApp/vapp-", '')

          task = response.css("VApp Task[operationName='vdcComposeVapp']").first
          task_id = task['href'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/vApp/vapp-", '')

          task = response.css("Task [operationName='vdcRecomposeVapp']").first
          task_id = task['href'].gsub("#{@api_url}/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 => vm['href'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 = headers['Location'].gsub("#{@api_url}/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 edge_gateway['href'].gsub(
              "#{@api_url}/admin/edgeGateway/", ''
            )
          else
            return nil
          end
        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 = edge_gateway['href'].gsub(
              "#{@api_url}/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',
{'fraction_non_alphanumeric': 0.108197345152553, 'fraction_numerical': 0.002025832158126833, 'mean_word_length': 1.5556191184462687, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 330, 'https://': 2, 'lorem ipsum': 0, 'www.': 23, 'xml': 321}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6426002', 'n_tokens_mistral': 25557, 'n_tokens_neox': 24827, 'n_words': 4919}