right_infrastructure_agent 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +10 -0
- data/README.rdoc +65 -0
- data/Rakefile +86 -0
- data/lib/right_infrastructure_agent.rb +26 -0
- data/lib/right_infrastructure_agent/command_constants.rb +34 -0
- data/lib/right_infrastructure_agent/global_object_replicator_sink.rb +337 -0
- data/lib/right_infrastructure_agent/global_object_replicator_source.rb +117 -0
- data/lib/right_infrastructure_agent/infrastructure_auth_client.rb +88 -0
- data/lib/right_infrastructure_agent/infrastructure_helpers.rb +85 -0
- data/lib/right_infrastructure_agent/login_policy_factory.rb +137 -0
- data/lib/right_infrastructure_agent/models_helper.rb +483 -0
- data/lib/right_infrastructure_agent/rainbows_agent_controller.rb +192 -0
- data/lib/right_infrastructure_agent/scripts/infrastructure_agent_deployer.rb +278 -0
- data/right_infrastructure_agent.gemspec +54 -0
- data/spec/global_object_replicator_sink_spec.rb +305 -0
- data/spec/global_object_replicator_source_spec.rb +113 -0
- data/spec/infrastructure_auth_client_spec.rb +140 -0
- data/spec/infrastructure_helpers_spec.rb +80 -0
- data/spec/login_policy_factory_spec.rb +279 -0
- data/spec/models_helper_spec.rb +546 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +85 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ca12e04c5b64e16c354a7031c39599a36d0c431d
|
4
|
+
data.tar.gz: 463c200718bfe3c8f01817497764942c2a44b173
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 90d0a95a758ecc476cb5b78069e4a9914a1f4f61671855a9bb7f0217254a1b46c1b494bf52a9e35a6bae1a0ed01a3832a16d4f84b06f25ed8751dfe7ef8bcd69
|
7
|
+
data.tar.gz: 51a45fc9db03ee26f6e0b1b6d536e1e3060ff49f2682cf00565c4f17f63b7b9c4f28582276e2f3f8936646de8cb19ae763e6066b3d5c08c62a320510c4326299
|
data/LICENSE
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Copyright (c) 2009-2011 RightScale, Inc, All Rights Reserved Worldwide.
|
2
|
+
#
|
3
|
+
# THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
|
4
|
+
# AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
|
5
|
+
# reproduction, modification, or disclosure of this program is
|
6
|
+
# strictly prohibited. Any use of this program by an authorized
|
7
|
+
# licensee is strictly subject to the terms and conditions,
|
8
|
+
# including confidentiality obligations, set forth in the applicable
|
9
|
+
# License Agreement between RightScale.com, Inc. and
|
10
|
+
# the licensee.
|
data/README.rdoc
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
= RightInfrastructureAgent
|
2
|
+
|
3
|
+
= DESCRIPTION
|
4
|
+
|
5
|
+
== Synopsis
|
6
|
+
|
7
|
+
RightInfrastructureAgent provides the foundation for RightScale infrastructure
|
8
|
+
servers that connect into the RightScale system via the RabbitMQ message bus.
|
9
|
+
It extends RightAgent in configuration, monitoring, and packet handling areas
|
10
|
+
as needed generically by infrastructure servers.
|
11
|
+
|
12
|
+
Maintained by the RightScale Teal Team
|
13
|
+
|
14
|
+
== Interface
|
15
|
+
|
16
|
+
A RightInfrastructureAgent exposes its services via actors and methods that are
|
17
|
+
invoked by requests packets it receives via its message queue. The actors
|
18
|
+
provided are specific to the given agent.
|
19
|
+
|
20
|
+
RightInfrastructureAgent comes with several library modules for forming basic
|
21
|
+
command line tools in addition to those provided in RightAgent:
|
22
|
+
|
23
|
+
* <b>infrastructure_agent_deployer</b>: Build configuration file for running an agent (rad tool)
|
24
|
+
* <b>infrastructure_agent_controller</b>: Manage an agent that has been configured (rnac tool)
|
25
|
+
|
26
|
+
== Supported Configuration
|
27
|
+
|
28
|
+
RightInfrastructureAgent has been tested on EC2 instances running CentOS 5.2 and Ubuntu 8.10.
|
29
|
+
|
30
|
+
== Work in Progress
|
31
|
+
|
32
|
+
RightInfrastructureAgent is work in progress.
|
33
|
+
|
34
|
+
= ADDITIONAL RESOURCES
|
35
|
+
|
36
|
+
* [1] RabbitMQ is http://www.rabbitmq.com/documentation.html
|
37
|
+
|
38
|
+
= CHANGELOG
|
39
|
+
|
40
|
+
* 0.15.1: The server_superuser role is now taken into account when building policies
|
41
|
+
|
42
|
+
= LICENSE
|
43
|
+
|
44
|
+
<b>RightAgent</b>
|
45
|
+
|
46
|
+
Copyright:: Copyright (c) 2011 RightScale, Inc.
|
47
|
+
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
49
|
+
a copy of this software and associated documentation files (the
|
50
|
+
'Software'), to deal in the Software without restriction, including
|
51
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
52
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
53
|
+
permit persons to whom the Software is furnished to do so, subject to
|
54
|
+
the following conditions:
|
55
|
+
|
56
|
+
The above copyright notice and this permission notice shall be
|
57
|
+
included in all copies or substantial portions of the Software.
|
58
|
+
|
59
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
60
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
61
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
62
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
63
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
64
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
65
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#-- -*-ruby-*-
|
2
|
+
# Copyright: Copyright (c) 2010 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'rubygems'
|
25
|
+
require 'bundler/setup'
|
26
|
+
require 'fileutils'
|
27
|
+
require 'rake'
|
28
|
+
require 'rspec/core/rake_task'
|
29
|
+
require 'rdoc/task'
|
30
|
+
require 'rake/gempackagetask'
|
31
|
+
require 'rake/clean'
|
32
|
+
|
33
|
+
task :default => 'spec'
|
34
|
+
|
35
|
+
# == Gem packaging == #
|
36
|
+
|
37
|
+
desc "Package gem"
|
38
|
+
gemtask = Rake::GemPackageTask.new(Gem::Specification.load("right_infrastructure_agent.gemspec")) do |package|
|
39
|
+
package.package_dir = ENV['PACKAGE_DIR'] || 'pkg'
|
40
|
+
package.need_zip = true
|
41
|
+
package.need_tar = true
|
42
|
+
end
|
43
|
+
|
44
|
+
directory gemtask.package_dir
|
45
|
+
|
46
|
+
CLEAN.include(gemtask.package_dir)
|
47
|
+
|
48
|
+
# == Unit tests == #
|
49
|
+
|
50
|
+
RSPEC_OPTS = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
|
51
|
+
|
52
|
+
desc 'Run unit tests'
|
53
|
+
RSpec::Core::RakeTask.new do |t|
|
54
|
+
t.rspec_opts = RSPEC_OPTS
|
55
|
+
end
|
56
|
+
|
57
|
+
namespace :spec do
|
58
|
+
desc 'Run unit tests with RCov'
|
59
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
60
|
+
t.rspec_opts = RSPEC_OPTS
|
61
|
+
t.rcov = true
|
62
|
+
t.rcov_opts = %q[--exclude "spec"]
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'Print Specdoc for all unit tests'
|
66
|
+
RSpec::Core::RakeTask.new(:doc) do |t|
|
67
|
+
t.rspec_opts = ["--format", "documentation"]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# == Documentation == #
|
72
|
+
|
73
|
+
desc 'Generate API documentation to doc/rdocs/index.html'
|
74
|
+
Rake::RDocTask.new do |rd|
|
75
|
+
rd.rdoc_dir = 'doc/rdocs'
|
76
|
+
rd.main = 'README.rdoc'
|
77
|
+
rd.rdoc_files.include 'README.rdoc', 'lib/**/*.rb'
|
78
|
+
end
|
79
|
+
CLEAN.include('doc/rdocs')
|
80
|
+
|
81
|
+
# == Emacs integration ==
|
82
|
+
|
83
|
+
desc 'Rebuild TAGS file for emacs'
|
84
|
+
task :tags do
|
85
|
+
sh 'rtags -R lib spec'
|
86
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright (c) 2009-2011 RightScale, Inc, All Rights Reserved Worldwide.
|
2
|
+
#
|
3
|
+
# THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
|
4
|
+
# AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
|
5
|
+
# reproduction, modification, or disclosure of this program is
|
6
|
+
# strictly prohibited. Any use of this program by an authorized
|
7
|
+
# licensee is strictly subject to the terms and conditions,
|
8
|
+
# including confidentiality obligations, set forth in the applicable
|
9
|
+
# License Agreement between RightScale.com, Inc. and the licensee.
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'right_agent'
|
13
|
+
|
14
|
+
unless defined?(RIGHT_INFRASTRUCTURE_AGENT_BASE_DIR)
|
15
|
+
RIGHT_INFRASTRUCTURE_AGENT_BASE_DIR =
|
16
|
+
File.normalize_path(File.join(File.dirname(__FILE__), 'right_infrastructure_agent'))
|
17
|
+
end
|
18
|
+
|
19
|
+
require File.normalize_path(File.join(RIGHT_INFRASTRUCTURE_AGENT_BASE_DIR, 'models_helper'))
|
20
|
+
require File.normalize_path(File.join(RIGHT_INFRASTRUCTURE_AGENT_BASE_DIR, 'login_policy_factory'))
|
21
|
+
require File.normalize_path(File.join(RIGHT_INFRASTRUCTURE_AGENT_BASE_DIR, 'infrastructure_helpers'))
|
22
|
+
require File.normalize_path(File.join(RIGHT_INFRASTRUCTURE_AGENT_BASE_DIR, 'infrastructure_auth_client'))
|
23
|
+
require File.normalize_path(File.join(RIGHT_INFRASTRUCTURE_AGENT_BASE_DIR, 'rainbows_agent_controller'))
|
24
|
+
require File.normalize_path(File.join(RIGHT_INFRASTRUCTURE_AGENT_BASE_DIR, 'global_object_replicator_sink'))
|
25
|
+
require File.normalize_path(File.join(RIGHT_INFRASTRUCTURE_AGENT_BASE_DIR, 'global_object_replicator_source'))
|
26
|
+
require File.normalize_path(File.join(RIGHT_INFRASTRUCTURE_AGENT_BASE_DIR, 'command_constants'))
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2014 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#
|
23
|
+
|
24
|
+
module RightScale
|
25
|
+
|
26
|
+
# Extend this class to support infrastructure agents
|
27
|
+
class CommandConstants
|
28
|
+
|
29
|
+
# Ports used for command protocol
|
30
|
+
BASE_TESTER_AGENT_SOCKET_PORT = 69000
|
31
|
+
BASE_ROUTER_SOCKET_PORT = 79000
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,337 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2011-2013 RightScale, Inc, All Rights Reserved Worldwide.
|
3
|
+
#
|
4
|
+
# THIS PROGRAM IS CONFIDENTIAL AND PROPRIETARY TO RIGHTSCALE
|
5
|
+
# AND CONSTITUTES A VALUABLE TRADE SECRET. Any unauthorized use,
|
6
|
+
# reproduction, modification, or disclosure of this program is
|
7
|
+
# strictly prohibited. Any use of this program by an authorized
|
8
|
+
# licensee is strictly subject to the terms and conditions,
|
9
|
+
# including confidentiality obligations, set forth in the applicable
|
10
|
+
# License Agreement between RightScale.com, Inc. and
|
11
|
+
# the licensee.
|
12
|
+
#++
|
13
|
+
|
14
|
+
module RightScale
|
15
|
+
|
16
|
+
# This module is for use as a mixin in an HTTP controller that handles requests from
|
17
|
+
# a global object replication source (e.g., library) to synchronize its replicas.
|
18
|
+
#
|
19
|
+
# This module expects the following to be defined:
|
20
|
+
# - logger - variable pointing to the standard logger in use
|
21
|
+
# - global_object_replicator_sink - method returning type of replication sink, e.g., 'core'
|
22
|
+
module GlobalObjectReplicatorSink
|
23
|
+
|
24
|
+
include RightScale::InfrastructureHelpers
|
25
|
+
include RightScale::ModelsHelper
|
26
|
+
|
27
|
+
# Update the specified right_site core object (class_name, id) so that its attributes
|
28
|
+
# match those of the global object
|
29
|
+
#
|
30
|
+
# @param :source [String] Replication source, e.g., 'library' or 'wasabi'
|
31
|
+
# @param :class_name [String] Name of the object class being updated
|
32
|
+
# @param :id [Integer] Unique identifier of the object being updated
|
33
|
+
# @param :schema_version [Integer] Schema version of the model (class)
|
34
|
+
# @param :global_object_version [String] Global object version of the model (class)
|
35
|
+
# @param :attrs [String] All attributes defined for the object represented by :class_name
|
36
|
+
# and :id in yaml
|
37
|
+
#
|
38
|
+
# @return [NilClass] nil
|
39
|
+
#
|
40
|
+
# @raise [RightScale::Exceptions::QueryFailure] Failed to update object
|
41
|
+
# @raise [RightScale::Exceptions::RetryableError] Query failed but may be retried
|
42
|
+
def handle_global_object_change
|
43
|
+
source = params[:source]
|
44
|
+
if (object_class = replicate_sink_class(params[:class_name]))
|
45
|
+
logger.info("GlobalObjectReplica: Processing #{source} global object change for #{object_class.name}/#{params[:id]}")
|
46
|
+
|
47
|
+
id = to_int_or_nil(params[:id])
|
48
|
+
schema_version = to_int_or_nil(params[:schema_version])
|
49
|
+
action = "replicator sink handle_global_object_change for #{object_class.name}/#{id} " +
|
50
|
+
"(schema version #{schema_version}, global object version #{params[:global_object_version]})"
|
51
|
+
success = query(action, :include_backtrace_in_last_error => true) do
|
52
|
+
object_class.handle_global_object_change(id, schema_version, params[:global_object_version], params[:attrs])
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
raise RightScale::Exceptions::QueryFailure.new(@last_error) unless success
|
57
|
+
else
|
58
|
+
logger.warn("GlobalObjectReplica: Ignoring replicator sink handle_global_object_change from #{source} " +
|
59
|
+
"for unknown will_replicate sink class #{params[:class_name].inspect}")
|
60
|
+
end
|
61
|
+
render_nothing
|
62
|
+
end
|
63
|
+
|
64
|
+
# Compare checksum parameter of a replicated table and, if they do not match,
|
65
|
+
# begin the synchronization process by sending verify_replica_range to replicator source
|
66
|
+
#
|
67
|
+
# @param :source [String] Replication source, e.g., 'library' or 'wasabi'
|
68
|
+
# @param :class_name [String] Name of a class that acts_as_global_object_replica
|
69
|
+
# @param :checksum_type [String] Type of checksum: only 'global_object_version_sum'
|
70
|
+
# @param :checksum_value [Integer, NilClass] Sum of global_object_versions for the table
|
71
|
+
# in the source database
|
72
|
+
#
|
73
|
+
# @return [NilClass] nil
|
74
|
+
#
|
75
|
+
# @raise [ArgumentError] Unknown replication source
|
76
|
+
# @raise [RightScale::Exceptions::QueryFailure] Failed to complete verify replicas query
|
77
|
+
# @raise [RightScale::Exceptions::RetryableError] Query failed but may be retried
|
78
|
+
def verify_replicas
|
79
|
+
source = params[:source]
|
80
|
+
if (replica_class = replicate_sink_class(params[:class_name]))
|
81
|
+
checksum_type = params[:checksum_type]
|
82
|
+
checksum_value = params[:checksum_value]
|
83
|
+
|
84
|
+
success = query("verify_replicas for #{replica_class.name}", :email_errors => true) do
|
85
|
+
if replica_class.calculate_global_object_checksum(checksum_type) == checksum_value
|
86
|
+
logger.info("GlobalObjectReplica: Verified #{source} #{replica_class.name} global_object_version_sum.")
|
87
|
+
set_global_object_replication_status(replica_class.name, checksum_type, :completed => true)
|
88
|
+
else
|
89
|
+
logger.info("GlobalObjectReplica: Verification of #{source} #{replica_class.name} global_object_version_sum failed. " +
|
90
|
+
"Beginning synchronization.")
|
91
|
+
set_global_object_replication_status(replica_class.name, checksum_type, :start => true)
|
92
|
+
|
93
|
+
max_id = replica_class.max_id
|
94
|
+
verify_next_replica_range(source, replica_class, checksum_type, max_id, 0, max_id)
|
95
|
+
end
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
unless success
|
100
|
+
set_global_object_replication_status(replica_class.name, checksum_type, :failed => true)
|
101
|
+
raise RightScale::Exceptions::QueryFailure.new(@last_error)
|
102
|
+
end
|
103
|
+
else
|
104
|
+
logger.warn("GlobalObjectReplica: Ignoring replicator sink verify_replicas from #{source} " +
|
105
|
+
"for unknown will_replicate sink class #{params[:class_name].inspect}")
|
106
|
+
end
|
107
|
+
render_nothing
|
108
|
+
end
|
109
|
+
|
110
|
+
# Update rows using any received synchronization data and send a
|
111
|
+
# verify_replica_range request for the next range to process
|
112
|
+
#
|
113
|
+
# @param :source [String] Replication source, e.g., 'library' or 'wasabi'
|
114
|
+
# @param :class_name [String] Name of a class that acts_as_global_object_replica
|
115
|
+
# @param :checksum_type [String] Type of checksum: only 'global_object_version_sum'
|
116
|
+
# @param :max_id_at_start [Integer] Max ID of the replica_class when first
|
117
|
+
# started synchronization process
|
118
|
+
# @param :begin_id [Integer, NilClass] First row ID in range verified by the source,
|
119
|
+
# or nil for all rows
|
120
|
+
# @param :end_id [Integer, NilClass] Last row ID in range verified by the source,
|
121
|
+
# or nil for all rows
|
122
|
+
# @param :checksum_matched [Boolean] Whether checksum matched
|
123
|
+
# @param :records_to_synchronize [Array, NilClass] Blank array or nil (if the last
|
124
|
+
# verification check was positive) or an array of hashes to be used to update the
|
125
|
+
# replicated table with each hash containing the keys used in handle_global_object_change:
|
126
|
+
# :id, :schema_version, :global_object_version, :attrs
|
127
|
+
# @param :has_more [Boolean] Indicates at least one more range should be verified
|
128
|
+
#
|
129
|
+
# @return [NilClass] nil
|
130
|
+
#
|
131
|
+
# @raise [ArgumentError] Unknown replication source
|
132
|
+
# @raise [RightScale::Exceptions::QueryFailure] Failed to complete synchronization query
|
133
|
+
# @raise [RightScale::Exceptions::RetryableError] Query failed but may be retried
|
134
|
+
def synchronize_replica_range
|
135
|
+
source = params[:source]
|
136
|
+
if (replica_class = replicate_sink_class(params[:class_name]))
|
137
|
+
checksum_type = params[:checksum_type]
|
138
|
+
max_id_at_start = to_int_or_nil(params[:max_id_at_start])
|
139
|
+
begin_id = to_int_or_nil(params[:begin_id])
|
140
|
+
end_id = to_int_or_nil(params[:end_id])
|
141
|
+
checksum_matched = to_bool(params[:checksum_matched])
|
142
|
+
records = params[:records_to_synchronize] || []
|
143
|
+
has_more = to_bool(params[:has_more])
|
144
|
+
|
145
|
+
success = query("synchronize_replica_range for #{replica_class.name}", :email_errors => true) do
|
146
|
+
logger.info("GlobalObjectReplica: Synchronizing #{source} #{replica_class.name} range #{begin_id}-#{end_id} " +
|
147
|
+
"(#{checksum_type} #{checksum_matched ? 'match' : 'mismatch'}) #{records.size} rows received.")
|
148
|
+
# Shuffle the records here so that collisions are less likely if two processes are trying to sync at once
|
149
|
+
records.shuffle.each do |h|
|
150
|
+
h = RightScale::SerializationHelper.symbolize_keys(h)
|
151
|
+
replica_class.handle_global_object_change(h[:id], h[:schema_version], h[:global_object_version], h[:attrs])
|
152
|
+
end
|
153
|
+
|
154
|
+
in_sync = checksum_matched || !(records.nil? || records.empty?)
|
155
|
+
|
156
|
+
if has_more || !in_sync
|
157
|
+
set_global_object_replication_status(replica_class.name, checksum_type, :percent_complete =>
|
158
|
+
(100 * ([begin_id, 1].max / [max_id_at_start, 1].max.to_f)).floor) if in_sync
|
159
|
+
|
160
|
+
next_begin_id, next_end_id = calculate_next_range_for_binary_sync(source, max_id_at_start,
|
161
|
+
replica_class.will_replicate_initialization_chunk_size,
|
162
|
+
begin_id, end_id, in_sync)
|
163
|
+
verify_next_replica_range(source, replica_class, checksum_type, max_id_at_start, next_begin_id, next_end_id)
|
164
|
+
else
|
165
|
+
logger.info("GlobalObjectReplica: Synchronization of #{source} #{replica_class.name} complete at row #{end_id}")
|
166
|
+
set_global_object_replication_status(replica_class.name, checksum_type, :completed => true)
|
167
|
+
end
|
168
|
+
true
|
169
|
+
end
|
170
|
+
|
171
|
+
unless success
|
172
|
+
set_global_object_replication_status(replica_class.name, checksum_type, :failed => true)
|
173
|
+
raise RightScale::Exceptions::QueryFailure.new(@last_error)
|
174
|
+
end
|
175
|
+
else
|
176
|
+
logger.warn("GlobalObjectReplica: Ignoring replicator sink synchronize_replica_range from #{source} " +
|
177
|
+
"for unknown will_replicate sink class #{params[:class_name].inspect}")
|
178
|
+
end
|
179
|
+
render_nothing
|
180
|
+
end
|
181
|
+
|
182
|
+
protected
|
183
|
+
|
184
|
+
# Calculate the checksum for the next synchronization range and send to the replication source
|
185
|
+
#
|
186
|
+
# @param :source [String] Replication source, e.g., 'library' or 'wasabi'
|
187
|
+
# @param replica_class [Class] Class that acts_as_global_object_replica
|
188
|
+
# @param checksum_type [String] Type of checksum to perform: only 'global_object_version_sum'
|
189
|
+
# @param max_id_at_start [Integer] Max ID of the replica_class when first
|
190
|
+
# started synchronization process
|
191
|
+
# @param begin_id [Integer, NilClass] First ID in the checksum range, or nil for all rows
|
192
|
+
# @param end_id [Integer, NilClass] Last ID in the checksum range, or nil for all rows
|
193
|
+
#
|
194
|
+
# @return [TrueClass] Always return true
|
195
|
+
#
|
196
|
+
# @raise [ArgumentError] Unknown replication source
|
197
|
+
def verify_next_replica_range(source, replica_class, checksum_type, max_id_at_start, begin_id, end_id)
|
198
|
+
replicator = case source # TODO Replace this with "#{source}_replicator" once all replicator actors gone
|
199
|
+
when "library" then "library_replicator"
|
200
|
+
when "wasabi" then "wasabi_replicator_source"
|
201
|
+
else raise ArgumentError, "Unknown replication source: #{source.inspect}"
|
202
|
+
end
|
203
|
+
payload = {
|
204
|
+
:sink => global_object_replicator_sink,
|
205
|
+
:class_name => replica_class.name,
|
206
|
+
:schema_version => replica_class.will_replicate_current_schema_version,
|
207
|
+
:checksum_type => checksum_type,
|
208
|
+
:max_id_at_start => max_id_at_start,
|
209
|
+
:begin_id => begin_id,
|
210
|
+
:end_id => end_id,
|
211
|
+
:send_records_on_checksum_mismatch => calc_size(begin_id, end_id) <= replica_class.will_replicate_initialization_chunk_size,
|
212
|
+
:checksum_value => replica_class.calculate_global_object_checksum(checksum_type, begin_id, end_id),
|
213
|
+
# Assume global if this sink has no associated shard
|
214
|
+
:shard_id => (RightSharding::ShardHandler.fetch_this_shard_id(SystemConfig) rescue RightScale::Packet::GLOBAL) }
|
215
|
+
|
216
|
+
EM.next_tick do
|
217
|
+
# Execute this request on next_tick since, depending on the configuration, the router could route
|
218
|
+
# this request directly via HTTP and there would be no break in the chain of replication requests
|
219
|
+
begin
|
220
|
+
RightScale::RightHttpClient.push("/#{replicator}/verify_replica_range", payload)
|
221
|
+
rescue Exception => e
|
222
|
+
logger.error(format_error("Failed to verify_replica_range for class #{replica_class.name}", e))
|
223
|
+
end
|
224
|
+
end
|
225
|
+
true
|
226
|
+
end
|
227
|
+
|
228
|
+
# Calculate the begin_id and end_id for the next range to compare given the current range & status
|
229
|
+
# Basically successive calls to this method will result in a binary depth-first search finding out
|
230
|
+
# of sync ranges by using the following rules:
|
231
|
+
# 1. If the current range is not in_sync, return the left half as the next range
|
232
|
+
# 2. If the current range is a left half (of some greater range) and it is in_sync, return the
|
233
|
+
# corresponding right half as the next range
|
234
|
+
# 3. If the current range is a right half and it is in_sync, move it up until it is part of a left
|
235
|
+
# half, then return the corresponding right half as the next range.
|
236
|
+
#
|
237
|
+
# @param max_id [Integer] Max ID when the search started, which is needed to calculate the left/right
|
238
|
+
# halves consistently during the whole search
|
239
|
+
# @param min_size [Integer] Minimum chunk size that is used to retrieve records
|
240
|
+
# @param last_begin_id [Integer] First ID in the current checksum range
|
241
|
+
# @param last_end_id [Integer] Last ID in the current checksum range
|
242
|
+
# @param in_sync [Boolean] true if the current checksum range is synchronized, i.e.,
|
243
|
+
# if do not need to go deeper here, otherwise false
|
244
|
+
#
|
245
|
+
# @return [Array] Next range to search in the form [next_begin_id, next_end_id]
|
246
|
+
#
|
247
|
+
# @raise [StandardError] Unexpected state
|
248
|
+
def calculate_next_range_for_binary_sync(source, max_id, min_size, last_begin_id, last_end_id, in_sync)
|
249
|
+
last_size, last_center = calc_size_and_center(last_begin_id, last_end_id)
|
250
|
+
|
251
|
+
next_begin_id = nil
|
252
|
+
next_end_id = nil
|
253
|
+
|
254
|
+
if !in_sync
|
255
|
+
if last_size <= min_size
|
256
|
+
raise StandardError.new("Unexpected state: Not in sync, but last_size #{last_size} is less " +
|
257
|
+
"then min_size #{min_size}. This shouldn't happen because should have received sync records " +
|
258
|
+
"from the #{source} database and applied them.")
|
259
|
+
else
|
260
|
+
# Check the left half next (i.e. depth-first search)
|
261
|
+
next_begin_id = last_begin_id
|
262
|
+
next_end_id = last_center
|
263
|
+
end
|
264
|
+
else
|
265
|
+
if last_end_id >= max_id
|
266
|
+
# Past the end, so just do the next initialization chunk size
|
267
|
+
next_begin_id = last_end_id + 1
|
268
|
+
next_end_id = last_end_id + min_size
|
269
|
+
else
|
270
|
+
# In sync in the last range, so figure out current location in the depth first search and
|
271
|
+
# check the next right branch
|
272
|
+
cur_begin_id = 0
|
273
|
+
cur_end_id = max_id
|
274
|
+
cur_size, cur_center = calc_size_and_center(cur_begin_id, cur_end_id)
|
275
|
+
|
276
|
+
while cur_size > last_size
|
277
|
+
if last_center < cur_center
|
278
|
+
# About to move down a left branch, so save the corresponding right branch and
|
279
|
+
# if don't go down anymore lefts, this is the branch to sync next
|
280
|
+
next_begin_id = cur_center + 1
|
281
|
+
next_end_id = cur_end_id
|
282
|
+
|
283
|
+
cur_end_id = cur_center
|
284
|
+
else
|
285
|
+
cur_begin_id = cur_center + 1
|
286
|
+
end
|
287
|
+
|
288
|
+
cur_size, cur_center = calc_size_and_center(cur_begin_id, cur_end_id)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
if next_begin_id.nil? || next_end_id.nil?
|
294
|
+
raise StandardError.new("Unexpected state: Nil result when calculating next range for binary sync: max_id=#{max_id}, " +
|
295
|
+
"min_size=#{min_size}, last_begin=#{last_begin_id}, last_end=#{last_end_id}, in_sync=#{in_sync}")
|
296
|
+
end
|
297
|
+
|
298
|
+
[next_begin_id, next_end_id]
|
299
|
+
end
|
300
|
+
|
301
|
+
def calc_size(begin_id, end_id)
|
302
|
+
end_id - begin_id + 1
|
303
|
+
end
|
304
|
+
|
305
|
+
def calc_size_and_center(begin_id, end_id)
|
306
|
+
[calc_size(begin_id, end_id), ((begin_id + end_id) / 2)]
|
307
|
+
end
|
308
|
+
|
309
|
+
def replicate_sink_class(class_name)
|
310
|
+
klass = constantize(class_name) rescue nil
|
311
|
+
(klass.respond_to?(:will_replicate_sink?) && klass.will_replicate_sink?) ? klass : nil
|
312
|
+
end
|
313
|
+
|
314
|
+
def set_global_object_replication_status(class_name, checksum_type, options = {})
|
315
|
+
status = GlobalObjectReplicationStatus.find_or_initialize_by_name(class_name)
|
316
|
+
status.last_sync_at = Time.now
|
317
|
+
status.last_sync_status = if options[:failed]
|
318
|
+
status.last_sync_status = "failed" + status.last_sync_status.sub("in progress", "")
|
319
|
+
elsif options[:completed]
|
320
|
+
'completed'
|
321
|
+
elsif options[:start]
|
322
|
+
"in progress 0%"
|
323
|
+
elsif options[:percent_complete] && options[:percent_complete] > 100
|
324
|
+
"in progress unknown %"
|
325
|
+
else
|
326
|
+
"in progress #{options[:percent_complete]}%"
|
327
|
+
end
|
328
|
+
status.last_sync_checksum_type = checksum_type
|
329
|
+
status.last_sync_start_at = Time.now if options[:start]
|
330
|
+
status.save!
|
331
|
+
rescue Exception => e
|
332
|
+
logger.error(format_error("Failed to update global object replication status", e, :trace))
|
333
|
+
end
|
334
|
+
|
335
|
+
end # GlobalObjectReplicatorSink
|
336
|
+
|
337
|
+
end # RightScale
|