# 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 'rubygems' require 'erb' require 'yaml' require 'restr' require 'xmlrpc/client' module GenomeBootstrap class GenomeRepo attr_reader :machines, :machine_types_url, :cobbler_profiles, :fqdn, :repo def initialize(repo) @repo = repo @fqdn = @repo @genomed = "http://#{@fqdn}/genome" @machine_types_url = @genomed + "/machine_types.html" @cobblerd = XMLRPC::Client.new2("http://#{@fqdn}/cobbler_api_rw") # TODO: figure out a way to pass these values in @token = @cobblerd.call2("login", "cobbler", "password")[1] @cobbler_profiles = @cobblerd.call2("get_profiles", @token)[1].map {|p| p["name"]} end def machines @machines ||= fetch_data end def facts_for(type) # Don't want to pull in the genome lib. See below fore more detail. facts = restr_get("#{@genomed}/machine_types/#{type}.xml", "fact") return (!facts or facts.empty?) ? {} : facts.map do |f| def f.name self["name"] end def f.desc self["description"] end # Here's where we make use of the DSL's simple templating f.instance_variable_set(:@repo, @repo) def f.default # handle there case where there is no default self["default"].gsub("%repo%", @repo) rescue "" end f end end def bootstrapping_cobbler_profiles @cobbler_profiles.delete_if{|profile| profile.include?("__Appliance") } end def classes_for(type) restr_get("#{@genomed}/machine_types/#{type}.xml", "class") end def fetch_data # Build out some mock objects so we don't have to couple to the genome # lib. That lib has lots of funky metaprogramming stuff in it and it # might need to be changed in the future. The fewer tools relying on it # the better. machine_types = restr_get("#{@genomed}/machine_types.xml", "machine_type") return machine_types.map do |m| def m.name self["name"] end def m.desc self["description"] end m end end def post_yaml(node_name, machine_type, yaml) Restr.post("#{@genomed}/nodes", :node_name => node_name, :yaml => yaml) end def add_system_to_cobbler(name, params, email) system_id = @cobblerd.call2("new_system", @token)[1] @cobblerd.call2('modify_system', system_id, 'name', name, @token) @cobblerd.call2('modify_system', system_id, 'profile', params["cobbler_profile"], @token) # These values ultimately get used by genome-firstboot ksmeta = "fqdn=#{name} " + "genome_repo=#{@fqdn}" ksmeta << " email=#{email}" if email @cobblerd.call2('modify_system', system_id, 'ksmeta', ksmeta, @token) @cobblerd.call2('save_system', system_id, @token) end def register_machine(machine_fqdn, config, email="") machine_type = config["parameters"]["genome_machine_type"] post_yaml(machine_fqdn, machine_type, YAML.dump(config)) add_system_to_cobbler(machine_fqdn, config["parameters"], email) end def get_system_ip(system_name, max_tries=1) status = 1.upto(max_tries) do # Technically the yield should be after the remote call, but it would # trip up the 'find' yield if block_given? data = @cobblerd.call2("get_status")[1].find do |ip, s| s[2] == "system:%s" % system_name && s[5] =~ /^installing/ end # This returns from the block break data unless data.nil? end return status.nil? ? nil : status[0] end # This is a workaround for a Restr "feature". Restr does not set # 'forcearray => true' for it's XmlSimple instance. This means exactly # what you would think. We always want to work with arrays since puppet's # external nodes cares what data structures are used. def restr_get(url, xml_node=nil) data = xml_node ? Restr.get(url)[xml_node] : Restr.get(url) return case data when nil, Array data else Array.new << data end end end class CloudController attr_reader :repo, :cloud # Create a new CloudController with the given server name (prefix or fqdn) # and repo (full GenomeRepo object) def initialize(name, repo) @repo = repo # If @repo is not an GenomeRepo object, assume it's the # fqdn of a repo if not @repo.is_a? GenomeRepo then @repo = GenomeRepo.new(@repo) end @cloud = "http://#{name}/cloud" end def create_machine(system_name, email) return Restr.post("#{@cloud}/koan", :system_name => system_name, :repo => @repo.fqdn, :email => email).split(":") end def update_hostname(id, hostname) Restr.post("#{@cloud}/koan", :machine_id => id, :hostname => hostname) end end end