# Copyright (C) 2008 Red Hat, Inc # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # a long with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. require 'yaml' require 'fileutils' require 'rubygems' require 'picnic' require 'reststop' require 'restr' require 'xmlrpc/client' require 'sqlite3' require 'open3' require 'cloudmasterd/syncer' require 'date' Camping.goes :Cloudmasterd Cloudmasterd.picnic! module Cloudmasterd::Helpers class Cobblerd def initialize(repo) @repo = repo @xmlrpc = XMLRPC::Client.new2("http://#{@repo}/cobbler_api_rw") @token = @xmlrpc.call2("login", "cobbler", "password")[1] end def _call(method, *args) return @xmlrpc.call2(method, *args)[1] end def distros _call("get_distros", @token) end def profiles _call("get_profiles", @token) end def systems _call("get_systems", @token) end def repos _call("get_repos", @token) end def distro(name) _call("get_distro", name, @token) end def distro_for_koan(name) _call("get_distro_for_koan", name, @token) end def profile(name) _call("get_profile", name, @token) end def profile_for_koan(name) _call("get_profile_for_koan", name, @token) end def system(name) _call("get_system", name, @token) end def system_for_koan(name) _call("get_system_for_koan", name, @token) end def repo(name) _call("get_repo", name, @token) end def repo_for_koan(name) _call("get_repo_for_koan", name, @token) end end end module Cloudmasterd::Models class Cloud < Base; end class Machine < Base; end class CreateTheBasics < V 1.0 def self.up create_table :cloudmasterd_cloud do |t| t.column :name, :string, :null => false, :limit => 255 t.column :virttype, :string, :limit => 255 t.column :memory, :integer, :null => false t.column :added_date, :datetime, :null => true end create_table :cloudmasterd_machine do |t| t.column :name, :string, :null => false, :limit => 255 t.column :email, :string, :null => false, :limit => 255 t.column :cloud, :string, :null => false, :limit => 255 t.column :state, :string, :null => false, :limit => 255 t.column :repo, :string, :limit => 255 t.column :hostname, :string, :limit => 255 t.column :created_date, :datetime end end def self.down drop_table :cloudmasterd_cloud drop_table :cloudmasterd_machine end end end module Cloudmasterd::Controllers class Koan < REST 'koan' # Retrieve the cobbler profile for the given profile name def _get_cobbler_profile(system_name, repo) cobblerd = Cloudmasterd::Helpers::Cobblerd.new(repo) cobbler_system = cobblerd.system(system_name) return cobblerd.profile(cobbler_system["profile"]) end # Retrieve the cobbler distro for the given profile name def _get_cobbler_distro(profile_name, repo) cobblerd = Cloudmasterd::Helpers::Cobblerd.new(repo) return cobblerd.distro(profile_name) end # Find the best host on which to create a VM def _get_best_host(ram, arch) return `func-find-resources -m #{ram} -a #{arch}`.chomp() end # run the appropriate koan command to create the given "fqdn" # on the given "host" and "vol_group" def _koan(host, system_name, repo) # Run the koan process output = `func "#{host}" call virt install #{repo} #{system_name} True #{system_name} /images` # Throw an exception if the process failed raise output unless $?.success? # Make the new host autostart `func "#{host}" call virt autostart #{system_name}` end # POST /koan def create if input.machine_id Machine.find(input.machine_id).update_attribute(:hostname, input.hostname) else system_name = input.system_name repo = input.repo email = input.email cobbler_profile = _get_cobbler_profile(system_name, repo) cobbler_distro = _get_cobbler_distro(cobbler_profile["distro"], repo) # Synchronize access before making the func calls Syncer::lock do @host = _get_best_host(cobbler_profile["virt_ram"], cobbler_distro["arch"]) begin _koan(@host, system_name, repo) @machine = Machine.create :name => system_name, :repo => repo, :email => email, :cloud => @host, :state => "Installing", :created_date => DateTime.now() render :_koan rescue Exception => e @exception = e render :_error end end end end def destroy(name) Syncer::lock do Machine.find(:all, :conditions => "name = '#{name}'").each do |machine| # If the machine is missing, don't bother remotely trying to cleanup unless machine.state == "missing" then # Destroy the virtual machine `func "#{machine.cloud}" call command run "virsh destroy #{machine.name}"` `func "#{machine.cloud}" call command run "virsh undefine #{machine.name}"` # Remove the auto start file if it exists `func "#{machine.cloud}" call command run "rm -rf /etc/xen/auto/#{machine.name}"` # Remove the image file `func "#{machine.cloud}" call command run "rm -rf /images/#{machine.name}-disk0"` end # Delete the DB record machine.destroy end end redirect R(Status) end end class Status < REST 'status' def list @machines = {} Machine.find(:all, :select => "distinct email").map{|x| x.email}.each do |email| @machines[email] = Machine.find(:all, :conditions => "email = '#{email}'", :order => 'cloud') end @memory = Cloud.sum('memory') @cloud_machines = Cloud.find(:all) @cloud_names = {} @cloud_machines.each do |machine| printed_name = machine.name if machine.virttype printed_name += (machine.virttype == "Xen") ? " (Xen)": " (KVM)" end @cloud_names[machine.name] = printed_name end render :status end end end module Cloudmasterd::Views def initialize(*args) super(*args) @x = Builder::XmlMarkup.new(:indent => 1) end module HTML def layout html do head do title 'Genome Cloud Master Controller' link :rel => 'stylesheet', :type => 'text/css', :href => "/styles.css", :media => 'screen' end body do div.header! do div.headerLogo! {} end div.body! do div.content! do div.inner! do self << yield end end end end end end def status div.cloud! do h2 "Available cloud memory: #{@memory}" table do tr do th 'Cloud Instance' th 'Memory' th 'Added Date' end @cloud_machines.each do |machine| tr do td @cloud_names[machine.name] td machine.memory unless machine.added_date == nil then td machine.added_date.strftime("%m/%d/%Y") else td 'Unknown' end end end end end div.machines! do @machines.keys.each do |email| div.user do h2 email table do tr do th 'Name' th 'Cloud Instance' th 'State' th 'Created Date' th '' end @machines[email].each do |machine| name = machine.hostname ? "#{machine.name} (#{machine.hostname})" : machine.name tr do td do if machine.repo a name, :href=>"http://#{machine.repo}/genome/nodes/#{machine.name}.html" else a name end end td @cloud_names[machine.cloud] td machine.state unless machine.created_date == nil then td machine.created_date.strftime("%m/%d/%Y") else td 'Unknown' end td do form(:method => :delete, :action => "/cloud" + R(Cloudmasterd::Controllers::Koan, "#{machine.name}").to_s) do input :type => :submit, :value => 'Destroy' end end end end end end end end end def _koan "#{@machine.id}:#{@host}" end def _error "ERROR: #{@exception.to_s}" end end default_format :HTML module YAML CONTENT_TYPE = 'text/plain' def layout yield end end module XML def layout yield end end end def Cloudmasterd.create #ActiveRecord::Base.logger = Logger.new(STDOUT) #ActiveRecord::Base.colorize_logging = true ActiveRecord::Base.pluralize_table_names = false ActiveRecord::Base.establish_connection( :adapter => "sqlite3", :database => "/var/lib/cloudmaster.db" ) Cloudmasterd::Models.create_schema :assume => (Cloudmasterd::Models::Machine.table_exists? ? 1.0 : 0.0) end Cloudmasterd.start_picnic