#!/usr/bin/env ruby # -------------------------------------------------------------------------- # # Copyright 2002-2017, OpenNebula Project, OpenNebula Systems # # # # 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. # #--------------------------------------------------------------------------- # ONE_LOCATION = ENV["ONE_LOCATION"] if !ONE_LOCATION RUBY_LIB_LOCATION = "/usr/lib/one/ruby" REMOTES_LOCATION = "/var/lib/one/remotes/" else RUBY_LIB_LOCATION = ONE_LOCATION+"/lib/ruby" REMOTES_LOCATION = ONE_LOCATION+"/var/remotes/" end $: << RUBY_LIB_LOCATION $: << RUBY_LIB_LOCATION+"/cli" $: << REMOTES_LOCATION+"vmm/vcenter/" require 'fileutils' require 'command_parser' require 'one_helper/onehost_helper' require 'one_helper/onecluster_helper' require 'vcenter_driver' require 'opennebula' TEMP_DIR="/var/tmp/vcenter_one54" FileUtils.mkdir_p TEMP_DIR def banner(msg, header=false, extended=nil) STDOUT.puts STDOUT.puts if !header STDOUT.puts "="*80 STDOUT.puts msg STDOUT.puts "-"*80 if extended STDOUT.puts extended if extended STDOUT.puts "="*80 end def logo_banner(msg, header=false) STDOUT.puts STDOUT.puts if !header STDOUT.puts "="*80 STDOUT.puts " / _ \\ _ __ ___ _ __ | \\ | | ___| |__ _ _| | __ _" STDOUT.puts "| | | | '_ \\ / _ \\ '_ \\| \\| |/ _ \\ '_ \\| | | | |/ _` |" STDOUT.puts "| |_| | |_) | __/ | | | |\\ | __/ |_) | |_| | | (_| |" STDOUT.puts " \\___/| .__/ \\___|_| |_|_| \\_|\\___|_.__/ \\__,_|_|\\__,_|" STDOUT.puts " |_|" STDOUT.puts "-"*80 STDOUT.puts msg STDOUT.puts "="*80 end ################################################################################ # Monkey patch XMLElement with retrieve_xmlelements ################################################################################ class OpenNebula::XMLElement def retrieve_xmlelements(xpath_str) collection = [] if OpenNebula::NOKOGIRI @xml.xpath(xpath_str).each { |pelem| collection << OpenNebula::XMLElement.new(pelem) } else @xml.elements.each(xpath_str) { |pelem| collection << OpenNebula::XMLElement.new(pelem) } end collection end end def get_image_size(ds, img_str) ds_name = ds.name img_path = File.dirname img_str img_name = File.basename img_str # Create Search Spec spec = RbVmomi::VIM::HostDatastoreBrowserSearchSpec.new vmdisk_query = RbVmomi::VIM::VmDiskFileQuery.new vmdisk_query.details = RbVmomi::VIM::VmDiskFileQueryFlags(:diskType => true, :capacityKb => true, :hardwareVersion => true, :controllerType => true) spec.query = [vmdisk_query, RbVmomi::VIM::IsoImageFileQuery.new] spec.details = RbVmomi::VIM::FileQueryFlags(:fileOwner => true, :fileSize => true, :fileType => true, :modification => true) spec.matchPattern = img_name.nil? ? [] : [img_name] datastore_path = "[#{ds_name}]" datastore_path << " #{img_path}" if !img_path.nil? search_params = {'datastorePath' => datastore_path, 'searchSpec' => spec} # Perform search task and return results begin search_task = ds.browser.SearchDatastoreSubFolders_Task(search_params) search_task.wait_for_completion size = 0 # Try to get vmdk capacity as seen by VM size = search_task.info.result[0].file[0].capacityKb / 1024 rescue nil # Try to get file size size = search_task.info.result[0].file[0].fileSize / 1024 / 1024 rescue nil if !size raise "Could not get file size or capacity" if size.nil? size rescue raise "Could not find file #{img_path}." end end def create_cdata_element(item, xml_doc, element_name, element_value) item.add_child(xml_doc.create_element(element_name)).add_child(Nokogiri::XML::CDATA.new(xml_doc,element_value)) end def create_disk(xml_doc, image_name, image_source, image_prefix, image_id, disk_index, cluster_id, ds, ds_ref, ds_name, vi_client) disk_size = get_image_size(RbVmomi::VIM::Datastore.new(vi_client.vim, ds_ref), image_source) device_letters = ('a'..'z').to_a # Add new disk attributes xml_template = xml_doc.root.at_xpath("TEMPLATE") disk = xml_template.add_child(xml_doc.create_element("DISK")) create_cdata_element(disk, xml_doc, "CLONE", "YES") create_cdata_element(disk, xml_doc, "CLONE_TARGET", "SYSTEM") create_cdata_element(disk, xml_doc, "CLUSTER_ID", "#{cluster_id}") create_cdata_element(disk, xml_doc, "DATASTORE", "#{ds_name}") create_cdata_element(disk, xml_doc, "DATASTORE_ID", "#{ds["ID"]}") create_cdata_element(disk, xml_doc, "DEV_PREFIX", "#{image_prefix}") create_cdata_element(disk, xml_doc, "DISK_ID", "#{disk_index}") create_cdata_element(disk, xml_doc, "DISK_SNAPSHOT_TOTAL_SIZE", "0") create_cdata_element(disk, xml_doc, "DISK_TYPE", "FILE") create_cdata_element(disk, xml_doc, "IMAGE", "#{image_name}") create_cdata_element(disk, xml_doc, "IMAGE_ID", "#{image_id}") create_cdata_element(disk, xml_doc, "IMAGE_STATE", "2") create_cdata_element(disk, xml_doc, "LN_TARGET", "NONE") create_cdata_element(disk, xml_doc, "OPENNEBULA_MANAGED", "NO") create_cdata_element(disk, xml_doc, "ORIGINAL_SIZE", "#{disk_size}") create_cdata_element(disk, xml_doc, "READONLY", "NO") create_cdata_element(disk, xml_doc, "SAVE", "NO") create_cdata_element(disk, xml_doc, "SIZE", "#{disk_size}") create_cdata_element(disk, xml_doc, "SOURCE", "#{image_source}") create_cdata_element(disk, xml_doc, "TARGET", "#{image_prefix}#{device_letters[disk_index]}") create_cdata_element(disk, xml_doc, "TM_MAD", "vcenter") create_cdata_element(disk, xml_doc, "TYPE", "FILE") create_cdata_element(disk, xml_doc, "VCENTER_ADAPTER_TYPE","#{ds["TEMPLATE/VCENTER_DS_REF"]}") create_cdata_element(disk, xml_doc, "VCENTER_DISK_TYPE", "#{ds["TEMPLATE/VCENTER_DS_REF"]}") create_cdata_element(disk, xml_doc, "VCENTER_DS_REF", "#{ds["TEMPLATE/VCENTER_DS_REF"]}") end def create_nic(xml_doc, network, mac_address, cluster_id, nic_index) xml_template = xml_doc.root.at_xpath("TEMPLATE") nic = xml_template.add_child(xml_doc.create_element("NIC")) create_cdata_element(nic, xml_doc, "BRIDGE", "#{network["BRIDGE"]}") create_cdata_element(nic, xml_doc, "CLUSTER_ID", "#{cluster_id}") create_cdata_element(nic, xml_doc, "MAC", "#{mac_address}") create_cdata_element(nic, xml_doc, "NETWORK", "#{network["NAME"]}") create_cdata_element(nic, xml_doc, "NETWORK_ID", "#{network["ID"]}") create_cdata_element(nic, xml_doc, "NIC_ID", "#{nic_index}") create_cdata_element(nic, xml_doc, "OPENNEBULA_MANAGED", "NO") create_cdata_element(nic, xml_doc, "SECURITY_GROUPS", "0") create_cdata_element(nic, xml_doc, "VCENTER_CCR_REF", "#{network["TEMPLATE/VCENTER_CCR_REF"]}") create_cdata_element(nic, xml_doc, "VCENTER_INSTANCE_ID", "#{network["TEMPLATE/VCENTER_INSTANCE_ID"]}") create_cdata_element(nic, xml_doc, "VCENTER_NET_REF", "#{network["TEMPLATE/VCENTER_NET_REF"]}") create_cdata_element(nic, xml_doc, "VCENTER_PORTGROUP_TYPE", "#{network["TEMPLATE/VCENTER_PORTGROUP_TYPE"]}") create_cdata_element(nic, xml_doc, "VN_MAD", "dummy") end def create_image_ds(ds_name, ds_ref, vcenter_name, vcenter_uuid, ccr_name, dc_name, dc_ref, one_client, vcenter_user, vcenter_pass, vcenter_host, cluster_id=nil) image_ds_name = "#{ds_name}" template = "" template << "NAME=\"#{image_ds_name}\"\n" template << "TM_MAD=vcenter\n" template << "VCENTER_INSTANCE_ID=\"#{vcenter_uuid}\"\n" template << "VCENTER_DS_REF=\"#{ds_ref}\"\n" template << "VCENTER_DC_REF=\"#{dc_ref}\"\n" template << "VCENTER_DS_NAME=\"#{ds_name}\"\n" template << "VCENTER_DC_NAME=\"#{dc_name}\"\n" template << "VCENTER_CLUSTER=\"#{ccr_name}\"\n" template << "TYPE=IMAGE_DS\n" template << "DS_MAD=vcenter\n" one_ds = OpenNebula::Datastore.new(OpenNebula::Datastore.build_xml, one_client) rc = one_ds.allocate(template) raise rc.message if OpenNebula.is_error?(rc) rc = one_ds.info raise rc.message if OpenNebula.is_error?(rc) # Wait till the DS is monitored loop do sleep(2) one_ds.info break if one_ds["STATE"].to_i == 0 && one_ds["TOTAL_MB"].to_i > 0 end STDOUT.puts "--- New IMAGE datastore #{image_ds_name} created with ID: #{one_ds["ID"]}!\n" return one_ds end def create_system_ds(ds_name, ds_ref, vcenter_name, vcenter_uuid, dc_name, dc_ref, one_client, vcenter_host, vcenter_user, vcenter_pass, cluster_id=nil) system_ds_name = "#{ds_name} (SYS)" template = "" template << "NAME=\"#{system_ds_name}\"\n" template << "TM_MAD=vcenter\n" template << "VCENTER_INSTANCE_ID=\"#{vcenter_uuid}\"\n" template << "VCENTER_DC_REF=\"#{dc_ref}\"\n" template << "VCENTER_DC_NAME=\"#{dc_name}\"\n" template << "VCENTER_DS_REF=\"#{ds_ref}\"\n" template << "VCENTER_DS_NAME=\"#{ds_name}\"\n" template << "VCENTER_USER=\"#{vcenter_user}\"\n" if vcenter_user template << "VCENTER_PASSWORD=\"#{vcenter_pass}\"\n" if vcenter_pass template << "VCENTER_HOST=\"#{vcenter_host}\"\n" if vcenter_host template << "TYPE=SYSTEM_DS\n" one_ds = OpenNebula::Datastore.new(OpenNebula::Datastore.build_xml, one_client) if cluster_id rc = one_ds.allocate(template, cluster_id.to_i) else rc = one_ds.allocate(template) end raise rc.message if OpenNebula.is_error?(rc) one_ds.info rc = one_ds.info raise rc.message if OpenNebula.is_error?(rc) STDOUT.puts "Datastore \e[96m#{ds_name}\e[39m is now also a SYSTEM datastore with ID: #{one_ds["ID"]}\n" one_ds end def get_dc(item) while !item.instance_of? RbVmomi::VIM::Datacenter item = item.parent raise "Could not find the parent Datacenter" if !item end return item end def find_image(ipool, ds_name, image_path) element = ipool.select do |e| e["SOURCE"] == image_path && e["DATASTORE"] == ds_name end.first rescue nil return element end def find_datastore_by_name(dspool, name) element = dspool.select do |e| e["NAME"] == name end.first rescue nil return element end def find_cluster_by_name(cpool, name) element = cpool.select do |e| e["NAME"] == name end.first rescue nil return element end def find_datastore(dspool, ds_ref, dc_ref, vcenter_uuid, type) element = dspool.select do |e| e["TEMPLATE/TYPE"] == type && e["TEMPLATE/VCENTER_DS_REF"] == ds_ref && e["TEMPLATE/VCENTER_DC_REF"] == dc_ref && e["TEMPLATE/VCENTER_INSTANCE_ID"] == vcenter_uuid end.first rescue nil return element end def find_network(vnpool, net_ref, ccr_ref, template_ref, vcenter_uuid) element = vnpool.select do |e| e["TEMPLATE/VCENTER_NET_REF"] == net_ref && e["TEMPLATE/VCENTER_TEMPLATE_REF"] == template_ref && e["TEMPLATE/VCENTER_CCR_REF"] == ccr_ref && e["TEMPLATE/VCENTER_INSTANCE_ID"] == vcenter_uuid && e["TEMPLATE/OPENNEBULA_MANAGED"] == "NO" end.first rescue nil return element end def find_template(tpool, template_id, template_ref, ccr_ref, vcenter_uuid) element = tpool.select do |e| e["ID"] == template_id && e["TEMPLATE/VCENTER_TEMPLATE_REF"] == template_ref && e["TEMPLATE/VCENTER_CCR_REF"] == ccr_ref && e["TEMPLATE/VCENTER_INSTANCE_ID"] == vcenter_uuid end.first rescue nil return element end def vm_unmanaged_discover(devices, xml_doc, template_xml, existing_disks, existing_nics, ccr_name, ccr_ref, vcenter_name, vcenter_uuid, vcenter_user, vcenter_pass, vcenter_host, dc_name, dc_ref, ipool, vnpool, dspool, hpool, one_client, vi_client, vm_wild, vm_id, vm_name, vc_templates, vm_ref, vc_vmachines, template_ref, one_clusters, vcenter_ids) unmanaged = {} unmanaged[:images] = [] unmanaged[:networks] = [] ide_controlled = [] sata_controlled = [] scsi_controlled = [] disk_index = 0 unmanaged_disk_index = 0 managed_disk_index = 0 nic_index = 0 managed_nic_index = 0 extraconfig = [] # Get cluster's host ID hosts = hpool.retrieve_xmlelements("HOST[NAME=\"#{ccr_name}\"]") if hosts.empty? raise "Cannot find cluster's host ID associated with this template." end host_id = hosts.first["ID"] if !one_clusters.key?(host_id) raise "Could not find the OpenNebula cluster ID that is going to be associated to images and networks found in the template." end cluster_id = one_clusters[host_id] if !vm_wild && template_xml devices.each do |device| rc = vnpool.info_all raise "\n ERROR! Could not update vnpool. Reason #{rc.message}" if OpenNebula.is_error?(rc) rc = ipool.info_all raise "\n ERROR! Could not update ipool. Reason #{rc.message}" if OpenNebula.is_error?(rc) rc = dspool.info raise "\n ERROR! Could not update dspool. Reason #{rc.message}" if OpenNebula.is_error?(rc) if defined?(RbVmomi::VIM::VirtualIDEController) && device.is_a?(RbVmomi::VIM::VirtualIDEController) ide_controlled += device.device next end if defined?(RbVmomi::VIM::VirtualSATAController) && device.is_a?(RbVmomi::VIM::VirtualSATAController) sata_controlled += device.device next end if defined?(RbVmomi::VIM::VirtualSCSIController) && device.is_a?(RbVmomi::VIM::VirtualSCSIController) scsi_controlled += device.device next end #cluster_id = xml_doc.root.xpath("HISTORY_RECORDS/HISTORY[last()]/CID").text # If CDROM if !(device.class.ancestors.index(RbVmomi::VIM::VirtualCdrom)).nil? device_backing_datastore = device.backing.datastore rescue nil if device_backing_datastore ds_name = device.backing.datastore.name ds_ref = device.backing.datastore._ref image_path = device.backing.fileName.sub(/^\[(.*?)\] /, "") image = find_image(ipool, ds_name, image_path) if image # It's a persistent disk if it already exists xml_template = xml_doc.root.at_xpath("TEMPLATE") existing_disk = existing_disks[managed_disk_index] # Replace DISK_ID existind_disk_id = existing_disk.at_xpath("DISK_ID") existind_disk_id.content = disk_index disk = xml_template.add_child(existing_disks[managed_disk_index]) # Add VCENTER_DS_REF disk.add_child(xml_doc.create_element("VCENTER_DS_REF")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{ds_ref}")) STDOUT.puts "--- Added VCENTER_DS_REF=#{ds_ref} to CDROM (IMAGE_ID=#{image["ID"]})" # Update indexes managed_disk_index = managed_disk_index + 1 disk_index = disk_index + 1 end end end # If Virtual Disk if !(device.class.ancestors.index(RbVmomi::VIM::VirtualDisk)).nil? ds_name = device.backing.datastore.name ds_ref = device.backing.datastore._ref image_type = "OS" image_path = device.backing.fileName.sub(/^\[(.*?)\] /, "") image_prefix = "hd" if ide_controlled.include?(device.key) image_prefix = "sd" if scsi_controlled.include?(device.key) image_prefix = "sd" if sata_controlled.include?(device.key) file_name = File.basename(image_path).gsub(/\.vmdk$/,"") image_name = "#{file_name} - #{ds_name}" #Check if the image already exists one_image = find_image(ipool, ds_name, image_path) if !one_image #Check if the IMAGE DS is there ds = find_datastore(dspool, ds_ref, dc_ref, vcenter_uuid, "IMAGE_DS") #Create IMAGE and SYSTEM DS if datastore is not found if !ds ds = create_image_ds(ds_name, ds_ref, vcenter_name, vcenter_uuid, ccr_name, dc_name, dc_ref, one_client, vcenter_user, vcenter_pass, vcenter_host, cluster_id) create_system_ds(ds_name, ds_ref, vcenter_name, vcenter_uuid, dc_name, dc_ref, one_client, vcenter_user, vcenter_pass, vcenter_host, cluster_id) end ds_id = ds["ID"].to_i template_disk = template_xml.xpath("VMTEMPLATE/TEMPLATE/DISK")[unmanaged_disk_index] rescue nil raise "Cannot find unmanaged disk inside template" if !template_disk image_id = template_disk.xpath("IMAGE_ID").text raise "Cannot find image id for unmanaged disk" if image_id.empty? one_image = OpenNebula::Image.new_with_id(image_id, one_client) rc = one_image.info raise "\n ERROR! Could not get image info for unmanaged disk. image_id '#{image_id}'. Reason #{rc.message}" if OpenNebula.is_error?(rc) ds_id = one_image['DATASTORE_ID'] ds = OpenNebula::Datastore.new_with_id(ds_id, one_client) rc = ds.info raise "\n ERROR! Could not get ds info. Reason #{rc.message}" if OpenNebula.is_error?(rc) ds_ref = ds["TEMPLATE/VCENTER_DS_REF"] ds_name = ds["NAME"] STDOUT.puts "--- Image #{one_image["NAME"]} with ID #{one_image["ID"]} already exists" # Create unmanaged disk element for vm template # Get disk size (capacity) image_name = one_image["NAME"] image_source = one_image["SOURCE"] image_id = one_image["ID"] # Add new disk attributes create_disk(xml_doc, image_name, image_source, image_prefix, image_id, disk_index, cluster_id, ds, ds_ref, ds_name, vi_client) STDOUT.puts "--- Added unmanaged disk to xml template (IMAGE_ID=#{one_image["ID"]})" reference = {} reference[:key] = "opennebula.disk.#{unmanaged_disk_index}" reference[:value] = "#{device.key}" extraconfig << reference unmanaged_disk_index = unmanaged_disk_index + 1 disk_index = disk_index + 1 else if one_image["TEMPLATE/VCENTER_IMPORTED"] == "YES" # This is (probably) a wild VM. The code should not reach this. #Check if the IMAGE DS is there ds = find_datastore(dspool, ds_ref, dc_ref, vcenter_uuid, "IMAGE_DS") #Create IMAGE and SYSTEM DS if datastore is not found if !ds ds = create_image_ds(ds_name, ds_ref, vcenter_name, vcenter_uuid, ccr_name, dc_name, dc_ref, one_client, vcenter_user, vcenter_pass, vcenter_host, cluster_id) create_system_ds(ds_name, ds_ref, vcenter_name, vcenter_uuid, dc_name, dc_ref, one_client, vcenter_user, vcenter_pass, vcenter_host, cluster_id) end ds_id = ds["ID"].to_i #Create unmanaged disk element for vm template image_id = one_image["ID"] image_name = one_image["NAME"] image_source = one_image["SOURCE"] create_disk(xml_doc, image_name, image_source, image_prefix, image_id, disk_index, cluster_id, ds, ds_ref, ds_name, vi_client) STDOUT.puts "--- Added unmanaged disk in wild vm (IMAGE_ID=#{one_image["ID"]})" reference = {} reference[:key] = "opennebula.disk.#{unmanaged_disk_index}" reference[:value] = "#{device.key}" extraconfig << reference # Update indexes unmanaged_disk_index = unmanaged_disk_index + 1 disk_index = disk_index + 1 else # It's a persistent disk if it already exists xml_template = xml_doc.root.at_xpath("TEMPLATE") existing_disk = existing_disks[managed_disk_index] # Replace DISK_ID existing_disk_image_id = existing_disk.xpath("IMAGE_ID").text existing_disk_id = existing_disk.at_xpath("DISK_ID") existing_disk_id.content = disk_index disk = xml_template.add_child(existing_disks[managed_disk_index]) # Add VCENTER_DISK_TYPE and VCENTER_ADAPTER_TYPE if found if !existing_disk.xpath("DISK_TYPE").text.empty? disk.add_child(xml_doc.create_element("VCENTER_DISK_TYPE")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{existing_disk.xpath("DISK_TYPE").text}")) STDOUT.puts "--- Added VCENTER_DISK_TYPE=#{existing_disk.xpath("DISK_TYPE").text} to existing disk (IMAGE_ID=#{existing_disk_image_id})" end if !existing_disk.xpath("ADAPTER_TYPE").text.empty? disk.add_child(xml_doc.create_element("VCENTER_ADAPTER_TYPE")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{existing_disk.xpath("ADAPTER_TYPE").text}")) STDOUT.puts "--- Added VCENTER_ADAPTER_TYPE=#{existing_disk.xpath("ADAPTER_TYPE").text} to existing disk (IMAGE_ID=#{existing_disk_image_id})" end # Add VCENTER_DS_REF disk.add_child(xml_doc.create_element("VCENTER_DS_REF")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{ds_ref}")) STDOUT.puts "--- Added VCENTER_DS_REF=#{ds_ref} to existing disk (IMAGE_ID=#{existing_disk_image_id})" # Update indexes managed_disk_index = managed_disk_index + 1 disk_index = disk_index + 1 end end end # If VirtualEthernetCard if !device.class.ancestors.index(RbVmomi::VIM::VirtualEthernetCard).nil? network_bridge = device.backing.network.name network_ref = device.backing.network._ref network_name = "#{network_bridge} [#{vm_name}]" network_type = device.backing.network.instance_of?(RbVmomi::VIM::DistributedVirtualPortgroup) ? "Distributed Port Group" : "Port Group" # Create network if doesn't exist network = find_network(vnpool, network_ref, ccr_ref, template_ref, vcenter_uuid) if !network one_net = "" one_net << "NAME=\"#{network_name}\"\n" one_net << "BRIDGE=\"#{network_bridge}\"\n" one_net << "VN_MAD=\"dummy\"\n" one_net << "VCENTER_PORTGROUP_TYPE=\"#{network_type}\"\n" one_net << "VCENTER_NET_REF=\"#{network_ref}\"\n" one_net << "VCENTER_CCR_REF=\"#{ccr_ref}\"\n" one_net << "VCENTER_INSTANCE_ID=\"#{vcenter_uuid}\"\n" one_net << "VCENTER_TEMPLATE_REF=\"#{template_ref}\"\n" one_net << "OPENNEBULA_MANAGED=\"NO\"\n" one_net << "AR=[\n" one_net << "TYPE=\"ETHER\",\n" one_net << "SIZE=\"255\"\n" one_net << "]\n" one_vn = OpenNebula::VirtualNetwork.new(OpenNebula::VirtualNetwork.build_xml, one_client) rc = one_vn.allocate(one_net, cluster_id.to_i) raise "\n ERROR! Could not create vnet for vm #{vm_name}. Reason #{rc.message}" if OpenNebula.is_error?(rc) rc = one_vn.info raise "\n ERROR! Could not get network info for vnet #{network_name}. Reason #{rc.message}" if OpenNebula.is_error?(rc) network = one_vn STDOUT.puts "--- Network #{one_vn["NAME"]} with ID #{one_vn["ID"]} has been created" else STDOUT.puts "--- Network #{network["NAME"]} with ID #{network["ID"]} already exists" end existing_macs = [] existing_nics.xpath("MAC").each do |mac| existing_macs << mac.text end mac_address = device.macAddress if !existing_macs.include?(mac_address) # Unmanaged nic create_nic(xml_doc, network, mac_address, cluster_id, nic_index) #Update indexes nic_index = nic_index + 1 else # Managed nic managed_nic_index = existing_macs.index(mac_address) xml_template = xml_doc.root.at_xpath("TEMPLATE") existing_nic = existing_nics[managed_nic_index] # Replace NIC_ID existind_nic_id = existing_nic.at_xpath("NIC_ID") existind_nic_id.content = nic_index # Add existing NIC to XML template nic = xml_template.add_child(existing_nics[managed_nic_index]) create_cdata_element(nic, xml_doc, "VCENTER_NET_REF", "#{network["TEMPLATE/VCENTER_NET_REF"]}") create_cdata_element(nic, xml_doc, "VCENTER_CCR_REF", "#{network["TEMPLATE/VCENTER_CCR_REF"]}") create_cdata_element(nic, xml_doc, "VCENTER_INSTANCE_ID", "#{network["TEMPLATE/VCENTER_INSTANCE_ID"]}") create_cdata_element(nic, xml_doc, "VCENTER_PORTGROUP_TYPE", "#{network["TEMPLATE/VCENTER_PORTGROUP_TYPE"]}") #Update indexes nic_index = nic_index + 1 end end end end # Reference some nodes xml_template = xml_doc.root.at_xpath("TEMPLATE") xml_user_template = xml_doc.root.at_xpath("USER_TEMPLATE") xml_monitoring = xml_doc.root.at_xpath("MONITORING") xml_history = xml_doc.root.at_xpath("HISTORY_RECORDS/HISTORY") # Update context disk ID if !vm_wild xml_template.xpath("CONTEXT/DISK_ID").remove xml_context = xml_template.at_xpath("CONTEXT") xml_context.add_child(xml_doc.create_element("DISK_ID")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{disk_index}")) end # Remove PUBLIC_CLOUD as it is no longer used with vcenter xml_user_template.xpath("PUBLIC_CLOUD").remove # Remove KEEP_DISKS_ON_DONE from USER_TEMPLATE xml_user_template.xpath("KEEP_DISKS_ON_DONE").remove # Remove SCHED_DS_REQUIREMENTS xml_user_template.xpath("SCHED_DS_REQUIREMENTS").remove # Remove VCENTER_DATASTORE and USER_INPUTS/VCENTER_DATASTORE... no longer used # If datastore is a system_ds we may have to add SCHED_DS_REQUIREMENTS # Also identify the datastore id to update last history records with the new DS_ID vcenter_datastore = xml_user_template.xpath("VCENTER_DATASTORE").text ds_id = nil if !vcenter_datastore.empty? # If VCENTER_DATASTORE is a SYSTEM_DS it is a StoragePod then # SCHED_DS_REQUIREMENTS must contain ID="DS_ID" ds = find_datastore_by_name(dspool, "#{vcenter_datastore}") ds_id = ds["ID"] ds_type = ds["TEMPLATE/TYPE"] if ds_type == "SYSTEM_DS" sched_ds_req = xml_user_template.xpath("SCHED_DS_REQUIREMENTS") if !sched_ds_req.empty? xml_user_template.xpath("SCHED_DS_REQUIREMENTS").remove requirements = "ID=#{ds_id} & (#{sched_ds_req})" xml_user_template.add_child(xml_doc.create_element("SCHED_DS_REQUIREMENTS")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"\"#{requirements}\"")) else # Add a SCHED_DS_REQUIREMENTS to template xml_user_template.add_child(xml_doc.create_element("SCHED_DS_REQUIREMENTS")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"\"ID=#{ds_id}\"")) end end # New ds should be a system datastore ds = find_datastore_by_name(dspool, "#{vcenter_datastore} (SYS)") ds_id = ds["ID"] else if vm_wild if !vc_vmachines.key? vm_ref raise "Could not find vcenter vm using ref #{vm_ref} in order to assign a datastore" else vc_system_ds = vc_vmachines[vm_ref]["datastore"].first rescue nil raise "Could not find Datastore associated with the VM" if vc_system_ds.nil? ds_ref = vc_system_ds._ref ds = find_datastore(dspool, ds_ref, dc_ref, vcenter_uuid, "SYSTEM_DS") if !ds ds_name = vc_system_ds.name ds = create_system_ds(ds_name, ds_ref, vcenter_name, vcenter_uuid, dc_name, dc_ref, one_client, vcenter_user, vcenter_pass, vcenter_host, cluster_id) end ds_id = ds["ID"] end else if !vc_templates.key? template_ref raise "Could not find vcenter template using ref #{template_ref} in order to assign a datastore" else ds_ref = vc_templates[template_ref]["datastore"].first._ref rescue nil raise "Could not get ds ref in order to assign a datastore in history records" if !ds_ref ds = find_datastore(dspool, ds_ref, dc_ref, vcenter_uuid, "SYSTEM_DS") ds_id = ds["ID"] end end end # Remove some attributes xml_user_template.xpath("VCENTER_DATASTORE").remove xml_user_template.xpath("USER_INPUTS/VCENTER_DATASTORE").remove xml_user_template.xpath("SCHED_REQUIREMENTS").remove # Replace USER_TEMPLATE/RESOURCE_POOL with VCENTER_RESOURCE_POOL resource_pool = xml_user_template.xpath("RESOURCE_POOL").text if !resource_pool.empty? xml_user_template.xpath("RESOURCE_POOL").remove vcenter_rp = xml_user_template.xpath("VCENTER_RESOURCE_POOL").text if !vcenter_rp.empty? xml_user_template.xpath("VCENTER_RESOURCE_POOL").remove end xml_user_template.add_child(xml_doc.create_element("VCENTER_RESOURCE_POOL")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{resource_pool}")) end # Replace USER_INPUTS/RESOURCE_POOL... no longer used user_resource_pool = xml_user_template.xpath("USER_INPUTS/RESOURCE_POOL").text if !user_resource_pool.empty? xml_user_template.xpath("USER_INPUTS/RESOURCE_POOL").remove vcenter_rp = xml_user_template.xpath("USER_INPUTS/VCENTER_RESOURCE_POOL").text if !vcenter_rp.empty? xml_user_template.xpath("USER_INPUTS/VCENTER_RESOURCE_POOL").remove end user_inputs = xml_user_template.at_xpath("USER_INPUTS") user_inputs.add_child(xml_doc.create_element("VCENTER_RESOURCE_POOL")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{user_resource_pool}")) end # Replace CUSTOMIZATION_SPEC with VCENTER_CUSTOMIZATION_SPEC customization_spec = xml_user_template.xpath("CUSTOMIZATION_SPEC").text if !customization_spec.empty? xml_user_template.xpath("CUSTOMIZATION_SPEC").remove xml_user_template.add_child(xml_doc.create_element("VCENTER_CUSTOMIZATION_SPEC")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{customization_spec}")) end # Add VCENTER_CCR_REF and VCENTER_INSTANCE_ID vcenter_ccr_ref = xml_user_template.xpath("VCENTER_CCR_REF").text if !vcenter_ccr_ref.empty? xml_user_template.xpath("VCENTER_CCR_REF").remove end xml_user_template.add_child(xml_doc.create_element("VCENTER_CCR_REF")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{ccr_ref}")) vcenter_instance_id = xml_user_template.xpath("VCENTER_INSTANCE_ID").text if !vcenter_instance_id.empty? xml_user_template.xpath("VCENTER_INSTANCE_ID").remove end xml_user_template.add_child(xml_doc.create_element("VCENTER_INSTANCE_ID")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{vcenter_uuid}")) # Add VCENTER_TEMPLATE_REF if VM is not wild if !vm_wild vcenter_template_ref = xml_user_template.xpath("VCENTER_TEMPLATE_REF").text if !vcenter_template_ref.empty? xml_user_template.xpath("VCENTER_TEMPLATE_REF").remove end xml_user_template.add_child(xml_doc.create_element("VCENTER_TEMPLATE_REF")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{template_ref}")) end # Monitoring info attributes xml_monitoring.xpath("LAST_MON").remove esx_host = xml_monitoring.xpath("ESX_HOST").text if !esx_host.empty? xml_monitoring.xpath("ESX_HOST").remove xml_monitoring.add_child(xml_doc.create_element("VCENTER_ESX_HOST")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{esx_host}")) end guest_state = xml_monitoring.xpath("GUEST_STATE").text if !guest_state.empty? xml_monitoring.xpath("GUEST_STATE").remove xml_monitoring.add_child(xml_doc.create_element("VCENTER_GUEST_STATE")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{guest_state}")) end resource_pool = xml_monitoring.xpath("RESOURCE_POOL").text if !guest_state.empty? xml_monitoring.xpath("RESOURCE_POOL").remove xml_monitoring.add_child(xml_doc.create_element("VCENTER_RP_NAME")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{resource_pool}")) end vmware_running_status = xml_monitoring.xpath("VMWARETOOLS_RUNNING_STATUS").text if !vmware_running_status.empty? xml_monitoring.xpath("VMWARETOOLS_RUNNING_STATUS").remove xml_monitoring.add_child(xml_doc.create_element("VCENTER_VMWARETOOLS_RUNNING_STATUS")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{vmware_running_status}")) end vmware_tools_version = xml_monitoring.xpath("VMWARETOOLS_VERSION").text if !vmware_tools_version.empty? xml_monitoring.xpath("VMWARETOOLS_VERSION").remove xml_monitoring.add_child(xml_doc.create_element("VCENTER_VMWARETOOLS_VERSION")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{vmware_tools_version}")) end vmware_tools_version_status = xml_monitoring.xpath("VMWARETOOLS_VERSION_STATUS").text if !vmware_tools_version_status.empty? xml_monitoring.xpath("VMWARETOOLS_VERSION_STATUS").remove xml_monitoring.add_child(xml_doc.create_element("VCENTER_VMWARETOOLS_VERSION_STATUS")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{vmware_tools_version_status}")) end # History record info attributes vmware_tools_version = xml_history.xpath("VMWARETOOLS_VERSION").text history_ds_id = xml_history.at_xpath("DS_ID") history_ds_id.content = ds_id xml_history.xpath("TM_MAD").remove xml_history.add_child(xml_doc.create_element("TM_MAD")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"vcenter")) # Write template to file File.open("#{TEMP_DIR}/one_migrate_vm_#{vm_id}","w"){|f| f.puts(xml_doc.root.to_s.gsub(/>\s*/, ">").gsub(/\s* [ :obj => view, :skip => true, :selectSet => [ RbVmomi::VIM.TraversalSpec( :name => 'traverseEntities', :type => 'ContainerView', :path => 'view', :skip => false ) ] ], :propSet => [ { :type => 'ClusterComputeResource', :pathSet => ['name','host'] } ] ) result = pc.RetrieveProperties(:specSet => [filterSpec]) clusters = {} result.each do |r| clusters[r.obj._ref] = r.to_hash if r.obj.is_a?(RbVmomi::VIM::ClusterComputeResource) end view.DestroyView # Destroy the view return clusters end def retrieve_vcenter_datastores(vi_client) view = vi_client.vim.serviceContent.viewManager.CreateContainerView({ container: vi_client.vim.rootFolder, type: ['Datastore','StoragePod'], recursive: true }) pc = vi_client.vim.serviceContent.propertyCollector filterSpec = RbVmomi::VIM.PropertyFilterSpec( :objectSet => [ :obj => view, :skip => true, :selectSet => [ RbVmomi::VIM.TraversalSpec( :name => 'traverseEntities', :type => 'ContainerView', :path => 'view', :skip => false ) ] ], :propSet => [ { :type => 'Datastore', :pathSet => ['name'] }, { :type => 'StoragePod', :pathSet => ['name'] } ] ) result = pc.RetrieveProperties(:specSet => [filterSpec]) datastores = {} result.each do |r| datastores[r.obj._ref] = r.to_hash if r.obj.is_a?(RbVmomi::VIM::Datastore) || r.obj.is_a?(RbVmomi::VIM::StoragePod) datastores[r.obj._ref][:ds_type] = r.obj.is_a?(RbVmomi::VIM::Datastore) ? "Datastore" : "StoragePod" end view.DestroyView # Destroy the view return datastores end def retrieve_vcenter_networks(vi_client) view = vi_client.vim.serviceContent.viewManager.CreateContainerView({ container: vi_client.vim.rootFolder, type: ['Network','DistributedVirtualPortgroup'], recursive: true }) pc = vi_client.vim.serviceContent.propertyCollector filterSpec = RbVmomi::VIM.PropertyFilterSpec( :objectSet => [ :obj => view, :skip => true, :selectSet => [ RbVmomi::VIM.TraversalSpec( :name => 'traverseEntities', :type => 'ContainerView', :path => 'view', :skip => false ) ] ], :propSet => [ { :type => 'Network', :pathSet => ['name','host'] }, { :type => 'DistributedVirtualPortgroup', :pathSet => ['name','host'] } ] ) result = pc.RetrieveProperties(:specSet => [filterSpec]) networks = {} result.each do |r| networks[r.obj._ref] = r.to_hash if r.obj.is_a?(RbVmomi::VIM::DistributedVirtualPortgroup) || r.obj.is_a?(RbVmomi::VIM::Network) networks[r.obj._ref][:network_type] = r.obj.is_a?(RbVmomi::VIM::DistributedVirtualPortgroup) ? "Distributed Port Group" : "Port Group" end view.DestroyView # Destroy the view return networks end def retrieve_vcenter_vms(vi_client) view = vi_client.vim.serviceContent.viewManager.CreateContainerView({ container: vi_client.vim.rootFolder, type: ['VirtualMachine'], recursive: true }) pc = vi_client.vim.serviceContent.propertyCollector filterSpec = RbVmomi::VIM.PropertyFilterSpec( :objectSet => [ :obj => view, :skip => true, :selectSet => [ RbVmomi::VIM.TraversalSpec( :name => 'traverseEntities', :type => 'ContainerView', :path => 'view', :skip => false ) ] ], :propSet => [ { :type => 'VirtualMachine', :pathSet => ['name','config.template','config.uuid','config.hardware.device', 'config.extraConfig', 'datastore'] } ] ) result = pc.RetrieveProperties(:specSet => [filterSpec]) vms = {} result.each do |r| vms[r.obj._ref] = r.to_hash if r.obj.is_a?(RbVmomi::VIM::VirtualMachine) end vmachines = {} templates = {} vms.each do |ref,value| if value["config.template"] templates[ref] = value else vmachines[ref] = value end end view.DestroyView # Destroy the view return vmachines, templates end def select_cluster(vc_clusters, ccr_name, vi_client) ccr_ref = nil STDOUT.puts("\n\e[93mWARNING: Manual intervention required!\e[39m") STDOUT.puts("\nWhich vCenter cluster is represented by OpenNebula \e[96mhost #{ccr_name}?\e[39m\n") STDOUT.puts index = 0 ccr_refs = [] vc_clusters.each do |ref, ccr| if ccr_name == ccr["name"] item = RbVmomi::VIM::ClusterComputeResource.new(vi_client.vim, ref) folders = [] while !item.instance_of? RbVmomi::VIM::Datacenter item = item.parent if !item.instance_of?(RbVmomi::VIM::Datacenter) if item.name != "host" folders << item.name else folders << "" end end if item.nil? raise "Could not find the Datacenter for the host" end end datacenter = item location = folders.reverse.join("/") location = "/" if location.empty? ccr_refs << ref STDOUT.puts("#{index+1}: #{ccr["name"]} found in #{datacenter.name} datacenter at #{location}") index += 1 end end loop do STDOUT.print("\nFrom the list above, please \e[95mpick a number\e[39m in order to specify the cluster: ") cluster_index = STDIN.gets.strip.to_i next if cluster_index == 0 || cluster_index - 1 < 0 || cluster_index - 1 > ccr_refs.size ccr_ref = ccr_refs[cluster_index-1] rescue nil break if ccr_ref end STDOUT.puts STDOUT.puts("-" * 80) ccr_ref end ################################################################################ def add_new_host_attrs(vc_clusters, hpool, one_client, vcenter_ids) # Get all hosts from pool with VM_MAD=vcenter hosts = hpool.retrieve_xmlelements("HOST[VM_MAD=\"vcenter\"]") hosts.each do |host| begin # Get OpenNebula host and prepare variables one_host = OpenNebula::Host.new_with_id(host["ID"], one_client) rc = one_host.info raise rc.message if OpenNebula.is_error?(rc) ccr_name = host["NAME"] ccr_ref = nil vi_client = VCenterDriver::VIClient.new(host["ID"]) vcenter_uuid = vi_client.vim.serviceContent.about.instanceUuid vcenter_version = vi_client.vim.serviceContent.about.apiVersion # We try to obtain the Host's moref from vCenter objects clusters_with_name = vc_clusters[vcenter_uuid].select {|ref, ccr| ccr["name"] == ccr_name} # If we cannot obtain the moref we raise an exception if clusters_with_name.size == 0 raise "Host #{ccr_name} could not be updated, cannot find cluster's MOREF" end # If only one moref is found we assign the ref, if many results are # found the administrator must select if from a list if clusters_with_name.size == 1 ccr_ref = clusters_with_name.keys.first else ccr_ref = select_cluster(vc_clusters[vcenter_uuid], ccr_name, vi_client) end # The host's template is updated with the new attributes template = "" template << "VCENTER_CCR_REF=\"#{ccr_ref}\"\n" template << "VCENTER_INSTANCE_ID=\"#{vcenter_uuid}\"\n" template << "VCENTER_VERSION=\"#{vcenter_version}\"" rc = one_host.update(template, true) raise "Host #{ccr_name} could not be updated. Reason #{rc.message}" if OpenNebula.is_error?(rc) STDOUT.puts "\nHost \e[96m#{ccr_name}\e[39m got new attributes:\n" STDOUT.puts STDOUT.puts "--- VCENTER_CCR_REF=#{ccr_ref}\n" STDOUT.puts "--- VCENTER_INSTANCE_ID=#{vcenter_uuid}\n" STDOUT.puts "--- VCENTER_VERSION=#{vcenter_version}\n" STDOUT.puts STDOUT.puts "-" * 80 STDOUT.puts # We track what hosts have been modified so we can create # XML templates later vcenter_ids[:host] << one_host["ID"] rescue Exception => e raise e ensure vi_client.vim.close if vi_client end end end ################################################################################ def create_new_clusters(vc_clusters, hpool, cpool, one_client) clusters = {} # Delete existing files from a previous script launch ##if File.exist?("#{TEMP_DIR}/one_migrate_clusters_ids") ## File.delete("#{TEMP_DIR}/one_migrate_clusters_ids") ##end # Get all hosts from pool with VN_MAD="vcenter" hosts = hpool.retrieve_xmlelements("HOST[VM_MAD=\"vcenter\"]") hosts.each do |host| begin # Get OpenNebula host and assign variables one_host = OpenNebula::Host.new_with_id(host["ID"], one_client) rc = one_host.info raise rc.message if OpenNebula.is_error?(rc) vi_client = VCenterDriver::VIClient.new(host["ID"]) vcenter_uuid = vi_client.vim.serviceContent.about.instanceUuid ccr_name = host["NAME"] # Check if we find the moref for the vCenter cluster clusters_with_name = vc_clusters[vcenter_uuid].select {|ref, ccr| ccr["name"] == ccr_name} if clusters_with_name.size == 0 raise "Host #{ccr_name} could not be updated, cannot find cluster's MOREF" end # Check if host is assigned to a non default cluster if host["CLUSTER_ID"] == "0" # Check if there's an OpenNebula cluster with the host's name one_cluster = find_cluster_by_name(cpool, ccr_name) if !one_cluster # If the cluster doesn't exits we create a new cluster one_cluster = OpenNebula::Cluster.new(OpenNebula::Cluster.build_xml, one_client) rc = one_cluster.allocate(ccr_name) if OpenNebula.is_error?(rc) STDOUT.puts " Error creating OpenNebula cluster you should create a cluster by hand with name #{ccr_name} before you upgrade OpenNebula: #{rc.message}\n" next end # We inform that the Cluster has been created STDOUT.puts "OpenNebula Cluster named #{ccr_name} \e[92mhas been created.\e[39m" STDOUT.puts # Fetch the cluster info rc = one_cluster.info if OpenNebula.is_error?(rc) STDOUT.puts " Error Getting information from cluster '#{ccr_name}'. Reason: #{rc.message}\n" next end else STDOUT.puts "OpenNebula Cluster #{ccr_name} \e[92malready exists.\e[39m" STDOUT.puts end # Write what cluster ID will be associated to what host ID: host_id:cluster_id ##File.open("#{TEMP_DIR}/one_migrate_clusters_ids","a"){|f| f.puts("#{host["ID"]}:#{one_cluster["ID"]}")} # Store in memory the same information clusters[host["ID"]] = one_cluster["ID"] else # Write existing cluster ID STDOUT.puts "OpenNebula Cluster #{host["CLUSTER_ID"]} \e[92malready contains Host #{ccr_name}.\e[39m" ##File.open("#{TEMP_DIR}/one_migrate_clusters_ids","a"){|f| f.puts("#{host["ID"]}:#{host["CLUSTER_ID"]}")} clusters[host["ID"]] = host["CLUSTER_ID"] end rescue Exception => e raise e ensure vi_client.vim.close if vi_client end end return clusters end ################################################################################ def prepare_host_xml_templates(host_ids, one_clusters, one_client) host_ids.each do |host_id| # Create XML removing old attributes one_host = OpenNebula::Host.new_with_id(host_id, one_client) raise rc.message if OpenNebula.is_error?(one_host) rc = one_host.info raise rc.message if OpenNebula.is_error?(rc) # Let's see in which OpenNebula cluster we have to group this host if !one_clusters.key?(host_id) raise "Could not find the OpenNebula cluster ID that is going to be associated to host ID: #{host_id}" end cluster_id = one_clusters[host_id] one_cluster = OpenNebula::Cluster.new_with_id(cluster_id, one_client) raise rc.message if OpenNebula.is_error?(one_cluster) rc = one_cluster.info raise rc.message if OpenNebula.is_error?(rc) # We remove old attributes xml_doc = Nokogiri::XML(one_host.to_xml, nil, "UTF-8"){|c| c.default_xml.noblanks} xml_doc.root.xpath("TEMPLATE/PUBLIC_CLOUD").remove xml_doc.root.xpath("TEMPLATE/VCENTER_DATASTORE").remove xml_doc.root.xpath("TEMPLATE/RESOURCE_POOL").remove # We have to assign the host to the right cluster xml_cluster_id = xml_doc.root.at_xpath("CLUSTER_ID") xml_cluster_id.content = cluster_id xml_cluster = xml_doc.root.at_xpath("CLUSTER") xml_cluster.content = one_cluster["NAME"] STDOUT.puts STDOUT.puts "New XML file #{TEMP_DIR}/one_migrate_host_#{host_id} for host \e[96m#{one_host["NAME"]}\e[39m \e[92mwas created and attributes were removed\e[39m\n" File.open("#{TEMP_DIR}/one_migrate_host_#{host_id}","w"){|f| f.puts(xml_doc.root.to_s.gsub(/>\s*/, ">").gsub(/\s* ds_info.size ds_ref = ds_info[ds_index-1][:ref] rescue nil ds_type = ds_info[ds_index-1][:ds_type] rescue nil dc_name = ds_info[ds_index-1][:dc_name] rescue nil dc_ref = ds_info[ds_index-1][:dc_ref] rescue nil break if ds_ref end STDOUT.puts STDOUT.puts("-" * 80) STDOUT.puts end # Raise and exception if we cannot find the datastore's moref if ds_ref.nil? raise "Datastore #{ds_name} could not be updated, cannot find datastore's MOREF" end # Prepare new datastore attributes template = "" template << "VCENTER_DS_REF=\"#{ds_ref}\"\n" template << "VCENTER_DS_NAME=\"#{ds_name}\"\n" template << "VCENTER_DC_REF=\"#{dc_ref}\"\n" template << "VCENTER_DC_NAME=\"#{dc_name}\"\n" template << "VCENTER_HOST=\"#{vcenter_host}\"\n" template << "VCENTER_USER=\"#{vcenter_user}\"\n" template << "VCENTER_PASSWORD=\"#{vcenter_pass}\"\n" template << "VCENTER_INSTANCE_ID=\"#{vcenter_uuid}\"\n" # Update the template rc = one_ds.update(template, true) raise "Datastore #{ds_name} could not be updated. Reason #{rc.message}" if OpenNebula.is_error?(rc) # Inform what attributes have been added STDOUT.puts "Datastore \e[96m#{ds_name}\e[39m got new attributes:\n" STDOUT.puts STDOUT.puts "--- VCENTER_DS_REF=\"#{ds_ref}\"\n" STDOUT.puts "--- VCENTER_DS_NAME=\"#{ds_name}\"\n" STDOUT.puts "--- VCENTER_DC_REF=\"#{dc_ref}\"\n" STDOUT.puts "--- VCENTER_DC_NAME=\"#{dc_name}\"\n" STDOUT.puts "--- VCENTER_HOST=\"#{vcenter_host}\"\n" STDOUT.puts "--- VCENTER_USER=\"#{vcenter_user}\"\n" STDOUT.puts "--- VCENTER_PASSWORD=\"#{vcenter_pass}\"\n" STDOUT.puts "--- VCENTER_INSTANCE_ID=\"#{vcenter_uuid}\"\n" STDOUT.puts # Update datastore information rc = one_ds.info raise "Datastore info #{ds_name} could not be retrieved. Reason #{rc.message}" if OpenNebula.is_error?(rc) # Let's see in which OpenNebula cluster we have to group this datastore if !one_clusters.key?(host_id) raise "Could not find the OpenNebula cluster ID that is going to be associated to host ID: #{host_id}" end cluster_id = one_clusters[host_id] # Add IMAGE datastore to OpenNebula cluster if it hasn't been assigned previously cluster_ids = one_ds.retrieve_xmlelements("CLUSTERS") found_cluster_ids = cluster_ids.select { |cluster| cluster["ID"] == cluster_id } if found_cluster_ids.empty? one_cluster = OpenNebula::Cluster.new_with_id(cluster_id, one_client) rc = one_cluster.adddatastore(ds_id.to_i) if OpenNebula.is_error?(rc) raise "Datastore #{ds_name} could not be assigned to cluster ID: #{cluster_id} you should assign this datastore to that cluster by hand once this script finishes. Reason #{rc.message}" else STDOUT.puts "Datastore \e[96m#{ds_name}\e[39m has been assigned to cluster ID: #{cluster_id}." STDOUT.puts end end # Check if SYSTEM datastore was not created before # and create SYSTEM_DS associated to the existing IMAGE_DS and add it # to the right OpenNebula cluster if ds_type == "Datastore" create_system_ds(ds_name, ds_ref, vcenter_name, vcenter_uuid, dc_name, dc_ref, one_client, vcenter_host, vcenter_user, vcenter_pass, cluster_id) end STDOUT.puts STDOUT.puts "-" * 80 STDOUT.puts # We track what datastores have been modified so we can create # XML templates later vcenter_ids[:ds] << one_ds["ID"] rescue Exception => e raise e ensure vi_client.vim.close if vi_client end end end ################################################################################ def prepare_ds_xml_templates(ds_ids, one_client) ds_ids.each do |ds_id| # Create XML removing old attributes one_ds = OpenNebula::Datastore.new_with_id(ds_id, one_client) rc = one_ds.info raise rc.message if OpenNebula.is_error?(rc) xml_doc = Nokogiri::XML(one_ds.to_xml, nil, "UTF-8"){|c| c.default_xml.noblanks} xml_doc.root.xpath("TEMPLATE/VCENTER_CLUSTER").remove # Replace CLONE_TARGET from NONE to SYSTEM xml_template = xml_doc.root.at_xpath("TEMPLATE") clone_target = xml_template.xpath("CLONE_TARGET").text if !clone_target.empty? xml_template.xpath("CLONE_TARGET").remove xml_template.add_child(xml_doc.create_element("CLONE_TARGET")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"SYSTEM")) end File.open("#{TEMP_DIR}/one_migrate_ds_#{one_ds["ID"]}","w"){|f| f.puts(xml_doc.root.to_s.gsub(/>\s*/, ">").gsub(/\s* ccr_names.size+1 ccr_name = ccr_names[cluster_index-1] rescue nil break end STDOUT.puts STDOUT.puts("-" * 80) STDOUT.puts end # If we could not determine what cluster name is associated to the network # raise an exception. if !ccr_name raise "We could not find the host name associated to vnet #{vnet_name}\n"\ "You may have to import the vCenter cluster using the onevcenter tool." end # Get cluster's ccr ref and host id from its name hosts = hpool.retrieve_xmlelements("HOST[NAME=\"#{ccr_name}\"]") ccr_ref = hosts.first["TEMPLATE/VCENTER_CCR_REF"] if ccr_ref.nil? raise "Vnet #{vnet_name} could not be updated, cannot find cluster's MOREF" end host_id = hosts.first["ID"] vi_client = VCenterDriver::VIClient.new(host_id) vcenter_uuid = vi_client.vim.serviceContent.about.instanceUuid # Get vnet MOREF from vcenter info and the port group's name vnets_with_name = vc_networks[vcenter_uuid].select {|ref, net| net["name"] == vnet_bridge} if vnets_with_name.empty? raise "Could not find vnet in vcenter by its name #{vnet_name}" end # Check how many refs we've found for that port group name vnet_ref = nil if vnets_with_name.size == 1 vnet_ref = vnets_with_name.keys.first else # If there are many possible morefs the admin must select one from a list STDOUT.puts("\n\e[93mWARNING: Manual intervention required!\e[39m") STDOUT.puts("\nWhich vCenter port group is represented by OpenNebula \e[96mvnet #{vnet_name}?\e[39m\n") STDOUT.puts index = 0 vnet_refs = [] vc_networks[vcenter_uuid].each do |ref, net| if net["name"] == vnet_bridge item = RbVmomi::VIM::Network.new(vi_client.vim, ref) # We need the datatacenter info associated to the port group while !item.instance_of? RbVmomi::VIM::Datacenter item = item.parent if item.nil? raise "Could not find the Datacenter for the datastore" end end datacenter = item vnet_refs << ref STDOUT.puts("#{index+1}: Virtual Network #{vnet_name} in #{datacenter.name}") index += 1 end end # Loop until the administrator selects a vnet loop do STDOUT.print("\nFrom the list above, please \e[95mpick one number\e[39m in order to specify the vnet: ") vnet_index = STDIN.gets.strip.to_i next if vnet_index == 0 || vnet_index - 1 < 0 || vnet_index - 1 > vnet_refs.size vnet_ref = vnet_refs[vnet_index-1] rescue nil break if vnet_ref end STDOUT.puts STDOUT.puts("-" * 80) end STDOUT.puts raise "Vnet #{vnet_name} could not be updated, cannot find vnet's MOREF" if vnet_ref.nil? # Prepare vnet's template attributes template = "" template << "VCENTER_NET_REF=\"#{vnet_ref}\"\n" template << "VCENTER_INSTANCE_ID=\"#{vcenter_uuid}\"\n" template << "VCENTER_PORTGROUP_TYPE=\"#{vnet_pg_type}\"\n" template << "VCENTER_CCR_REF=\"#{ccr_ref}\"\n" # Try to update the vnet template rc = one_vnet.update(template, true) raise "Vnet #{vnet_name} could not be updated. Reason #{rc.message}" if OpenNebula.is_error?(rc) # Inform what attributes have been added STDOUT.puts "\nVnet \e[96m#{vnet_name}\e[39m got new attributes:\n" STDOUT.puts "--- VCENTER_NET_REF=#{vnet_ref}\n" STDOUT.puts "--- VCENTER_INSTANCE_ID=#{vcenter_uuid}\n" STDOUT.puts "--- VCENTER_PORTGROUP_TYPE=#{vnet_pg_type}\n" STDOUT.puts "--- VCENTER_CCR_REF=#{ccr_ref}\n" STDOUT.puts # Let's see in which OpenNebula cluster we have to group this vnet if !one_clusters.key?(host_id) raise "Could not find the OpenNebula cluster ID that is going to be associated to host ID: #{host_id}" end # Add vnet to OpenNebula cluster # Check if the vnet is already assigned to the right cluster cluster_id = one_clusters[host_id] cluster_ids = one_vnet.retrieve_xmlelements("CLUSTERS") found_cluster_ids = cluster_ids.select { |cluster| cluster["ID"] == cluster_id } if found_cluster_ids.empty? one_cluster = OpenNebula::Cluster.new_with_id(cluster_id, one_client) rc = one_cluster.addvnet(vnet_id.to_i) if OpenNebula.is_error?(rc) raise "Network #{vnet_name} could not be assigned to cluster ID: #{cluster_id} you should assign this virtual network to that cluster by hand once this script finishes. Reason #{rc.message}" else STDOUT.puts "Vnet \e[96m#{vnet_name}\e[39m has been assigned to cluster ID: #{cluster_id}." end end STDOUT.puts STDOUT.puts "-" * 80 STDOUT.puts # We track what vcenter_ids have been modified so we can create # XML templates later vcenter_ids[:vnet] << one_vnet["ID"] rescue Exception => e raise e ensure vi_client.vim.close if vi_client end end end ################################################################################ def prepare_vnet_xml_templates(vnet_ids, one_client) vnet_ids.each do |vnet_id| # Create XML removing old attributes one_vnet = OpenNebula::VirtualNetwork.new_with_id(vnet_id, one_client) rc = one_vnet.info raise rc.message if OpenNebula.is_error?(rc) xml_doc = Nokogiri::XML(one_vnet.to_xml, nil, "UTF-8"){|c| c.default_xml.noblanks} xml_doc.root.xpath("TEMPLATE/VCENTER_TYPE").remove File.open("#{TEMP_DIR}/one_migrate_vnet_#{vnet_id}","w"){|f| f.puts(xml_doc.root.to_s.gsub(/>\s*/, ">").gsub(/\s*\s*/, ">").gsub(/\s* e raise e ensure vi_client.vim.close if vi_client end end end ################################################################################ def inspect_templates(vc_templates, vc_clusters, one_clusters, tpool, ipool, vnpool, dspool, hpool, one_client, vcenter_ids) # Retrieve all OpenNebula templates associated with PUBLIC_CLOUD=vcenter templates = tpool.retrieve_xmlelements("VMTEMPLATE[TEMPLATE/PUBLIC_CLOUD/TYPE=\"vcenter\"]") templates.each do |template| begin # Refresh pools rc = vnpool.info_all raise "\n ERROR! Could not update vnpool. Reason #{rc.message}" if OpenNebula.is_error?(rc) rc = ipool.info_all raise "\n ERROR! Could not update ipool. Reason #{rc.message}" if OpenNebula.is_error?(rc) rc = dspool.info raise "\n ERROR! Could not update dspool. Reason #{rc.message}" if OpenNebula.is_error?(rc) # Get some variables ccr_ref = nil vcenter_uuid = nil host_id = nil template_name = template["NAME"] template_uuid = template["TEMPLATE/PUBLIC_CLOUD/VM_TEMPLATE"] template_ref = template["TEMPLATE/PUBLIC_CLOUD/VCENTER_REF"] template_cluster = template["TEMPLATE/PUBLIC_CLOUD/HOST"] template_rp = template["TEMPLATE/RESOURCE_POOL"] template_user_rp = template["TEMPLATE/USER_INPUTS/RESOURCE_POOL"] # Check if we can find what vCenter cluster is associated with the # Template if template_cluster # Does a host with the template host name exist? hosts = hpool.retrieve_xmlelements("HOST[NAME=\"#{template_cluster}\"]") if hosts.empty? template_cluster = nil else # Check if we can get the morefs from the OpenNebula host ccr_ref = hosts.first["TEMPLATE/VCENTER_CCR_REF"] vcenter_uuid = hosts.first["TEMPLATE/VCENTER_INSTANCE_ID"] host_id = hosts.first["ID"] vcenter_user = hosts.first["TEMPLATE/VCENTER_USER"] vcenter_pass = hosts.first["TEMPLATE/VCENTER_PASSWORD"] vcenter_host = hosts.first["TEMPLATE/VCENTER_HOST"] template_cluster = nil if !ccr_ref || !vcenter_uuid end end # As we don't know which vCenter cluster is associated with the template # The administrator must select one from a list if !template_cluster hpool_vcenter = hpool.select{|h| h["VM_MAD"] == "vcenter"} if hpool_vcenter.count == 1 template_cluster = hpool_vcenter.first["NAME"] else STDOUT.puts("\n\e[93mWARNING: Manual intervention required!\e[39m") STDOUT.puts("\nWhich vCenter cluster is associated with OpenNebula \e[96mtemplate #{template_name}?\n\e[39m") STDOUT.puts ccr_names = [] hpool_vcenter.each_with_index do |host, index| STDOUT.puts("#{index+1}: #{host["NAME"]} in #{host["TEMPLATE/VCENTER_HOST"]}") ccr_names << host["NAME"] end STDOUT.puts("#{ccr_names.size+1}: None of the above.") loop do STDOUT.print("\nFrom the list above, please \e[95mpick one number\e[39m in order to specify the cluster: ") cluster_index = STDIN.gets.strip.to_i next if cluster_index == 0 || cluster_index - 1 < 0 || cluster_index > ccr_names.size+1 template_cluster = ccr_names[cluster_index-1] rescue nil break end STDOUT.puts STDOUT.puts "-" * 80 STDOUT.puts end if !template_cluster raise "We could not find the host name associated to template #{template_name}\n"\ "You may have to import the OpenNebula host first using onevcenter tool." end # Get host attributes from the name of the cluster associated # to the template hosts = hpool.retrieve_xmlelements("HOST[NAME=\"#{template_cluster}\"]") ccr_ref = hosts.first["TEMPLATE/VCENTER_CCR_REF"] vcenter_uuid = hosts.first["TEMPLATE/VCENTER_INSTANCE_ID"] host_id = hosts.first["ID"] vcenter_user = hosts.first["TEMPLATE/VCENTER_USER"] vcenter_pass = hosts.first["TEMPLATE/VCENTER_PASSWORD"] vcenter_host = hosts.first["TEMPLATE/VCENTER_HOST"] end if ccr_ref.nil? || vcenter_uuid.nil? raise "Template #{template_name} could not be updated, cannot find cluster's MOREF: '#{ccr_ref}'" \ ", or vcenter uuid: '#{vcenter_uuid}'. " end # Create Rbvmomi connection vi_client = VCenterDriver::VIClient.new(host_id) vcenter_uuid = vi_client.vim.serviceContent.about.instanceUuid # We try to check if the template's moref found inside the XML template # is found in the templates retrieved from vcenter only once if template_ref templates_found = vc_templates[vcenter_uuid].select do |ref, value| template_ref == ref end if templates_found.size != 1 template_ref = nil end end # Try to get moref using the templates uuid. Note that that uuid # is not unique templates_same_uuid = {} if !template_ref && template_uuid templates_found = 0 vc_templates[vcenter_uuid].each do |ref, value| if value["config.uuid"] == template_uuid templates_found += 1 templates_same_uuid[ref] = value if templates_found > 1 template_ref = nil else template_ref = ref end end end end # If we could not found the template moref the administrator has to help us if !template_ref STDOUT.puts("\n\e[93mWARNING: Manual intervention required!\e[39m") STDOUT.puts("\nWhich vCenter template is associated with OpenNebula \e[96mtemplate #{template_name}?\n\e[39m") STDOUT.puts index = 0 template_refs = [] if templates_same_uuid.length > 1 # Choose only between those that have the same UUID templates_list = templates_same_uuid else templates_list = vc_templates[vcenter_uuid] end templates_list.each do |ref, t| item = RbVmomi::VIM::VirtualMachine.new(vi_client.vim, ref) folders = [] while !item.instance_of? RbVmomi::VIM::Datacenter item = item.parent if !item.instance_of?(RbVmomi::VIM::Datacenter) if item.name != "vm" folders << item.name else folders << "" end end if item.nil? raise "Could not find the Datacenter for the template" end end datacenter = item location = folders.reverse.join("/") location = "/" if location.empty? template_refs << ref STDOUT.puts("#{index+1}: Template #{t["name"]} in #{datacenter.name} Location: #{location}") index += 1 end STDOUT.puts("#{template_refs.size+1}: None of the above.") loop do STDOUT.print("\nFrom the list above, please \e[95mpick a number\e[39m in order to specify the template: ") template_index = STDIN.gets.strip.to_i next if template_index == 0 || template_index - 1 < 0 || template_index > template_refs.size + 1 template_ref = template_refs[template_index-1] rescue nil break end STDOUT.puts STDOUT.puts "-" * 80 end # Get OpenNebulas's template one_template = OpenNebula::Template.new_with_id(template["ID"], one_client) STDOUT.puts if !template_ref STDOUT.print "Could not upgrade this template. Not found in vCenter. Do you want to remove it? (y/n) " loop do option = STDIN.gets.strip case option when "y" # delete rc = one_template.delete raise "Template #{template["ID"]}: '#{template["NAME"]}' could not be deleted. Reason #{rc.message}" if OpenNebula.is_error?(rc) STDOUT.puts("\nTemplate #{template["ID"]}: '#{template["NAME"]}' has been \e[93mdeleted\e[39m.") break when "n" STDOUT.puts("\nTemplate #{template["ID"]}: '#{template["NAME"]}' is \e[93mbroken\e[39m. Please inspect it manually after the upgrade.") STDOUT.puts("\nPress any key to continue.\n") STDIN.gets break end end next end rc = one_template.info raise "Could not get info for template #{template["ID"]}. Reason: #{rc.message}" if OpenNebula.is_error?(rc) # Find vcenter template in vc_templates vc_template = nil vc_template_object = nil vc_templates[vcenter_uuid].each do |ref, value| if ref == template_ref vc_template = value vc_template_object = RbVmomi::VIM::VirtualMachine.new(vi_client.vim, template_ref) break end end if vc_template.nil? raise "Template #{template_name} could not be updated, cannot find vcenter info for this template" end # Migrate USER_INPUTS/RESOURCE_POOL to USER_INPUTS/VCENTER_RESOURCE_POOL user_inputs = nil if template_user_rp inputs = template.retrieve_xmlelements("TEMPLATE/USER_INPUTS").first.to_hash["USER_INPUTS"] rescue nil if inputs user_inputs = "" user_inputs << "USER_INPUTS=[" inputs.each do |key, value| user_inputs << "#{key}=\"#{value}\",\n" end user_inputs << "VCENTER_RESOURCE_POOL=\"#{template_user_rp}\"\n" user_inputs << "]" end end # Prepare VM template with new attributes template = "" template << "VCENTER_TEMPLATE_REF=\"#{template_ref}\"\n" template << "VCENTER_INSTANCE_ID=\"#{vcenter_uuid}\"\n" template << "VCENTER_CCR_REF=\"#{ccr_ref}\"\n" template << "VCENTER_RESOURCE_POOL=\"#{template_rp}\"\n" if template_rp template << user_inputs if template_user_rp # Try to update VM template rc = one_template.update(template, true) raise "Template #{template_name} could not be updated. Reason #{rc.message}" if OpenNebula.is_error?(rc) # Inform what attributes have been added STDOUT.puts "\nTemplate \e[96m#{template_name}:\e[39m" STDOUT.puts "--- New attribute VCENTER_TEMPLATE_REF=#{template_ref} added\n" STDOUT.puts "--- New attribute VCENTER_INSTANCE_ID=#{vcenter_uuid} added\n" STDOUT.puts "--- New attribute VCENTER_CCR_REF=#{ccr_ref} added\n" STDOUT.puts "--- New attribute VCENTER_RESOURCE_POOL=#{template_rp} added\n" if template_rp STDOUT.puts "--- New attribute USER_INPUTS/VCENTER_RESOURCE_POOL=#{template_user_rp} added\n" if template_user_rp # Prepare template for migration xml_doc = Nokogiri::XML(one_template.to_xml, nil, "UTF-8"){|c| c.default_xml.noblanks} xml_template = xml_doc.root.at_xpath("TEMPLATE") # Retrieve and remove existing disks existing_disks = xml_doc.root.xpath("TEMPLATE/DISK").remove # Retrieve and remove existing nics existing_nics = xml_doc.root.xpath("TEMPLATE/NIC").remove template_id = one_template["ID"] # Discover existing disks and/or nics. Note that datastores will # be created if the images require them. dc = get_dc(vc_template_object) dc_name = dc.name dc_ref = dc._ref vcenter_name = vi_client.host STDOUT.puts "--- Discovering disks and network interfaces inside the template (please be patient)" devices = vc_template["config.hardware.device"] has_spaces = false devices.each do |device| if !(device.class.ancestors.index(RbVmomi::VIM::VirtualDisk)).nil? image_path = device.backing.fileName.sub(/^\[(.*?)\] /, "") if image_path.include?(" ") has_spaces = true break end end end if has_spaces STDOUT.puts STDOUT.puts("\n\e[93mWARNING: Manual intervention required!\e[39m") STDOUT.puts "Template #{one_template["ID"]}: '#{one_template["NAME"]}' is not compatible." STDOUT.puts STDOUT.puts "Images in this template contain spaces, which is not supported." STDOUT.puts "You need to remove the spaces from the paths and import it again" STDOUT.puts "in OpenNebula 5.4." STDOUT.puts STDOUT.puts "Press 'y' to delete the template from OpenNebula." STDOUT.puts "Alternatively press 'q' to quit the premigrator" STDOUT.puts "in order to fix the path before running the premigrator again." STDOUT.puts "(y/q)" STDOUT.puts loop do option = STDIN.gets.strip case option when "y" rc = one_template.delete raise "Template #{one_template["ID"]}: '#{one_template["NAME"]}' could not be deleted. Reason #{rc.message}" if OpenNebula.is_error?(rc) break when "q" exit 0 end end next end unmanaged = template_unmanaged_discover(devices, template_cluster, ccr_ref, vcenter_name, vcenter_uuid, vcenter_user, vcenter_pass, vcenter_host, dc_name, dc_ref, ipool, vnpool, dspool, hpool, one_client, template_ref, vc_template["name"], template_id, one_clusters, vcenter_ids) if !unmanaged[:images].empty? STDOUT.puts "--- Adding DISK elements for discovered disks to new XML" # Create images for unmanaged disks unmanaged[:images].each do |image_id| # Add existing disk to xml disk = xml_template.add_child(xml_doc.create_element("DISK")) disk.add_child(xml_doc.create_element("IMAGE_ID")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{image_id}")) disk.add_child(xml_doc.create_element("OPENNEBULA_MANAGED")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"NO")) end end # Add managed disks after unmanaged disks xml_template.add_child(existing_disks) if !unmanaged[:networks].empty? STDOUT.puts "--- Adding NIC elements for discovered nics to new XML" # Create networks for unmanaged nics unmanaged[:networks].each do |network_id| # Add existing nics to xml nic = xml_template.add_child(xml_doc.create_element("NIC")) nic.add_child(xml_doc.create_element("NETWORK_ID")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{network_id}")) nic.add_child(xml_doc.create_element("OPENNEBULA_MANAGED")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"NO")) end else STDOUT.puts "--- All discovered networks are ready to use" end # Add managed disks after unmanaged disks xml_template.add_child(existing_nics) # Remove SCHED_REQUIREMENTS from TEMPLATE xml_template.xpath("SCHED_REQUIREMENTS").remove # Remove KEEP_DISKS_ON_DONE from TEMPLATE xml_template.xpath("KEEP_DISKS_ON_DONE").remove # Remove PUBLIC_CLOUD section from TEMPLATE xml_template.xpath("PUBLIC_CLOUD").remove # Remove RESOURCE_POOL section from TEMPLATE and USER_INPUTS xml_template.xpath("RESOURCE_POOL").remove xml_template.xpath("USER_INPUTS/RESOURCE_POOL").remove # Remove VCENTER_DATASTORE section from TEMPLATE and USER_INPUTS vcenter_datastore = xml_template.xpath("VCENTER_DATASTORE").text xml_template.xpath("VCENTER_DATASTORE").remove xml_template.xpath("USER_INPUTS/VCENTER_DATASTORE").remove # Replace CUSTOMIZATION_SPEC with VCENTER_CUSTOMIZATION_SPEC customization_spec = xml_template.xpath("CUSTOMIZATION_SPEC").text if !customization_spec.empty? xml_template.xpath("CUSTOMIZATION_SPEC").remove xml_template.add_child(xml_doc.create_element("VCENTER_CUSTOMIZATION_SPEC")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"#{customization_spec}")) end # Remove SCHED_DS_REQUIREMENTS, OpenNebula will choose datastores # using the clusters associated to datastores xml_template.xpath("SCHED_DS_REQUIREMENTS").remove # If vcenter_datastore is a SYSTEM_DS then it's a storage pod # Let's create a SCHED_DS_REQUIREMENTS to force using that datastore ds_id = nil if !vcenter_datastore.empty? ds = find_datastore_by_name(dspool, "#{vcenter_datastore}") ds_id = ds["ID"] ds_type = ds["TEMPLATE/TYPE"] if ds_type == "SYSTEM_DS" sched_ds_req = one_template["TEMPLATE/SCHED_DS_REQUIREMENTS"] if sched_ds_req xml_template.xpath("SCHED_DS_REQUIREMENTS").remove requirements = "ID=#{ds_id} & (#{sched_ds_req})" xml_template.add_child(xml_doc.create_element("SCHED_DS_REQUIREMENTS")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"\"#{requirements}\"")) else # Add a SCHED_DS_REQUIREMENTS to template xml_template.add_child(xml_doc.create_element("SCHED_DS_REQUIREMENTS")).add_child(Nokogiri::XML::CDATA.new(xml_doc,"\"ID=#{ds_id}\"")) end end end #Write new XML template to file xml_doc = xml_doc.root.to_s.gsub(/>\s*/, ">").gsub(/\s* e raise e ensure vi_client.vim.close if vi_client end end end ################################################################################ def inspect_vms(vc_vmachines, vc_templates, vc_clusters, one_clusters, vmpool, ipool, tpool, vnpool, dspool, hpool, one_client, vcenter_ids) # Retrieve vCenter deployed or importer VMs vms = vmpool.retrieve_xmlelements("VM[USER_TEMPLATE/PUBLIC_CLOUD/TYPE=\"vcenter\"]") vms.each do |vm| next if !vm["DEPLOY_ID"] # Ignore undeployed vms begin # Refresh pools rc = vnpool.info_all raise "\n ERROR! Could not update vnpool. Reason #{rc.message}" if OpenNebula.is_error?(rc) rc = ipool.info_all raise "\n ERROR! Could not update ipool. Reason #{rc.message}" if OpenNebula.is_error?(rc) rc = dspool.info raise "\n ERROR! Could not update dspool. Reason #{rc.message}" if OpenNebula.is_error?(rc) # Find vcenter VM in vc_vmachines vm_name = vm["NAME"] vm_id = vm["ID"] # Get cluster's MOREF and name host_id = vm["HISTORY_RECORDS/HISTORY[last()]/HID"] host = OpenNebula::Host.new_with_id(host_id, one_client) rc = host.info raise "\n ERROR! Could not get host info for wild vm disk. Reason #{rc.message}" if OpenNebula.is_error?(rc) ccr_name = host["NAME"] ccr_ref = host["TEMPLATE/VCENTER_CCR_REF"] vcenter_user = host["TEMPLATE/VCENTER_USER"] vcenter_pass = host["TEMPLATE/VCENTER_PASSWORD"] vcenter_host = host["TEMPLATE/VCENTER_HOST"] if !ccr_ref raise "VM #{vm_name} could not be updated, cannot find cluster's MOREF" end # Create vi_client vi_client = VCenterDriver::VIClient.new(host_id) vcenter_uuid = vi_client.vim.serviceContent.about.instanceUuid vcenter_name = vi_client.host # Is VM a wild? vm_wild = vm["TEMPLATE/IMPORTED"] == "YES" # Try to find the vCenter object comparing its uuid with the DEPLOY_ID vc_vmachine = nil vm_ref = nil vc_vmachine_object = nil machines_found = vc_vmachines[vcenter_uuid].select do |ref, value| value["config.uuid"] == vm["DEPLOY_ID"] end if machines_found.size == 0 STDOUT.puts "VM \e[96m#{vm_name}\e[39m could not be migrated, \e[91mcannot find this VM in objects retrieved\e[39m,\n"\ "maybe it was deleted in vCenter but not in OpenNebula?" STDOUT.puts STDOUT.puts "Press any key to continue." STDIN.gets STDOUT.puts STDOUT.puts "-" * 80 STDOUT.puts next end if machines_found.size > 1 # We have several vCenter objects with the same UUID the admin # must help us to know which VM is the one we're looking for vm_refs = [] vm_values = [] index = 0 STDOUT.puts("\n\e[93mWARNING: Manual intervention required!\e[39m") STDOUT.puts("\nWhich vCenter VM is represented by OpenNebula \e[96mVM #{vm_name}?\e[39m\n") STDOUT.puts vc_vmachines[vcenter_uuid].each do |ref, v| if v["config.uuid"] == vm["DEPLOY_ID"] item = RbVmomi::VIM::VirtualMachine.new(vi_client.vim, ref) while !item.instance_of? RbVmomi::VIM::Datacenter item = item.parent if item.nil? raise "Could not find the Datacenter associated to this VM" end end datacenter = item vm_refs << ref vm_values << v STDOUT.puts("#{index+1}: VM #{v["name"]} in #{datacenter.name}") index += 1 end end loop do STDOUT.print("\nFrom the list above, please \e[95mpick up one number\e[39m in order to specify the right VM: ") vm_index = STDIN.gets.strip.to_i next if vm_index == 0 || vm_index - 1 < 0 || vm_index > vm_refs.size vm_ref = vm_refs[vm_index-1] rescue nil vc_vmachine = vm_values[vm_index-1] rescue nil vc_vmachine_object = RbVmomi::VIM::VirtualMachine.new(vi_client.vim, vm_ref) break if vm_ref end STDOUT.puts STDOUT.puts "-" * 80 STDOUT.puts else # We have only found one VM where the DEPLOY_ID matches the config.uuid vc_vmachines[vcenter_uuid].each do |ref, value| if value["config.uuid"] == vm["DEPLOY_ID"] vc_vmachine = value vc_vmachine_object = RbVmomi::VIM::VirtualMachine.new(vi_client.vim, ref) vm_ref = ref break end end end # We have to discriminate between Wild vms and VMs deployed by OpenNebula if vm_wild template_ref = vm_ref # The template ref is the VM's moref else # If the VM was deployed by OpenNebula the ref or uuid are in the USER_TEMPLATE template_ref = vm["USER_TEMPLATE/PUBLIC_CLOUD/VCENTER_REF"] template_uuid = vm["USER_TEMPLATE/PUBLIC_CLOUD/VM_TEMPLATE"] # We try to check if the template's moref found inside the XML template # is found in the templates retrieved from vcenter only once if template_ref templates_found = vc_templates[vcenter_uuid].select do |ref, value| template_ref == ref end if templates_found.size != 1 template_ref = nil end end # Try to get moref using the templates uuid. Note that that uuid # is not unique templates_same_uuid = {} if !template_ref && template_uuid templates_found = 0 vc_templates[vcenter_uuid].each do |ref, value| if value["config.uuid"] == template_uuid templates_found += 1 templates_same_uuid[ref] = value if templates_found > 1 template_ref = nil else template_ref = ref end end end end if !template_ref STDOUT.puts("\n\e[93mWARNING: Manual intervention required!\e[39m") STDOUT.puts("\nWhich vCenter template is associated with OpenNebula \e[96mVM #{vm_name}?\e[39m\n") STDOUT.puts index = 0 template_refs = [] if templates_same_uuid.length > 1 # Choose only between those that have the same UUID templates_list = templates_same_uuid else templates_list = vc_templates[vcenter_uuid] end templates_list.each do |ref, t| item = RbVmomi::VIM::VirtualMachine.new(vi_client.vim, ref) folders = [] while !item.instance_of? RbVmomi::VIM::Datacenter item = item.parent if !item.instance_of?(RbVmomi::VIM::Datacenter) if item.name != "vm" folders << item.name else folders << "" end end if item.nil? raise "Could not find the Datacenter for the template" end end datacenter = item location = folders.reverse.join("/") location = "/" if location.empty? template_refs << ref STDOUT.puts("#{index+1}: Template #{t["name"]} in Datacenter #{datacenter.name} Location: #{location}") index += 1 end STDOUT.puts("#{template_refs.size+1}: None of the above.") loop do STDOUT.print("\nFrom the list above, please \e[95mpick a number\e[39m in order to specify the venter template that this VM was based on: ") template_index = STDIN.gets.strip.to_i next if template_index == 0 || template_index - 1 < 0 || template_index > template_refs.size + 1 template_ref = template_refs[template_index-1] rescue nil break end end STDOUT.puts STDOUT.puts "-" * 80 STDOUT.puts end if !template_ref # This VM doesn't have an associated template any more. Let's # treat it as a wild VM vm_wild = true template_ref = vm_ref end # Get VM's datacenter name dc = get_dc(vc_vmachine_object) dc_name = dc.name dc_ref = dc._ref # Get xml template from tmp with unmanaged disks and nics and new attributes template_id = vm["TEMPLATE/TEMPLATE_ID"] template_xml = nil if !vm_wild template_filename = "#{TEMP_DIR}/one_migrate_template_#{template_id}" if File.exist?("#{template_filename}") template_xml = File.open(template_filename) { |f| Nokogiri::XML(f, nil, "UTF-8"){|c| c.default_xml.noblanks} } end end # Create a Nokogiri XML representing the VM's template xml_doc = Nokogiri::XML(vm.to_xml, nil, "UTF-8"){|c| c.default_xml.noblanks} # Replace VM's deploy_id uuid with moref xml_deploy_id = xml_doc.root.at_xpath("DEPLOY_ID") xml_deploy_id.content = vc_vmachine_object._ref # Inform about the changes STDOUT.puts "VM \e[96m#{vm_name}\e[39m:" STDOUT.puts STDOUT.puts "--- DEPLOY_ID has been changed to #{vc_vmachine_object._ref}" # Retrieve and remove disks and nics from vm existing_disks = xml_doc.root.xpath("TEMPLATE/DISK").remove # Retrieve and remove existing disks existing_nics = xml_doc.root.xpath("TEMPLATE/NIC").remove # Retrieve and remove existing nics # Discover existing disks and/or nics # It will return an extraconfig for VM reconfigure extraconfig = vm_unmanaged_discover(vc_vmachine["config.hardware.device"], xml_doc, template_xml, existing_disks, existing_nics, ccr_name, ccr_ref, vcenter_name, vcenter_uuid, vcenter_user, vcenter_pass, vcenter_host, dc_name, dc_ref, ipool, vnpool, dspool, hpool, one_client, vi_client, vm_wild, vm_id, vc_vmachine["name"], vc_templates[vcenter_uuid], vm_ref, vc_vmachines[vcenter_uuid], template_ref, one_clusters, vcenter_ids) # If VM has TOKEN=YES for CONTEXT we must generate a token.txt file from # the variable openebula.token or try to extract it from context if vm["TEMPLATE/CONTEXT/TOKEN"] == "YES" STDOUT.puts STDOUT.puts "VM #{vm_name} generating token.txt..." onegate_token = vc_vmachine["config.extraConfig"].select{|val| val[:key]=="opennebula.token"}.first.value rescue nil # For older versions try to extract if from context inside VM if !onegate_token context = vc_vmachine["config.extraConfig"].select{|val| val[:key]=="guestinfo.opennebula.context"}.first.value rescue nil if context onegate_token = Base64.decode64(context).split("\n").select{|line| line.start_with?("ONEGATE_TOKEN")}.first[/ONEGATE_TOKEN='(.*?)'/,1] rescue nil end end STDOUT.put "--- Could not extract token from vcenter vm or context section" if !onegate_token File.open("/var/lib/one/vms/#{vm_id}/token.txt",'w'){|f| f.puts(onegate_token)} end # Add opennebula.disk elements to vcenter VM so unmanaged disks are referenced spec = {} spec[:extraConfig] = extraconfig if !extraconfig.empty? vc_vmachine_object.ReconfigVM_Task(:spec => spec).wait_for_completion STDOUT.puts STDOUT.puts "-" * 80 STDOUT.puts rescue Exception => e raise e ensure vi_client.vim.close if vi_client end end end ################################################################################ # Pre-migrator tool # ################################################################################ CommandParser::CmdParser.new(ARGV) do usage "`vcenter_one54_pre` []" description "" version OpenNebulaHelper::ONE_VERSION helper=OpenNebulaHelper::OneHelper.new before_proc do helper.set_client(options) end cmd_options=CommandParser::OPTIONS-[CommandParser::VERBOSE] set :option, cmd_options+OpenNebulaHelper::CLIENT_OPTIONS option CommandParser::OPTIONS main do begin msg = " vCenter pre-migrator tool for OpenNebula 5.4 - Version: 1.0" logo_banner(msg) # Initialize opennebula client one_client = OpenNebula::Client.new() vcenter_instances = [] hpool = OpenNebula::HostPool.new(one_client) rc = hpool.info raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) cpool = OpenNebula::ClusterPool.new(one_client) rc = cpool.info raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) vc_clusters = {} vc_datastores = {} vc_networks = {} vc_vmachines = {} vc_templates = {} banner " PHASE 0 - Before running the script please read the following notes", true STDOUT.puts STDOUT.puts("- Please check that you have set PERSISTENT_ONLY=\"NO\" and REQUIRED_ATTRS=\"\"\n"\ " in you DS_MAD_CONF vcenter inside the /etc/one/oned.conf and that you have\n"\ " restarted your OpenNebula services to apply the new configuration before\n"\ " launching the script.") STDOUT.puts STDOUT.puts("- Edit the file \e[92m/var/lib/one/remotes/datastore/vcenter/rm\e[39m and replace the\n"\ " following lines:\n\n"\ " \e[96mvi_client.delete_virtual_disk(img_src,\n"\ " ds_name)\e[39m \n\n"\ " with the following lines:\n\n"\ " \e[96mif drv_action[\"/DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/VCENTER_IMPORTED\"] != \"YES\"\n"\ " vi_client.delete_virtual_disk(img_src,ds_name) \n"\ " end\e[39m \n\n"\ " in order to avoid that you accidentally remove a virtual hard disk from a template \n"\ " or wild VM when you delete an image.") STDOUT.puts STDOUT.puts("- Note that this script may take some time to perform complex tasks so \e[96mplease be patient.\e[39m") STDOUT.puts STDOUT.puts("- Although this scripts will do its best to be fully automated there may be situations\n"\ " where a manual intervention is needed, in that case a \e[93mWARNING\e[39m will be shown.") STDOUT.puts STDOUT.puts("- The virtual networks that represent port groups found inside existing templates\n"\ " will have an Ethernet address range with 255 MACs in the pool. You may want to\n"\ " change or increase this address range after the pre-migrator tool finishes.") STDOUT.puts STDOUT.puts("- It's advisable to disable the Sunstone user interface before launching this script\n"\ " in order to avoid that OpenNebula objects created by users while\n"\ " the script is running are not pre-migrated by the tool.") STDOUT.puts STDOUT.puts("- This script can be executed as many times as you wish. It will update previous\n"\ " results and XML template will be always overwritten.") STDOUT.puts STDOUT.puts("\e[93mDon't forget to restart OpenNebula if you have made changes!\e[39m") STDOUT.print("\nDo you want to continue? ([y]/n): ") exit! if STDIN.gets.strip.downcase == 'n' banner " PHASE 1 - Retrieve objects from vCenter instances", true STDOUT.puts STDOUT.puts "Inventory objects are being retrieved, \e[96mplease be patient...\e[39m" STDOUT.puts # For each vCenter host we: # - Create a rbvmomi connection # - Generate views for clusters, datastores, networks and VMs hpool.each do |host| next if host['VM_MAD'] != "vcenter" vi_client = VCenterDriver::VIClient.new(host["ID"]) vcenter_uuid = vi_client.vim.serviceContent.about.instanceUuid if vcenter_instances.include?(vcenter_uuid) vi_client.vim.close next end vcenter_instances << vcenter_uuid # Retrieve vCenter Managed Objects vc_clusters[vcenter_uuid] = retrieve_vcenter_clusters(vi_client) vc_datastores[vcenter_uuid] = retrieve_vcenter_datastores(vi_client) vc_networks[vcenter_uuid] = retrieve_vcenter_networks(vi_client) vc_vmachines[vcenter_uuid], vc_templates[vcenter_uuid] = retrieve_vcenter_vms(vi_client) STDOUT.puts "--- #{host["TEMPLATE/VCENTER_HOST"]} \e[92mobjects have been retrieved\e[39m" end STDOUT.puts STDOUT.puts "All vCenter objects have been retrieved, thanks for your patience." # Control what objects id have been modified vcenter_ids = {} vcenter_ids[:host] = [] vcenter_ids[:ds] = [] vcenter_ids[:vnet] = [] vcenter_ids[:image] = [] banner " PHASE 2 - Add new attributes to existing hosts", true add_new_host_attrs(vc_clusters, hpool, one_client, vcenter_ids) banner " PHASE 3 - Create OpenNebula clusters if needed", true STDOUT.puts one_clusters = create_new_clusters(vc_clusters, hpool, cpool, one_client) dspool = OpenNebula::DatastorePool.new(one_client) rc = dspool.info raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) rc = hpool.info # Update host pool to get new attributes raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) rc = cpool.info # Update cluster pool to get new clusters raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) extended_message = " - Add new attributes to datastores\n"\ " - Create SYSTEM datastores if needed\n"\ " - Assign datastores to OpenNebula Clusters" banner " PHASE 4 - Inspect existing datatores ", true, extended_message inspect_datastores(vc_datastores, vc_clusters, one_clusters, dspool, hpool, one_client, vcenter_ids) rc = dspool.info # Refresh datastore pool raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) vnpool = OpenNebula::VirtualNetworkPool.new(one_client) rc = vnpool.info_all raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) extended_message = " - Add new attributes to vnets\n"\ " - Assign vnets to OpenNebula Clusters" banner " PHASE 5 - Add new attributes to existing vnets", true, extended_message inspect_networks(vc_networks, vc_clusters, one_clusters, vnpool, hpool, one_client, vcenter_ids) ipool = OpenNebula::ImagePool.new(one_client) rc = ipool.info_all raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) banner " PHASE 6 - Add new attributes to existing images", true add_new_image_attrs(ipool, one_client, vcenter_ids) rc = ipool.info_all raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) tpool = OpenNebula::TemplatePool.new(one_client) rc = tpool.info_all raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) extended_message = " - Add new attributes to existing templates\n"\ " - Discover nics and disks inside templates\n"\ " - Import datastores where discovered virtual disks are found if needed.\n"\ " - Create images for discovered virtual hard disks\n"\ " - Create vnets for discovered port groups\n"\ " - Prepare XML VM templates removing old or deprecated attributes\n" banner " PHASE 7 - Inspect existing VM templates", true, extended_message inspect_templates(vc_templates, vc_clusters, one_clusters, tpool, ipool, vnpool, dspool, hpool, one_client, vcenter_ids) vmpool = OpenNebula::VirtualMachinePool.new(one_client) rc = vmpool.info_all raise "Error contacting OpenNebula #{rc.message}" if OpenNebula.is_error?(rc) extended_message = " - Add new attributes to existing VMs\n"\ " - Discover nics and disks inside VMs\n"\ " - Import datastores where discovered virtual disks are found if needed.\n"\ " - Create images for discovered virtual hard disks\n"\ " - Create vnets for discovered port groups\n"\ " - Prepare XML VM templates removing old or deprecated attributes\n"\ " - Reconfigure vCenter VM to add unmanaged disks and nics references\n"\ " - DEPLOY_ID will get VM's managed object reference\n" banner " PHASE 8 - Inspect existing VMs", true STDOUT.puts inspect_vms(vc_vmachines, vc_templates, vc_clusters, one_clusters, vmpool, ipool, tpool, vnpool, dspool, hpool, one_client, vcenter_ids) if !vcenter_ids[:host].empty? banner " PHASE 9 - Prepare XML templates for hosts without deprecated attributes", true prepare_host_xml_templates(vcenter_ids[:host], one_clusters, one_client) end if !vcenter_ids[:ds].empty? banner " PHASE 10 - Prepare XML templates for datastores without deprecated attributes", true prepare_ds_xml_templates(vcenter_ids[:ds], one_client) end if !vcenter_ids[:vnet].empty? banner " PHASE 11 - Prepare XML templates for vnets without deprecated attributes", true prepare_vnet_xml_templates(vcenter_ids[:vnet], one_client) end if !vcenter_ids[:image].empty? banner " PHASE 12 - Prepare XML templates for images without deprecated attributes", true prepare_image_xml_templates(vcenter_ids[:image], hpool, one_client) end puts "" exit_code 0 rescue Exception => e STDERR.puts "An error occurred when pre-migrating OpenNebula:\n"\ "#{e.message}\"\n#{e.backtrace}" exit -1 end end end