Advertisement
Guest User

Untitled

a guest
Aug 15th, 2017
490
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 11.63 KB | None | 0 0
  1. #!/usr/bin/ruby
  2.  
  3. require 'rubygems'
  4. require 'rdialog'
  5. require 'sqlite3'
  6. require 'fastercsv'
  7. require 'pathname'
  8.  
  9. @DB_NAME = "cloud.db"
  10.  
  11. # The RDialog library left out the --form option, which is unacceptable,
  12. # so I've decided to add this functionality myself. Perhaps later on
  13. # I can fork it and fix it up in the actual library, but this is easier.
  14. RDialog.class_eval do
  15.     def form(text, items, height=0, width=0, fheight=0)
  16.         temp_file = Tempfile.new('tmp')
  17.  
  18.         command = option_string() + "--form \ " + "\"#{text.to_s}\"" + "\ "
  19.         command += height.to_i.to_s + " " + width.to_i.to_s + " " + fheight.to_i.to_s + "\ "
  20.        
  21.         items.each do |item|
  22.             command += "\"#{item[0].to_s}\" #{item[1].to_i.to_s} #{item[2].to_i.to_s} "
  23.             command += "\"#{item[3].to_s}\" #{item[4].to_i.to_s} #{item[5].to_i.to_s} "
  24.             command += "#{item[6].to_i.to_s} #{item[7].to_i.to_s} \ "
  25.         end
  26.  
  27.         command += "2> #{temp_file.path}"
  28.  
  29.         success = system(command)
  30.  
  31.         results = Array.new
  32.         if success
  33.             results = temp_file.read.split("\n")
  34.             temp_file.close
  35.             return results
  36.         else
  37.             temp_file.close
  38.             return success
  39.         end
  40.     end
  41. end
  42.  
  43. # This sets up a few dialog options we want to stick around
  44. @dialog = RDialog.new
  45. @dialog.backtitle = "VM Manager"
  46. @dialog.shadow = false
  47.  
  48. # Our main menu! Everything goes through here.
  49. def menu_win
  50.     # Build up the menu items...
  51.     items = Array.new
  52.     items.push(["Create New VM",    ""])
  53.     items.push(["Modify VM",        ""])
  54.     items.push(["About",            ""])
  55.     items.push(["Quit",             ""])
  56.  
  57.     # Display them, and figure out what to execute
  58.     selected = @dialog.menu("VM Manager", items)
  59.  
  60.     case selected
  61.         when "Create New VM"
  62.             create_win
  63.         when "Modify VM"
  64.             modify_win
  65.         when "About"
  66.             about_win
  67.         else
  68.             exit 0
  69.     end
  70. end
  71.  
  72. # From this menu the user will be able to bring online a new VM
  73. # with whatever parameters they desire! Huzzah!
  74. def create_win
  75.  
  76.     # TODO: fix behavior associated with VMs from existing images
  77.  
  78.     # Ask for the host to bring the VM up on...
  79.     # We've got a nifty list of hosts in a CSV file, so lets read it
  80.     nodes = Hash.new
  81.     FasterCSV.foreach('hosts.csv') do |row|
  82.         nodes[row[0]] = row[1]
  83.     end
  84.    
  85.     # Pressing CTRL+c exits this dialog but keeps the program going,
  86.     # which is no good. This is just a simple defense against that.
  87.     node = nil
  88.     while(node == nil)
  89.         # Now we prompt the user with our list of nodes
  90.         sorted_nodes = nodes.to_a.sort { |a,b| a[1] <=> b[1] }
  91.         node_result = @dialog.menu("Select a node to bring up VM on:", sorted_nodes, 30, 60)
  92.  
  93.         if(node_result == false)
  94.             menu_win
  95.         end
  96.  
  97.         # Keep in mind, node_result is just the hostname, not the IP:
  98.         node = nodes[node_result].strip
  99.     end
  100.  
  101.     # We have one last thing to prompt the user for:
  102.     # whether or not they want to clone the HD or just
  103.     # use the one passed in by the path.
  104.     clone = nil
  105.     while(clone == nil)
  106.         prompt = "Is this VM going to be cloned from a template disk image, or should I not clone it?"
  107.         clone_result = @dialog.menu(prompt, [["Yes, clone it", ""], ["No, don't clone it", ""]], 10, 60)
  108.         case clone_result
  109.             when "Yes, clone it"
  110.                 clone = true
  111.             when "No, don't clone it"
  112.                 clone = false
  113.             else
  114.                 menu_win
  115.         end
  116.     end
  117.  
  118.     # If we're cloning the HD, follow this path:
  119.     if(clone)
  120.         clone_win(node)
  121.     else
  122.         # We're not cloning the HD! Take this path.
  123.         import_win(node)
  124.     end
  125.  
  126.     menu_win
  127. end
  128.  
  129. # This gets called when the user wants to import an OVA/OVF file
  130. def import_win(node)
  131.     items = Array.new
  132.     items += [["Operating System" , 1, 1, "", 1, 25, 30, 100]]
  133.     items += [["OVA/OVF Path"     , 2, 1, "", 2, 25, 30, 100]]
  134.     items += [["Network Interface", 3, 1, "", 3, 25, 30, 100]]
  135.     items += [["VM Number (0-99)" , 4, 1, "", 4, 25, 30, 100]]
  136.  
  137.     results = @dialog.form("Import a VM", items, 25, 60, 16)
  138.  
  139.     if(results == 0)
  140.         menu_win
  141.     end
  142.  
  143. # BEGIN ERROR CHECKING ==========================================================================
  144.     # Check the validity of the VM number
  145.     # We'll need to see which VM numbers are already taken first...
  146.     db = SQLite3::Database.new(@DB_NAME)
  147.     existing_nums = db.execute("select id from vms;").map { |i| i[0] }
  148.     db.close
  149.  
  150.     if(results[3].match(/^\d{2}$/) == nil)
  151.         @dialog.msgbox("ERROR\n\tInvalid VM number. Please Enter a number between 0 and 99.")
  152.         create_win
  153.     else if(existing_nums.include?(results[3]))
  154.         @dialog.msgbox("ERROR\n\tA VM with #{results[3]} already exists. Please choose a different number.")
  155.         create_win
  156.     end
  157.  
  158.     # Make sure the OS type is a valid VBox OS type
  159.     # First, grab a list of valid OS types from Virtual Box...
  160.     os_types = `vboxmanage list ostypes`.scan(/^ID:\s*(\w*)$/).map { |i| i[0] }
  161.     if(!os_types.include?(results[0]))
  162.         @dialog.msgbox("ERROR\n\tInvalid OS type. Please check the list of valid VBox OS types with 'vboxmanage list ostypes' first.")
  163.         create_win
  164.     end
  165.  
  166.     # Make sure the path to the OVA/OVF file exists on the node
  167.     disk_exists = system("ssh root@#{node} 'ls #{results[1]}'")
  168.     if(!disk_exists)
  169.         @dialog.msgbox("ERROR\n\tSpecified OVA/OVF file does not exist on #{node}")
  170.         create_win
  171.     end
  172.  
  173.     mac = "00C5CE3150#{results[3].to_s}"
  174.     vm_name = Pathname.new(results[1]).basename.to_s
  175.  
  176.     command = "vboxmanage import #{results[1]};"
  177.     command += "vboxmanage modifyvm --macaddress1 \"#{mac}\" --nic1 bridged --bridgeadapter1 \"#{results[2]}\";"
  178.     command += "vboxheadless --startvm #{vm_name}"
  179.  
  180.     success = system("ssh root@#{node} '#{command}'")
  181.  
  182.     if(!success)
  183.         @dialog.msgbox("ERROR\n\tCould not connect to host and/or modify/start the VM.")
  184.         create_win
  185.     else
  186.         @dialog.msgbox("Success! #{vm_name} is now running on #{node}")
  187.         menu_win
  188.     end
  189. end
  190.  
  191. # This gets called when we want to clone a VM from an existing disk
  192. def clone_win(node)
  193.     # Here we build up our giant form:
  194.     items = Array.new
  195.     items += [["Hostname",          1, 1, "",       1, 25, 30, 100]]
  196.     items += [["Username",          2, 1, "",       2, 25, 30, 100]]
  197.     items += [["Operating System",  3, 1, "Ubuntu", 3, 25, 30, 100]]
  198.     items += [["RAM (MB)",          4, 1, "512",    4, 25, 30, 100]]
  199.     items += [["VRAM (MB)",         5, 1, "32",     5, 25, 30, 100]]
  200.     items += [["Network Interface", 6, 1, "br0",    6, 25, 30, 100]]
  201.     items += [["Template VDI Path", 7, 1, "/home/cloud315/VMs/lamp_template.vdi", 7, 25, 30, 100]]
  202.     items += [["New Disk Location", 8, 1, "/home/cloud315/VMs/", 8, 25, 30, 100]]
  203.     items += [["Setup Script Path", 9, 1, "config_lamp.sh",       9, 25, 30, 100]]
  204.     items += [["VM Number (0-99)", 10, 1, "",      10, 25, 30, 100]]
  205.  
  206.     # And now it gets sent off to my awesome form thing!
  207.     results = @dialog.form("Set up a new VM", items, 25, 60, 16)
  208.    
  209.     # If they chose "cancel" then put 'em back at the beginning
  210.     if(results == 0)
  211.         menu_win
  212.     end
  213.  
  214. # BEGIN ERROR CHECKING ==========================================================================
  215.     # Check the validity of the VM number
  216.     # We'll need to see which VM numbers are already taken first...
  217.     db = SQLite3::Database.new(@DB_NAME)
  218.     existing_nums = db.execute("select id from vms;").map { |i| i[0] }
  219.     db.close
  220.  
  221.     if(results[9].match(/^\d{2}$/) == nil)
  222.         @dialog.msgbox("ERROR\n\tInvalid VM number. Please Enter a number between 0 and 99.")
  223.         create_win
  224.     else if(existing_nums.include?(results[9]))
  225.         @dialog.msgbox("ERROR\n\tA VM with #{results[9]} already exists. Please choose a different number.")
  226.         create_win
  227.     end
  228.  
  229.     # Check the hostname (can only contain a-z, 0-9, and dashes)
  230.     if(results[0].match(/[^a-zA-Z\-0-9]+/) != nil)
  231.             @dialog.msgbox("ERROR\n\tInvalid hostname. Hostnames can only contain characters a-z, digits 0-9, and dashes.")
  232.             create_win
  233.     end
  234.  
  235.     # Make sure the OS type is a valid VBox OS type
  236.     # First, grab a list of valid OS types from Virtual Box...
  237.     os_types = `vboxmanage list ostypes`.scan(/^ID:\s*(\w*)$/).map { |i| i[0] }
  238.     if(!os_types.include?(results[2]))
  239.         @dialog.msgbox("ERROR\n\tInvalid OS type. Please check the list of valid VBox OS types with 'vboxmanage list ostypes' first.")
  240.         create_win
  241.     end
  242.    
  243.     # Check the RAM and VRAM
  244.     if(results[3].match(/[^0-9]/) != nil)
  245.         @dialog.msgbox("ERROR\n\tInvalid RAM parameter. RAM must be specified as the number of MB of RAM.")
  246.         create_win
  247.     end
  248.  
  249.     if(results[4].match(/[^0-9]/) != nil)
  250.         @dialog.msgbox("ERROR\n\tInvalid VRAM parameter. VRAM must be specified as the number of MB of VRAM.")
  251.         create_win
  252.     end
  253.  
  254.     # Make sure the path to the template disk image exists on the host
  255.     template_exists = system("ssh root@#{node} 'ls #{results[6]}'")
  256.     if(!template_exists)
  257.         @dialog.msgbox("ERROR\n\tTemplate image does not exist on #{node}! Make sure your path is correct.")
  258.         create_win
  259.     end
  260.  
  261.     # Make sure the directory we're putting the cloned image in exists
  262.     clone_dir_exists = system("ssh root@#{node} 'ls #{results[7]}'")
  263.     if(!clone_dir_exists)
  264.         @dialog.msgbox("ERROR\n\tDirectory specified for cloned disk image does not exist! Make sure the path you entered is correct.")
  265.         create_win
  266.     end
  267.  
  268.     # Verify that the setup script exists
  269.     setup_script_exists = system("ls #{results[8]}")
  270.     if(!setup_script_exists)
  271.         @dialog.msgbox("ERROR\n\tSpecified setup script does not exist!")
  272.         create_win
  273.     end
  274.  
  275. # END ERROR CHECKING ==========================================================================
  276.  
  277.     mac = "00C5CE3150#{results[9].to_s}"
  278.  
  279.     # Now that we have the info we need, we build up a giant command:
  280.     command = "ssh root@#{node} /home/cloud315/bin/vm_manage/create_vm.sh "
  281.     command += "-h \"#{results[0]}\" -u \"#{results[1]}\" "
  282.     command += "-o \"#{results[2]}\" -M \"#{results[3]}\" -V \"#{results[4]}\" "
  283.     command += "-n \"#{results[5]}\" -v \"#{results[6]}\" -d \"#{results[7]}/\" "
  284.     command += "-m \"#{mac}\""
  285.  
  286.     command += "; exit 0"
  287.  
  288.     # ...and execute said giant command
  289.     puts node
  290.     puts command
  291.     result = system(command)
  292.  
  293.     # NOTE: Commented out until further notice, since command doesn't return true
  294.     # If things didn't work out, let the user know
  295.     # if(!result)
  296.     #   @dialog.msgbox("ERROR\n\tUnable to reach host #{node}")
  297.     #   create_win
  298.     # end
  299.  
  300.     # Now we run the config script on the VM.
  301.     # We HAVE to do it from the root node, because the template disk
  302.     # images need to have the keys of the root node in the authorized
  303.     # keys file of the root user so we can ssh in without a password.
  304.    
  305.     # scp over the config script
  306.     password = `pwgen`.strip
  307.     puts "PASSWORD GENERATED: #{password}"
  308.     puts "scp #{results[8]} root@192.168.1.2#{results[9]}:/root/"
  309.     success = system("scp #{results[8]} root@192.168.1.2#{results[9]}:/root/")
  310.  
  311.     # Inform the user if something went wrong
  312.     if(!success)
  313.         @dialog.msgbox("ERROR\n\tUnable to SCP config script to VM at 192.168.1.2#{results[9]}")
  314.         create_win
  315.     end
  316.  
  317.     # Execute the config script...
  318.     ssh_cmd = "ssh root@192.168.1.2#{results[9]} /root/#{results[8]}"
  319.     ssh_cmd += " -h \"#{results[0]}\" -u \"#{results[1]}\" -p \"#{password}\""
  320.     puts ssh_cmd
  321.     success = system(ssh_cmd)
  322.  
  323.     # Inform the user if something went wrong
  324.     if(!success)
  325.         @dialog.msgbox("ERROR\n\tUnable to execute config script on VM at 192.168.1.2#{results[9]}")
  326.         create_win
  327.     end
  328.  
  329.     @dialog.msgbox("Congrats! Your new VM is ready!
  330.     Hostname: #{results[0]}\nUsername: #{results[1]}\nPassword: #{password}")
  331. end
  332.  
  333. # You'll be able to change settings on currently running VMs
  334. # from this window. Also a work in progress.
  335. def modify_win
  336.     # First, we need to build a list of VMs to display for the user...
  337.     db = SQLite::Database.new(@DB_NAME)
  338.     vms = db.execute("SELECT hostname, host_ip, status from vms;")
  339.     vms.map! { |vm| "#{vm[0]} - on #{vm[1]} (#{status.gsub(/\(.*\)/, "").strip})" }
  340.     menu_win
  341. end
  342.  
  343. def about_win
  344.     @dialog.msgbox("About:\n\tThe VM Manager was written by Eric Wood and Robert Schumacher for Dr. Liu's CSCE 315 class.")
  345.     menu_win
  346. end
  347.  
  348. menu_win
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement