Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions lib/dolly/replication.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require 'dolly/replication/database'

module Dolly
module Replication
include Dolly::Replication::Database
end
end
16 changes: 16 additions & 0 deletions lib/dolly/replication/database.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require "dolly/requests/base"
require "dolly/db_config"

module Dolly
module Replication
module Database
include Dolly::DbConfig

def database
options = {'name' => '_replicator'}
replicator_env = env.merge options
Requests::Base.new(replicator_env)
end
end
end
end
118 changes: 4 additions & 114 deletions lib/dolly/request.rb
Original file line number Diff line number Diff line change
@@ -1,119 +1,9 @@
require "httparty"
require "dolly/bulk_document"
require "dolly/simple_replicator"

module Dolly

class Request
include HTTParty
REQUIRED_KEYS = %w/host port name/.freeze

attr_accessor :database_name, :host, :port, :bulk_document

def initialize options = {}
REQUIRED_KEYS.each do |key|
raise Dolly::MissingRequestConfigSettings.new(key) unless options[key]
end

@host = options["host"]
@port = options["port"]
@database_name = options["name"]
@username = options["username"]
@password = options["password"]
@protocol = options["protocol"]

@bulk_document = Dolly::BulkDocument.new []
self.class.base_uri "#{protocol}://#{host}:#{port}"
end

def get resource, data = nil
q = {query: values_to_json(data)} if data
request :get, full_path(resource), q
end

def stats
request :get, "/#{database_name}"
end

def put resource, data
request :put, full_path(resource), {body: data}
end

def post resource, data
request :post, full_path(resource), {body: data}
end

def delete resource
request :delete, full_path(resource), {}
end

def attach resource, attachment_name, data, headers = {}
data = StringIO.new(data) if data.is_a?(String)
request :put, attachment_path(resource, attachment_name), {body: data, headers: headers}
end

def protocol
@protocol ||= 'http'
end

def uuids opts = {}
tools("_uuids", opts)["uuids"]
end

def all_docs data = {}
data = values_to_json data.merge( include_docs: true )
request :get, full_path('_all_docs'), {query: data}
end

def request method, resource, data = nil
data ||= {}
data.merge!(basic_auth: auth_info) if auth_info.present?
headers = { 'Content-Type' => 'application/json' }
headers.merge! data[:headers] if data[:headers]
response = self.class.send method, resource, data.merge(headers: headers)
log_request(resource, response.code) if Dolly.log_requests?
if response.code == 404
raise Dolly::ResourceNotFound
elsif (400..600).include? response.code
raise Dolly::ServerError.new( response )
else
response
end
end

private
def tools path, opts = nil
data = {}
q = "?#{CGI.unescape(opts.to_query)}" unless opts.blank?
data.merge!(basic_auth: auth_info) if auth_info.present?
JSON::parse self.class.get("/#{path}#{q}", data)
end

def auth_info
return nil unless @username.present?
{username: @username, password: @password}
end

def values_to_json hash
hash.reduce({}){|h, v| h[v.first] = v.last.to_json; h}
end

def full_path resource
"/#{database_name}/#{resource}"
end

def attachment_path resource, attachment_name
"#{full_path(resource)}/#{attachment_name}"
end

def log_request resource, response_code
log_value = ->(resource, response_code) { "Query: #{resource}, Response Code: #{response_code}" }
case response_code
when 200..399
Dolly.logger.info log_value[resource, response_code]
when 400..600
Dolly.logger.warn log_value[resource, response_code]
end
class Request < Dolly::Requests::Base
def replicate! target_db, opts={}
Dolly::SimpleReplicator.new(self, target_db, opts).replicate!
end
end

end
121 changes: 121 additions & 0 deletions lib/dolly/requests/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
require "httparty"
require "dolly/bulk_document"

module Dolly

module Requests
class Base
include HTTParty
REQUIRED_KEYS = %w/host port name/.freeze

attr_accessor :database_name, :host, :port, :bulk_document

def initialize options = {}
REQUIRED_KEYS.each do |key|
raise Dolly::MissingRequestConfigSettings.new(key) unless options[key]
end

@host = options["host"]
@port = options["port"]
@database_name = options["name"]
@username = options["username"]
@password = options["password"]
@protocol = options["protocol"]

@bulk_document = Dolly::BulkDocument.new []
self.class.base_uri "#{protocol}://#{host}:#{port}"
end

def get resource, data = nil
q = {query: values_to_json(data)} if data
request :get, full_path(resource), q
end

def stats
request :get, "/#{database_name}"
end

def put resource, data
request :put, full_path(resource), {body: data}
end

def post resource, data
request :post, full_path(resource), {body: data}
end

def delete resource
request :delete, full_path(resource), {}
end

def attach resource, attachment_name, data, headers = {}
data = StringIO.new(data) if data.is_a?(String)
request :put, attachment_path(resource, attachment_name), {body: data, headers: headers}
end

def protocol
@protocol ||= 'http'
end

def uuids opts = {}
tools("_uuids", opts)["uuids"]
end

def all_docs data = {}
data = values_to_json data.merge( include_docs: true )
request :get, full_path('_all_docs'), {query: data}
end

def request method, resource, data = nil
data ||= {}
data.merge!(basic_auth: auth_info) if auth_info.present?
headers = { 'Content-Type' => 'application/json' }
headers.merge! data[:headers] if data[:headers]
response = self.class.send method, resource, data.merge(headers: headers)
log_request(resource, response.code) if Dolly.log_requests?
if response.code == 404
raise Dolly::ResourceNotFound
elsif (400..600).include? response.code
raise Dolly::ServerError.new( response )
else
response
end
end

private
def tools path, opts = nil
data = {}
q = "?#{CGI.unescape(opts.to_query)}" unless opts.blank?
data.merge!(basic_auth: auth_info) if auth_info.present?
JSON::parse self.class.get("/#{path}#{q}", data)
end

def auth_info
return nil unless @username.present?
{username: @username, password: @password}
end

def values_to_json hash
hash.reduce({}){|h, v| h[v.first] = v.last.to_json; h}
end

def full_path resource
"/#{database_name}/#{resource}"
end

def attachment_path resource, attachment_name
"#{full_path(resource)}/#{attachment_name}"
end

def log_request resource, response_code
log_value = ->(resource, response_code) { "Query: #{resource}, Response Code: #{response_code}" }
case response_code
when 200..399
Dolly.logger.info log_value[resource, response_code]
when 400..600
Dolly.logger.warn log_value[resource, response_code]
end
end
end
end

end
29 changes: 29 additions & 0 deletions lib/dolly/simple_replicator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'dolly/replication/database'

module Dolly
class SimpleReplicator
include Dolly::Replication::Database

attr_reader :source_db, :target_db

def initialize source_db, target_db, opts={}
#@opts = opts #TODO add options for difference replications
@source_db = source_db
@target_db = target_db
end

def replicate!
database.request :post, '/_replicate', body: request_body
end

private

def request_body
{
source: source_db.database_name,
target: target_db.database_name
}.to_json
end

end
end
30 changes: 30 additions & 0 deletions test/request_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,34 @@ def setup
Dolly::Request.new @default_settings.dup.delete('port')
end
end

class ReplicateTest < RequestTest
test 'posts a replication request to the replicator database' do
db = Dolly::Request.new @default_settings
other_db = Dolly::Request.new @default_settings.merge({'name' => 'database_2'})
resp = {
"ok" => true,
"session_id" => "8fa6f7bfdad58a9aa229d6c482a06012",
"source_last_seq" => 5,
"replication_id_version" => 3,
"history" => [
{
"session_id" => "8fa6f7bfdad58a9aa229d6c482a06012",
"start_time" => Date.today.to_s,
"end_time" => Date.today.to_s,
"start_last_seq" => 0,
"end_last_seq" => 5,
"recorded_seq" => 5,
"missing_checked" => 4,
"missing_found" => 0,
"docs_read" => 0,
"docs_written" => 0,
"doc_write_failures" => 0
}
]
}
FakeWeb.register_uri :post, "http://localhost:5984/_replicate", body: resp.to_json
assert db.replicate!(other_db)
end
end
end