# Install the MongoDB gem %w{ mongo bson_ext }.each do |mongo_gem| gem_install = gem_package mongo_gem do action :nothing end gem_install.run_action(:install) end Gem.clear_paths require 'mongo' # Get a list of other nodes in the same replica set REPL_SET = node[:mongodb][:repl_set] srch_trm = "mongodb_repl_set:#{REPL_SET}" REPLICA_PEERS = search(:node, srch_trm).to_ary.collect {|host| host[:fqdn] } REPLICA_PEERS.delete node[:fqdn] ruby_block "Configure mongoDB replication" do block do Chef::Log.info "Configuring mongoDB replication..." require 'mongo' ########################################################################## # HELPER FUNCTIONS - should be in a library, but the 'mongo' require fails # Returns a BSON::OrderedHash from a hash (the keys are sorted) def self.ordered_hash_from_hash(regular_hash) ordered_hash = BSON::OrderedHash.new regular_hash.keys.sort.each do |key| ordered_hash[key.to_sym] = regular_hash[key] end ordered_hash end # returns the results of mongoDB's 'isMaster' shell command def is_master(connection) connection['admin'].command(ordered_hash_from_hash({:isMaster => true})) end ########################################################################## # STEP 1: wait for the server to become available, and get current status local_db = master_status = nil my_hostport = "#{node[:fqdn]}:#{node[:mongodb][:port]}" Chef::Log.info "Connecting to mongodb://localhost:#{node[:mongodb][:port]}..." while (local_db == nil) or (master_status == nil) begin local_db = Mongo::Connection.from_uri( "mongodb://localhost:#{node[:mongodb][:port]}", :logger => Chef::Log, :slave_ok => true) master_status = is_master(local_db) rescue Exception => e Chef::Log.warn "Error getting local master status: #{e.message}" localdb_files = Dir["#{node[:mongodb][:db_path]}/local*"].sort Chef::Log.info "Waiting for replication log to initilize? "+ "(#{localdb_files.size} local.* files in #{node[:mongodb][:db_path]})" sleep 20 end end # STEP 2: configure replication based on current status if master_status['ismaster'] or master_status['secondary'] Chef::Log.info "Replication is already configured: #{master_status.inspect}" else Chef::Log.info "Replication is not configured (#{master_status['info']})" while is_master(local_db)['info'] =~ /local.system.replset .*EMPTYUNREACHABLE/ Chef::Log.info "Replication is not configured: #{is_master(local_db)['info']}" sleep 10 end if REPLICA_PEERS.empty? # Configure as the lone master Chef::Log.info "No other servers in replica set #{REPL_SET}; becoming master" Chef::Log.info "Setting master to: #{my_hostport}" local_db['admin'].command(ordered_hash_from_hash( :replSetInitiate => { "_id" => node[:mongodb][:repl_set], "members" => [{ "_id" => 0, "votes" => 2, "host" => my_hostport }] } )) else # Configure as a slave Chef::Log.info "Found replica peers: #{REPLICA_PEERS.join ','}" replication_stared = false REPLICA_PEERS.each do |server| if not replication_stared Chef::Log.info "Retrieving replication settings from #{server}..." peer_db = Mongo::Connection.from_uri( "mongodb://#{server}:#{node[:mongodb][:port]}", :logger => Chef::Log, :slave_ok => true) repl_settings = peer_db['local']["system.replset"].find_one Chef::Log.info "Replication settings for #{server}: "+ repl_settings.inspect if is_master(peer_db)['ismaster'] Chef::Log.info "Starting replication from master: #{server}..." active_peers = repl_settings['members'].collect {|m| m['host'] } if active_peers.include? my_hostport Chef::Log.error "Host #{my_hostport} already in replica set!" else repl_settings["version"] += 1 # increment config version max_id = repl_settings['members'].inject(0) do |max, peer| peer['_id'] > max ? peer['_id'] : max end repl_settings['members'].push( {'host' => my_hostport, '_id' => max_id+1}) bson = ordered_hash_from_hash(:replSetReconfig => repl_settings) Chef::Log.info "Pushing replication settings: #{repl_settings.inspect}" peer_db['admin'].command(bson) replication_stared = true end end end end end end # STEP 3: wait for the local replication status to be OK while not (is_master(local_db)['ismaster'] or is_master(local_db)['secondary']) Chef::Log.info "Waiting for local replication (#{is_master(local_db)['info']})" sleep 10 end Chef::Log.info("Local replication status: #{is_master(local_db).inspect}") end end