right_infrastructure_agent 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|