module Ironfan # # Ironfan::Server methods that handle Fog action # Server.class_eval do def fog_create_server step(" creating cloud server", :green) lint_fog launch_desc = fog_launch_description Chef::Log.debug(JSON.pretty_generate(launch_desc)) safely do @fog_server = Ironfan.fog_connection.servers.create(launch_desc) end end def lint_fog unless cloud.image_id then raise "No image ID found: nothing in Chef::Config[:ec2_image_info] for AZ #{self.default_availability_zone} flavor #{cloud.flavor} backing #{cloud.backing} image name #{cloud.image_name}, and cloud.image_id was not set directly. See https://github.com/infochimps-labs/ironfan/wiki/machine-image-(AMI)-lookup-by-name - #{cloud.list_images}" end unless cloud.image_id then cloud.list_flavors ; raise "No machine flavor found" ; end end def fog_launch_description user_data_hsh = if client_key.body then cloud.user_data.merge({ :client_key => client_key.body }) else cloud.user_data.merge({ :validation_key => cloud.validation_key }) ; end # description = { :image_id => cloud.image_id, :flavor_id => cloud.flavor, :vpc_id => cloud.vpc, :subnet_id => cloud.subnet, :groups => cloud.security_groups.keys, :key_name => cloud.keypair.to_s, # Fog does not actually create tags when it creates a server. :tags => { :name => self.fullname, :cluster => cluster_name, :facet => facet_name, :index => facet_index, }, :user_data => JSON.pretty_generate(user_data_hsh), :block_device_mapping => block_device_mapping, :availability_zone => self.default_availability_zone, :monitoring => cloud.monitoring, # permanence is applied during sync } if needs_placement_group? ui.warn "1.3.1 and earlier versions of Fog don't correctly support placement groups, so your nodes will land willy-nilly. We're working on a fix" description[:placement] = { 'groupName' => cloud.placement_group.to_s } end description end # # Takes key-value pairs and idempotently sets those tags on the cloud machine # def fog_create_tags(fog_obj, desc, tags) tags['Name'] ||= tags['name'] if tags.has_key?('name') tags_to_create = tags.reject{|key, val| fog_obj.tags[key] == val.to_s } return if tags_to_create.empty? step(" tagging #{desc} with #{tags_to_create.inspect}", :green) tags_to_create.each do |key, value| Chef::Log.debug( "tagging #{desc} with #{key} = #{value}" ) safely do Ironfan.fog_connection.tags.create({ :key => key, :value => value.to_s, :resource_id => fog_obj.id }) end end end def fog_address address_str = self.cloud.public_ip or return Ironfan.fog_addresses[address_str] end def discover_volumes! result = self.class.fields[:volumes].type.new volumes.each_pair do |vol_name, definition| next if definition.fog_volume next if Ironfan.chef_config[:cloud] == false vol = definition.dup vol.fog_volume = Ironfan.fog_volumes.find do |fv| ( # matches the explicit volume id (vol.volume_id && (fv.id == vol.volume_id) ) || # OR this server's machine exists, and this volume is attached to # it, and in the right place ( fog_server && fv.server_id && vol.device && (fv.server_id == fog_server.id) && (fv.device.to_s == vol.device.to_s) ) || # OR this volume is tagged as belonging to this machine ( fv.tags.present? && (fv.tags['server'] == self.fullname) && (fv.tags['device'] == vol.device.to_s) ) ) end next unless vol.fog_volume vol.volume_id(vol.fog_volume.id) unless vol.volume_id.present? vol.availability_zone(vol.fog_volume.availability_zone) unless vol.availability_zone.present? check_server_id_pairing(vol.fog_volume, vol.desc) result[vol.name] = vol end write_attribute(:volumes,result) end def attach_volumes return unless in_cloud? discover_volumes! return if volumes.empty? step(" attaching volumes") volumes.each_pair do |vol_name, vol| next if vol.volume_id.blank? || (vol.attachable != :ebs) if (not vol.in_cloud?) then Chef::Log.debug("Volume not found: #{vol.desc}") ; next ; end if (vol.has_server?) then check_server_id_pairing(vol.fog_volume, vol.desc) ; next ; end step(" - attaching #{vol.desc} -- #{vol.inspect}", :blue) safely do vol.fog_volume.device = vol.device vol.fog_volume.server = fog_server end end end def ensure_placement_group return unless needs_placement_group? pg_name = cloud.placement_group.to_s desc = "placement group #{pg_name} for #{self.fullname} (vs #{Ironfan.placement_groups.inspect}" return if Ironfan.placement_groups.include?(pg_name) safely do step(" creating #{desc}", :blue) unless_dry_run{ Ironfan.fog_connection.create_placement_group(pg_name, 'cluster') } Ironfan.placement_groups[pg_name] = { 'groupName' => pg_name, 'strategy' => 'cluster' } end pg_name end def needs_placement_group? cloud.flavor_info[:placement_groupable] end def associate_public_ip address = self.cloud.public_ip return unless self.in_cloud? && address desc = "elastic ip #{address} for #{self.fullname}" if (fog_address && fog_address.server_id) then check_server_id_pairing(fog_address, desc) ; return ; end safely do step(" assigning #{desc}", :blue) Ironfan.fog_connection.associate_address(self.fog_server.id, address) end end def check_server_id_pairing thing, desc return unless thing && thing.server_id && self.in_cloud? type_of_thing = thing.class.to_s.gsub(/.*::/,"") if thing.server_id != self.fog_server.id ui.warn "#{type_of_thing} mismatch: #{desc} is on #{thing.server_id} not #{self.fog_server.id}: #{thing.inspect.gsub(/\s+/m,' ')}" false else Chef::Log.debug("#{type_of_thing} paired: #{desc}") true end end def set_instance_attributes return unless self.in_cloud? && (not self.cloud.permanent.nil?) desc = "termination flag #{permanent?} for #{self.fullname}" # the EC2 API does not surface disable_api_termination as a value, so we # have to set it every time. safely do step(" setting #{desc}", :blue) unless_dry_run do Ironfan.fog_connection.modify_instance_attribute(self.fog_server.id, { 'DisableApiTermination.Value' => permanent?, }) end true end end end class ServerSlice def sync_keypairs step("ensuring keypairs exist") keypairs = servers.map{|svr| [svr.cluster.cloud.keypair, svr.cloud.keypair] }.flatten.map(&:to_s).reject(&:blank?).uniq keypairs = keypairs - Ironfan.fog_keypairs.keys keypairs.each do |keypair_name| keypair_obj = Ironfan::Ec2Keypair.create!(keypair_name) Ironfan.fog_keypairs[keypair_name] = keypair_obj end end # Create security groups, their dependencies, and synchronize their permissions def sync_security_groups step("ensuring security groups exist and are correct") security_groups.each{|name,group| group.run } end end end