longleaf 0.2.0.pre.1 → 0.3.0
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 +4 -4
- data/.circleci/config.yml +84 -0
- data/.gitignore +4 -2
- data/.rubocop.yml +42 -2
- data/.rubocop_todo.yml +390 -311
- data/.yardopts +1 -0
- data/Gemfile +16 -1
- data/README.md +67 -13
- data/Rakefile +6 -0
- data/bin/setup +16 -1
- data/docs/aboutlongleaf.md +28 -0
- data/docs/extra.css +32 -0
- data/docs/img/change-file.png +0 -0
- data/docs/img/ll-example-preserved.png +0 -0
- data/docs/index.md +19 -0
- data/docs/install.md +66 -0
- data/docs/ll-example/config-example-relative.yml +33 -0
- data/docs/ll-example/files-dir/LLexample-PDF.pdf +0 -0
- data/docs/ll-example/files-dir/LLexample-TOCHANGE.txt +15 -0
- data/docs/ll-example/files-dir/LLexample-tokeep.txt +10 -0
- data/docs/ll-example/metadata-dir/.gitkeep +0 -0
- data/docs/ll-example/replica-files/.gitkeep +0 -0
- data/docs/ll-example/replica-metadata/.gitkeep +0 -0
- data/docs/quickstart.md +270 -0
- data/docs/rdocs/Longleaf.html +135 -0
- data/docs/rdocs/Longleaf/AppFields.html +178 -0
- data/docs/rdocs/Longleaf/ApplicationConfigDeserializer.html +631 -0
- data/docs/rdocs/Longleaf/ApplicationConfigManager.html +610 -0
- data/docs/rdocs/Longleaf/ApplicationConfigValidator.html +238 -0
- data/docs/rdocs/Longleaf/CLI.html +909 -0
- data/docs/rdocs/Longleaf/ChecksumMismatchError.html +151 -0
- data/docs/rdocs/Longleaf/ConfigBuilder.html +1339 -0
- data/docs/rdocs/Longleaf/ConfigurationError.html +143 -0
- data/docs/rdocs/Longleaf/ConfigurationValidator.html +227 -0
- data/docs/rdocs/Longleaf/DeregisterCommand.html +420 -0
- data/docs/rdocs/Longleaf/DeregisterEvent.html +453 -0
- data/docs/rdocs/Longleaf/DeregistrationError.html +151 -0
- data/docs/rdocs/Longleaf/DigestHelper.html +419 -0
- data/docs/rdocs/Longleaf/EventError.html +147 -0
- data/docs/rdocs/Longleaf/EventNames.html +163 -0
- data/docs/rdocs/Longleaf/EventStatusTracking.html +656 -0
- data/docs/rdocs/Longleaf/FileCheckService.html +540 -0
- data/docs/rdocs/Longleaf/FileHelpers.html +520 -0
- data/docs/rdocs/Longleaf/FileRecord.html +716 -0
- data/docs/rdocs/Longleaf/FileSelector.html +901 -0
- data/docs/rdocs/Longleaf/FixityCheckService.html +691 -0
- data/docs/rdocs/Longleaf/IndexManager.html +1155 -0
- data/docs/rdocs/Longleaf/InvalidDigestAlgorithmError.html +143 -0
- data/docs/rdocs/Longleaf/InvalidStoragePathError.html +143 -0
- data/docs/rdocs/Longleaf/Logging.html +405 -0
- data/docs/rdocs/Longleaf/Logging/RedirectingLogger.html +1213 -0
- data/docs/rdocs/Longleaf/LongleafError.html +139 -0
- data/docs/rdocs/Longleaf/MDFields.html +193 -0
- data/docs/rdocs/Longleaf/MetadataBuilder.html +787 -0
- data/docs/rdocs/Longleaf/MetadataDeserializer.html +537 -0
- data/docs/rdocs/Longleaf/MetadataError.html +143 -0
- data/docs/rdocs/Longleaf/MetadataPersistenceManager.html +539 -0
- data/docs/rdocs/Longleaf/MetadataRecord.html +1411 -0
- data/docs/rdocs/Longleaf/MetadataSerializer.html +786 -0
- data/docs/rdocs/Longleaf/PreservationServiceError.html +147 -0
- data/docs/rdocs/Longleaf/PreserveCommand.html +410 -0
- data/docs/rdocs/Longleaf/PreserveEvent.html +491 -0
- data/docs/rdocs/Longleaf/RegisterCommand.html +428 -0
- data/docs/rdocs/Longleaf/RegisterEvent.html +628 -0
- data/docs/rdocs/Longleaf/RegisteredFileSelector.html +446 -0
- data/docs/rdocs/Longleaf/RegistrationError.html +151 -0
- data/docs/rdocs/Longleaf/ReindexCommand.html +576 -0
- data/docs/rdocs/Longleaf/RsyncReplicationService.html +1180 -0
- data/docs/rdocs/Longleaf/SequelIndexDriver.html +1978 -0
- data/docs/rdocs/Longleaf/ServiceCandidateFilesystemIterator.html +572 -0
- data/docs/rdocs/Longleaf/ServiceCandidateIndexIterator.html +532 -0
- data/docs/rdocs/Longleaf/ServiceCandidateLocator.html +333 -0
- data/docs/rdocs/Longleaf/ServiceClassCache.html +725 -0
- data/docs/rdocs/Longleaf/ServiceDateHelper.html +425 -0
- data/docs/rdocs/Longleaf/ServiceDefinition.html +683 -0
- data/docs/rdocs/Longleaf/ServiceDefinitionManager.html +371 -0
- data/docs/rdocs/Longleaf/ServiceDefinitionValidator.html +269 -0
- data/docs/rdocs/Longleaf/ServiceFields.html +173 -0
- data/docs/rdocs/Longleaf/ServiceManager.html +1229 -0
- data/docs/rdocs/Longleaf/ServiceMappingManager.html +410 -0
- data/docs/rdocs/Longleaf/ServiceMappingValidator.html +347 -0
- data/docs/rdocs/Longleaf/ServiceRecord.html +821 -0
- data/docs/rdocs/Longleaf/StorageLocation.html +985 -0
- data/docs/rdocs/Longleaf/StorageLocationManager.html +729 -0
- data/docs/rdocs/Longleaf/StorageLocationUnavailableError.html +143 -0
- data/docs/rdocs/Longleaf/StorageLocationValidator.html +373 -0
- data/docs/rdocs/Longleaf/StoragePathValidator.html +253 -0
- data/docs/rdocs/Longleaf/SystemConfigBuilder.html +441 -0
- data/docs/rdocs/Longleaf/SystemConfigFields.html +163 -0
- data/docs/rdocs/Longleaf/ValidateConfigCommand.html +451 -0
- data/docs/rdocs/Longleaf/ValidateMetadataCommand.html +408 -0
- data/docs/rdocs/_index.html +660 -0
- data/docs/rdocs/class_list.html +51 -0
- data/docs/rdocs/css/common.css +1 -0
- data/docs/rdocs/css/full_list.css +58 -0
- data/docs/rdocs/css/style.css +496 -0
- data/docs/rdocs/file.README.html +165 -0
- data/docs/rdocs/file_list.html +56 -0
- data/docs/rdocs/frames.html +17 -0
- data/docs/rdocs/index.html +165 -0
- data/docs/rdocs/js/app.js +303 -0
- data/docs/rdocs/js/full_list.js +216 -0
- data/docs/rdocs/js/jquery.js +4 -0
- data/docs/rdocs/method_list.html +2051 -0
- data/docs/rdocs/top-level-namespace.html +110 -0
- data/lib/longleaf/candidates/file_selector.rb +47 -15
- data/lib/longleaf/candidates/registered_file_selector.rb +67 -0
- data/lib/longleaf/candidates/service_candidate_filesystem_iterator.rb +29 -35
- data/lib/longleaf/candidates/service_candidate_index_iterator.rb +84 -0
- data/lib/longleaf/candidates/service_candidate_locator.rb +9 -4
- data/lib/longleaf/cli.rb +162 -80
- data/lib/longleaf/commands/deregister_command.rb +12 -11
- data/lib/longleaf/commands/preserve_command.rb +13 -8
- data/lib/longleaf/commands/register_command.rb +9 -6
- data/lib/longleaf/commands/reindex_command.rb +92 -0
- data/lib/longleaf/commands/validate_config_command.rb +27 -6
- data/lib/longleaf/commands/validate_metadata_command.rb +11 -9
- data/lib/longleaf/errors.rb +12 -12
- data/lib/longleaf/events/deregister_event.rb +13 -15
- data/lib/longleaf/events/event_status_tracking.rb +7 -7
- data/lib/longleaf/events/preserve_event.rb +24 -14
- data/lib/longleaf/events/register_event.rb +21 -35
- data/lib/longleaf/helpers/digest_helper.rb +4 -4
- data/lib/longleaf/helpers/service_date_helper.rb +5 -6
- data/lib/longleaf/indexing/index_manager.rb +101 -0
- data/lib/longleaf/indexing/sequel_index_driver.rb +324 -0
- data/lib/longleaf/logging.rb +4 -4
- data/lib/longleaf/logging/redirecting_logger.rb +20 -20
- data/lib/longleaf/models/app_fields.rb +2 -1
- data/lib/longleaf/models/file_record.rb +10 -6
- data/lib/longleaf/models/md_fields.rb +1 -1
- data/lib/longleaf/models/metadata_record.rb +22 -12
- data/lib/longleaf/models/service_definition.rb +3 -3
- data/lib/longleaf/models/service_fields.rb +1 -1
- data/lib/longleaf/models/service_record.rb +6 -5
- data/lib/longleaf/models/storage_location.rb +26 -7
- data/lib/longleaf/models/system_config_fields.rb +9 -0
- data/lib/longleaf/preservation_services/file_check_service.rb +58 -0
- data/lib/longleaf/preservation_services/fixity_check_service.rb +16 -14
- data/lib/longleaf/preservation_services/rsync_replication_service.rb +32 -31
- data/lib/longleaf/services/application_config_deserializer.rb +55 -18
- data/lib/longleaf/services/application_config_manager.rb +16 -4
- data/lib/longleaf/services/application_config_validator.rb +1 -2
- data/lib/longleaf/services/configuration_validator.rb +6 -4
- data/lib/longleaf/services/metadata_deserializer.rb +40 -38
- data/lib/longleaf/services/metadata_persistence_manager.rb +46 -0
- data/lib/longleaf/services/metadata_serializer.rb +23 -22
- data/lib/longleaf/services/service_class_cache.rb +15 -15
- data/lib/longleaf/services/service_definition_manager.rb +5 -6
- data/lib/longleaf/services/service_definition_validator.rb +5 -6
- data/lib/longleaf/services/service_manager.rb +37 -17
- data/lib/longleaf/services/service_mapping_manager.rb +9 -9
- data/lib/longleaf/services/service_mapping_validator.rb +9 -10
- data/lib/longleaf/services/storage_location_manager.rb +22 -8
- data/lib/longleaf/services/storage_location_validator.rb +11 -8
- data/lib/longleaf/services/storage_path_validator.rb +1 -1
- data/lib/longleaf/specs/config_builder.rb +30 -17
- data/lib/longleaf/specs/custom_matchers.rb +1 -1
- data/lib/longleaf/specs/file_helpers.rb +15 -14
- data/lib/longleaf/specs/metadata_builder.rb +91 -0
- data/lib/longleaf/specs/system_config_builder.rb +27 -0
- data/lib/longleaf/version.rb +1 -1
- data/longleaf.gemspec +17 -7
- data/mkdocs.yml +20 -0
- metadata +233 -22
|
@@ -15,9 +15,9 @@ module Longleaf
|
|
|
15
15
|
# * 'generate' - the service will generate and store any missing digests from the set of configured algorithms.
|
|
16
16
|
class FixityCheckService
|
|
17
17
|
include Longleaf::Logging
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
SUPPORTED_ALGORITHMS = ['md5', 'sha1', 'sha2', 'sha256', 'sha384', 'sha512', 'rmd160']
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
# service configuration property indicating how to handle situations where a file does not
|
|
22
22
|
# have a digest for one of the expected algorithms on record.
|
|
23
23
|
ABSENT_DIGEST_PROPERTY = 'absent_digest'
|
|
@@ -25,7 +25,7 @@ module Longleaf
|
|
|
25
25
|
GENERATE_IF_ABSENT = 'generate'
|
|
26
26
|
IGNORE_IF_ABSENT = 'ignore'
|
|
27
27
|
ABSENT_DIGEST_OPTIONS = [FAIL_IF_ABSENT, GENERATE_IF_ABSENT, IGNORE_IF_ABSENT]
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
# Initialize a FixityCheckService from the given service definition
|
|
30
30
|
#
|
|
31
31
|
# @param service_def [ServiceDefinition] the configuration for this service
|
|
@@ -36,12 +36,14 @@ module Longleaf
|
|
|
36
36
|
unless ABSENT_DIGEST_OPTIONS.include?(@absent_digest_behavior)
|
|
37
37
|
raise ArgumentError.new("Invalid option '#{@absent_digest_behavior}' for property #{ABSENT_DIGEST_PROPERTY} in service #{service_def.name}")
|
|
38
38
|
end
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
service_algs = service_def.properties[ServiceFields::DIGEST_ALGORITHMS]
|
|
41
41
|
if service_algs.nil? || service_algs.empty?
|
|
42
42
|
raise ArgumentError.new("FixityCheckService from definition #{service_def.name} requires a list of one or more digest algorithms")
|
|
43
43
|
end
|
|
44
|
-
|
|
44
|
+
|
|
45
|
+
service_algs = [service_algs] if service_algs.is_a?(String)
|
|
46
|
+
|
|
45
47
|
# Store the list of digest algorithms to verify, using normalized algorithm names.
|
|
46
48
|
@digest_algs = Set.new
|
|
47
49
|
service_algs.each do |alg|
|
|
@@ -49,11 +51,11 @@ module Longleaf
|
|
|
49
51
|
if SUPPORTED_ALGORITHMS.include?(normalized_alg)
|
|
50
52
|
@digest_algs << normalized_alg
|
|
51
53
|
else
|
|
52
|
-
raise ArgumentError.new("Unsupported checksum algorithm '#{alg}' in definition #{service_def.name}. Supported algorithms are: #{SUPPORTED_ALGORITHMS
|
|
54
|
+
raise ArgumentError.new("Unsupported checksum algorithm '#{alg}' in definition #{service_def.name}. Supported algorithms are: #{SUPPORTED_ALGORITHMS}")
|
|
53
55
|
end
|
|
54
56
|
end
|
|
55
57
|
end
|
|
56
|
-
|
|
58
|
+
|
|
57
59
|
# Perform all configured fixity checks on the provided file
|
|
58
60
|
#
|
|
59
61
|
# @param file_rec [FileRecord] record representing the file to perform the service on.
|
|
@@ -62,7 +64,7 @@ module Longleaf
|
|
|
62
64
|
def perform(file_rec, event)
|
|
63
65
|
path = file_rec.path
|
|
64
66
|
md_rec = file_rec.metadata_record
|
|
65
|
-
|
|
67
|
+
|
|
66
68
|
# Get the list of existing checksums for the file and normalize algorithm names
|
|
67
69
|
file_digests = Hash.new
|
|
68
70
|
md_rec.checksums&.each do |alg, digest|
|
|
@@ -73,10 +75,10 @@ module Longleaf
|
|
|
73
75
|
logger.debug("Metadata for file #{path} contains unexpected '#{alg}' digest, it will be ignored.")
|
|
74
76
|
end
|
|
75
77
|
end
|
|
76
|
-
|
|
78
|
+
|
|
77
79
|
@digest_algs.each do |alg|
|
|
78
80
|
existing_digest = file_digests[alg]
|
|
79
|
-
|
|
81
|
+
|
|
80
82
|
if existing_digest.nil?
|
|
81
83
|
if @absent_digest_behavior == FAIL_IF_ABSENT
|
|
82
84
|
raise ChecksumMismatchError.new("Fixity check using algorithm '#{alg}' failed for file #{path}: no existing digest of type '#{alg}' on record.")
|
|
@@ -85,11 +87,11 @@ module Longleaf
|
|
|
85
87
|
next
|
|
86
88
|
end
|
|
87
89
|
end
|
|
88
|
-
|
|
90
|
+
|
|
89
91
|
digest = DigestHelper::start_digest(alg)
|
|
90
92
|
digest.file(path)
|
|
91
93
|
generated_digest = digest.hexdigest
|
|
92
|
-
|
|
94
|
+
|
|
93
95
|
# Store the missing checksum if using the 'generate' behavior
|
|
94
96
|
if existing_digest.nil? && @absent_digest_behavior == GENERATE_IF_ABSENT
|
|
95
97
|
md_rec.checksums[alg] = generated_digest
|
|
@@ -104,7 +106,7 @@ module Longleaf
|
|
|
104
106
|
end
|
|
105
107
|
end
|
|
106
108
|
end
|
|
107
|
-
|
|
109
|
+
|
|
108
110
|
# Determine if this service is applicable for the provided event, given the configured service definition
|
|
109
111
|
#
|
|
110
112
|
# @param event [String] name of the event
|
|
@@ -118,4 +120,4 @@ module Longleaf
|
|
|
118
120
|
end
|
|
119
121
|
end
|
|
120
122
|
end
|
|
121
|
-
end
|
|
123
|
+
end
|
|
@@ -17,58 +17,59 @@ module Longleaf
|
|
|
17
17
|
# a file which already exists at a destination. Default: "replace".
|
|
18
18
|
# * rsync_command = the command to invoke in order to execute rsync. Default: "rsync"
|
|
19
19
|
# * rsync_options = additional parameters that will be passed along to rsync. Cannot include options
|
|
20
|
-
# which change the target of the command or prevent its execution, such as "files-from", "dry-run",
|
|
20
|
+
# which change the target of the command or prevent its execution, such as "files-from", "dry-run",
|
|
21
21
|
# "help", etc. Command will always include "-R". Default "-a".
|
|
22
22
|
class RsyncReplicationService
|
|
23
23
|
include Longleaf::Logging
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
COLLISION_PROPERTY = "replica_collision_policy"
|
|
26
26
|
DEFAULT_COLLISION_POLICY = "replace"
|
|
27
27
|
VALID_COLLISION_POLICIES = ["replace"]
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
RSYNC_COMMAND_PROPERTY = "rsync_command"
|
|
30
30
|
DEFAULT_COMMAND = "rsync"
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
RSYNC_OPTIONS_PROPERTY = "rsync_options"
|
|
33
33
|
DEFAULT_OPTIONS = "-a"
|
|
34
34
|
DISALLOWED_OPTIONS = ["files-from", "n", "dry-run", "exclude", "exclude-from", "cvs-exclude",
|
|
35
35
|
"h", "help", "f", "F", "filter"]
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
attr_reader :command, :options, :collision_policy
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
# Initialize a RsyncReplicationService from the given service definition
|
|
40
40
|
#
|
|
41
41
|
# @param service_def [ServiceDefinition] the configuration for this service
|
|
42
|
-
# @param
|
|
42
|
+
# @param app_manager [ApplicationConfigManager] the application configuration
|
|
43
43
|
def initialize(service_def, app_manager)
|
|
44
44
|
@service_def = service_def
|
|
45
45
|
@app_manager = app_manager
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
@command = @service_def.properties[RSYNC_COMMAND_PROPERTY] || DEFAULT_COMMAND
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
# Validate rsync parameters
|
|
50
50
|
@options = @service_def.properties[RSYNC_OPTIONS_PROPERTY] || DEFAULT_OPTIONS
|
|
51
51
|
if contains_disallowed_option?(@options)
|
|
52
52
|
raise ArgumentError.new("Service #{service_def.name} specifies a disallowed rsync paramter," \
|
|
53
|
-
+ " rsync_options may not include the following: #{DISALLOWED_OPTIONS.join(
|
|
53
|
+
+ " rsync_options may not include the following: #{DISALLOWED_OPTIONS.join(' ')}")
|
|
54
54
|
end
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
# Add -R (--relative) in to command options to ensure full path gets replicated
|
|
57
57
|
@options = @options + " -R"
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
# Set and validate the replica collision policy
|
|
60
60
|
@collision_policy = @service_def.properties[COLLISION_PROPERTY] || DEFAULT_COLLISION_POLICY
|
|
61
61
|
if !VALID_COLLISION_POLICIES.include?(@collision_policy)
|
|
62
62
|
raise ArgumentError.new("Service #{service_def.name} received invalid #{COLLISION_PROPERTY}" \
|
|
63
63
|
+ " value #{collision_policy}")
|
|
64
64
|
end
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
# Store and validate destinations
|
|
67
67
|
replicate_to = @service_def.properties[ServiceFields::REPLICATE_TO]
|
|
68
68
|
if replicate_to.nil? || replicate_to.empty?
|
|
69
69
|
raise ArgumentError.new("Service #{service_def.name} must provide one or more replication destinations.")
|
|
70
70
|
end
|
|
71
|
-
|
|
71
|
+
replicate_to = [replicate_to] if replicate_to.is_a?(String)
|
|
72
|
+
|
|
72
73
|
loc_manager = app_manager.location_manager
|
|
73
74
|
# Build list of destinations, translating to storage locations when relevant
|
|
74
75
|
@destinations = Array.new
|
|
@@ -86,7 +87,7 @@ module Longleaf
|
|
|
86
87
|
end
|
|
87
88
|
end
|
|
88
89
|
end
|
|
89
|
-
|
|
90
|
+
|
|
90
91
|
# During a replication event, perform replication of the specified file to all configured destinations
|
|
91
92
|
# as necessary.
|
|
92
93
|
#
|
|
@@ -96,35 +97,35 @@ module Longleaf
|
|
|
96
97
|
def perform(file_rec, event)
|
|
97
98
|
@destinations.each do |destination|
|
|
98
99
|
dest_is_storage_loc = destination.is_a?(Longleaf::StorageLocation)
|
|
99
|
-
|
|
100
|
+
|
|
100
101
|
if dest_is_storage_loc
|
|
101
102
|
dest_path = destination.path
|
|
102
103
|
else
|
|
103
104
|
dest_path = destination
|
|
104
105
|
end
|
|
105
|
-
|
|
106
|
+
|
|
106
107
|
# Determine the path to the file being replicated relative to its storage location
|
|
107
108
|
rel_path = file_rec.path.sub(/\A#{file_rec.storage_location.path}/, "")
|
|
108
109
|
# source path with . so that rsync will only create destination directories starting from that point
|
|
109
110
|
source_path = File.join(file_rec.storage_location.path, "./#{rel_path}")
|
|
110
|
-
|
|
111
|
+
|
|
111
112
|
# Check that the destination is available because attempting to write
|
|
112
113
|
verify_destination_available(destination, file_rec)
|
|
113
|
-
|
|
114
|
+
|
|
114
115
|
logger.debug("Invoking rsync with command: #{@command} \"#{source_path}\" \"#{dest_path}\" #{@options}")
|
|
115
116
|
stdout, stderr, status = Open3.capture3("#{@command} \"#{source_path}\" \"#{dest_path}\" #{@options}")
|
|
116
117
|
raise PreservationServiceError.new("Failed to replicate #{file_rec.path} to #{dest_path}: #{stderr}") \
|
|
117
118
|
unless status.success?
|
|
118
|
-
|
|
119
|
+
|
|
119
120
|
logger.info("Replicated #{file_rec.path} to destination #{dest_path}")
|
|
120
|
-
|
|
121
|
+
|
|
121
122
|
# For destinations which are storage locations, register the replica with longleaf
|
|
122
123
|
if dest_is_storage_loc
|
|
123
124
|
register_replica(destination, rel_path, file_rec)
|
|
124
125
|
end
|
|
125
126
|
end
|
|
126
127
|
end
|
|
127
|
-
|
|
128
|
+
|
|
128
129
|
# Determine if this service is applicable for the provided event, given the configured service definition
|
|
129
130
|
#
|
|
130
131
|
# @param event [String] name of the event
|
|
@@ -137,7 +138,7 @@ module Longleaf
|
|
|
137
138
|
false
|
|
138
139
|
end
|
|
139
140
|
end
|
|
140
|
-
|
|
141
|
+
|
|
141
142
|
private
|
|
142
143
|
def contains_disallowed_option?(options)
|
|
143
144
|
DISALLOWED_OPTIONS.each do |disallowed|
|
|
@@ -151,33 +152,33 @@ module Longleaf
|
|
|
151
152
|
end
|
|
152
153
|
end
|
|
153
154
|
end
|
|
154
|
-
|
|
155
|
+
|
|
155
156
|
false
|
|
156
157
|
end
|
|
157
|
-
|
|
158
|
+
|
|
158
159
|
def verify_destination_available(destination, file_rec)
|
|
159
160
|
if destination.is_a?(Longleaf::StorageLocation)
|
|
160
161
|
begin
|
|
161
162
|
destination.available?
|
|
162
163
|
rescue StorageLocationUnavailableError => e
|
|
163
|
-
raise StorageLocationUnavailableError.new("Cannot replicate #{file_rec.path} to destination" \
|
|
164
|
-
+
|
|
164
|
+
raise StorageLocationUnavailableError.new("Cannot replicate #{file_rec.path} to destination #{destination.name}: " \
|
|
165
|
+
+ e.message)
|
|
165
166
|
end
|
|
166
167
|
elsif destination.start_with?("/")
|
|
167
168
|
raise StorageLocationUnavailableError.new("Cannot replicate #{file_rec.path} to destination" \
|
|
168
169
|
+ " #{destination}, path does not exist.") unless Dir.exist?(destination)
|
|
169
170
|
end
|
|
170
171
|
end
|
|
171
|
-
|
|
172
|
+
|
|
172
173
|
def register_replica(destination, rel_path, file_rec)
|
|
173
174
|
dest_file_path = File.join(destination.path, rel_path)
|
|
174
175
|
dest_file_rec = FileRecord.new(dest_file_path, destination)
|
|
175
|
-
|
|
176
|
+
|
|
176
177
|
register_event = RegisterEvent.new(file_rec: dest_file_rec,
|
|
177
178
|
app_manager: @app_manager,
|
|
178
|
-
force: true,
|
|
179
|
+
force: true,
|
|
179
180
|
checksums: file_rec.metadata_record.checksums)
|
|
180
181
|
register_event.perform
|
|
181
182
|
end
|
|
182
183
|
end
|
|
183
|
-
end
|
|
184
|
+
end
|
|
@@ -1,44 +1,81 @@
|
|
|
1
1
|
require 'longleaf/services/application_config_validator'
|
|
2
2
|
require 'longleaf/services/application_config_manager'
|
|
3
|
+
require 'digest/md5'
|
|
4
|
+
require 'pathname'
|
|
3
5
|
|
|
4
6
|
module Longleaf
|
|
5
7
|
# Deserializer for application configuration files
|
|
6
8
|
class ApplicationConfigDeserializer
|
|
7
|
-
|
|
9
|
+
AF ||= Longleaf::AppFields
|
|
10
|
+
|
|
8
11
|
# Deserializes a valid application configuration file as a ApplicationConfigManager option
|
|
9
|
-
# @param config_path [String] file path to the
|
|
12
|
+
# @param config_path [String] file path to the service and storage mapping configuration file
|
|
10
13
|
# @param format [String] encoding format of the config file
|
|
11
14
|
# return [ApplicationConfigManager] manager for the loaded configuration
|
|
12
15
|
def self.deserialize(config_path, format: 'yaml')
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
content = load_config_file(config_path)
|
|
17
|
+
config = load(content, format)
|
|
18
|
+
|
|
19
|
+
config_md5 = Digest::MD5.hexdigest(content)
|
|
20
|
+
|
|
21
|
+
make_paths_absolute(config_path, config)
|
|
15
22
|
Longleaf::ApplicationConfigValidator.validate(config)
|
|
16
|
-
Longleaf::ApplicationConfigManager.new(config)
|
|
23
|
+
Longleaf::ApplicationConfigManager.new(config, config_md5)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.load_config_file(config_path)
|
|
27
|
+
begin
|
|
28
|
+
File.read(config_path)
|
|
29
|
+
rescue Errno::ENOENT
|
|
30
|
+
raise Longleaf::ConfigurationError.new(
|
|
31
|
+
"Configuration file #{config_path} does not exist.")
|
|
32
|
+
end
|
|
17
33
|
end
|
|
18
|
-
|
|
34
|
+
|
|
19
35
|
# Deserialize a configuration file into a hash
|
|
20
|
-
# @param
|
|
36
|
+
# @param content [String] the contents of the application configuration file
|
|
21
37
|
# @param format [String] encoding format of the config file
|
|
22
38
|
# return [Hash] hash containing the configuration
|
|
23
|
-
def self.load(
|
|
39
|
+
def self.load(content, format)
|
|
24
40
|
case format
|
|
25
41
|
when 'yaml'
|
|
26
|
-
from_yaml(
|
|
42
|
+
from_yaml(content)
|
|
27
43
|
else
|
|
28
|
-
raise ArgumentError.new(
|
|
44
|
+
raise ArgumentError.new("Invalid deserialization format #{format} specified")
|
|
29
45
|
end
|
|
30
46
|
end
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def self.from_yaml(config_path)
|
|
47
|
+
|
|
48
|
+
def self.from_yaml(content)
|
|
34
49
|
begin
|
|
35
|
-
YAML.
|
|
36
|
-
rescue Errno::ENOENT => err
|
|
37
|
-
raise Longleaf::ConfigurationError.new(
|
|
38
|
-
"Configuration file #{config_path} does not exist.")
|
|
50
|
+
YAML.safe_load(content, [], [], true)
|
|
39
51
|
rescue => err
|
|
40
52
|
raise Longleaf::ConfigurationError.new(err)
|
|
41
53
|
end
|
|
42
54
|
end
|
|
55
|
+
|
|
56
|
+
def self.make_paths_absolute(config_path, config)
|
|
57
|
+
base_pathname = Pathname.new(config_path).expand_path.parent
|
|
58
|
+
|
|
59
|
+
config[AF::LOCATIONS].each do |name, properties|
|
|
60
|
+
properties[AF::LOCATION_PATH] = absolution(base_pathname, properties[AF::LOCATION_PATH])
|
|
61
|
+
|
|
62
|
+
properties[AF::METADATA_PATH] = absolution(base_pathname, properties[AF::METADATA_PATH])
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.absolution(base_pathname, file_path)
|
|
67
|
+
if file_path.nil?
|
|
68
|
+
nil
|
|
69
|
+
else
|
|
70
|
+
path = Pathname.new(file_path)
|
|
71
|
+
if path.absolute?
|
|
72
|
+
path = path.expand_path.to_s
|
|
73
|
+
else
|
|
74
|
+
path = (base_pathname + path).to_s
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private_class_method :load_config_file
|
|
43
80
|
end
|
|
44
|
-
end
|
|
81
|
+
end
|
|
@@ -5,22 +5,34 @@ require_relative 'service_definition_manager'
|
|
|
5
5
|
require_relative 'service_mapping_validator'
|
|
6
6
|
require_relative 'service_mapping_manager'
|
|
7
7
|
require_relative 'service_manager'
|
|
8
|
+
require_relative 'metadata_persistence_manager'
|
|
9
|
+
require 'longleaf/indexing/index_manager'
|
|
10
|
+
require 'longleaf/models/app_fields'
|
|
8
11
|
|
|
9
12
|
module Longleaf
|
|
10
13
|
# Manager which loads and provides access to the configuration of the application
|
|
11
14
|
class ApplicationConfigManager
|
|
15
|
+
attr_reader :config_md5
|
|
12
16
|
attr_reader :service_manager
|
|
13
17
|
attr_reader :location_manager
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
attr_reader :index_manager
|
|
19
|
+
attr_reader :md_manager
|
|
20
|
+
|
|
21
|
+
def initialize(config, config_md5 = nil)
|
|
22
|
+
@config_md5 = config_md5
|
|
23
|
+
|
|
16
24
|
@location_manager = Longleaf::StorageLocationManager.new(config)
|
|
17
|
-
|
|
25
|
+
|
|
18
26
|
definition_manager = Longleaf::ServiceDefinitionManager.new(config)
|
|
19
27
|
mapping_manager = Longleaf::ServiceMappingManager.new(config)
|
|
20
28
|
@service_manager = Longleaf::ServiceManager.new(
|
|
21
29
|
definition_manager: definition_manager,
|
|
22
30
|
mapping_manager: mapping_manager,
|
|
23
31
|
app_manager: self)
|
|
32
|
+
|
|
33
|
+
sys_config = config[AppFields::SYSTEM]
|
|
34
|
+
@index_manager = IndexManager.new(sys_config, self)
|
|
35
|
+
@md_manager = MetadataPersistenceManager.new(@index_manager)
|
|
24
36
|
end
|
|
25
37
|
end
|
|
26
|
-
end
|
|
38
|
+
end
|
|
@@ -5,7 +5,6 @@ require_relative 'service_mapping_validator'
|
|
|
5
5
|
module Longleaf
|
|
6
6
|
# Validator for Longleaf application configuration
|
|
7
7
|
class ApplicationConfigValidator
|
|
8
|
-
|
|
9
8
|
# Validates the application configuration provided. Will raise ConfigurationError
|
|
10
9
|
# if any portion of the configuration is not syntactically or semantically valid.
|
|
11
10
|
# @param config [Hash] application configuration
|
|
@@ -15,4 +14,4 @@ module Longleaf
|
|
|
15
14
|
Longleaf::ServiceMappingValidator::validate_config(config)
|
|
16
15
|
end
|
|
17
16
|
end
|
|
18
|
-
end
|
|
17
|
+
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
module Longleaf
|
|
2
2
|
# Abstract configuration validator class
|
|
3
3
|
class ConfigurationValidator
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
class << self
|
|
5
|
+
protected
|
|
6
|
+
def assert(fail_message, assertion_passed)
|
|
7
|
+
raise ConfigurationError.new(fail_message) unless assertion_passed
|
|
8
|
+
end
|
|
7
9
|
end
|
|
8
10
|
end
|
|
9
|
-
end
|
|
11
|
+
end
|
|
@@ -9,7 +9,7 @@ module Longleaf
|
|
|
9
9
|
class MetadataDeserializer
|
|
10
10
|
extend Longleaf::Logging
|
|
11
11
|
MDF ||= MDFields
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
# Deserialize a file into a MetadataRecord object
|
|
14
14
|
#
|
|
15
15
|
# @param file_path [String] path of the file to read. Required.
|
|
@@ -19,13 +19,13 @@ module Longleaf
|
|
|
19
19
|
when 'yaml'
|
|
20
20
|
md = from_yaml(file_path, digest_algs)
|
|
21
21
|
else
|
|
22
|
-
raise ArgumentError.new(
|
|
22
|
+
raise ArgumentError.new("Invalid deserialization format #{format} specified")
|
|
23
23
|
end
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
if !md || !md.is_a?(Hash) || !md.key?(MDF::DATA) || !md.key?(MDF::SERVICES)
|
|
26
26
|
raise Longleaf::MetadataError.new("Invalid metadata file, did not contain data or services fields: #{file_path}")
|
|
27
27
|
end
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
data = Hash.new.merge(md[MDF::DATA])
|
|
30
30
|
# Extract reserved properties for submission as separate parameters
|
|
31
31
|
registered = data.delete(MDFields::REGISTERED_TIMESTAMP)
|
|
@@ -33,50 +33,52 @@ module Longleaf
|
|
|
33
33
|
checksums = data.delete(MDFields::CHECKSUMS)
|
|
34
34
|
file_size = data.delete(MDFields::FILE_SIZE)
|
|
35
35
|
last_modified = data.delete(MDFields::LAST_MODIFIED)
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
services = md[MDF::SERVICES]
|
|
38
38
|
service_records = Hash.new
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
run_needed: run_needed)
|
|
54
|
-
end
|
|
39
|
+
services&.each do |name, props|
|
|
40
|
+
raise Longleaf::MetadataError.new("Value of service #{name} must be a hash") unless props.class == Hash
|
|
41
|
+
|
|
42
|
+
service_props = Hash.new.merge(props)
|
|
43
|
+
|
|
44
|
+
stale_replicas = service_props.delete(MDFields::STALE_REPLICAS)
|
|
45
|
+
timestamp = service_props.delete(MDFields::SERVICE_TIMESTAMP)
|
|
46
|
+
run_needed = service_props.delete(MDFields::RUN_NEEDED)
|
|
47
|
+
|
|
48
|
+
service_records[name] = ServiceRecord.new(
|
|
49
|
+
properties: service_props,
|
|
50
|
+
stale_replicas: stale_replicas,
|
|
51
|
+
timestamp: timestamp,
|
|
52
|
+
run_needed: run_needed)
|
|
55
53
|
end
|
|
56
|
-
|
|
54
|
+
|
|
57
55
|
MetadataRecord.new(properties: data,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
services: service_records,
|
|
57
|
+
registered: registered,
|
|
58
|
+
deregistered: deregistered,
|
|
59
|
+
checksums: checksums,
|
|
60
|
+
file_size: file_size,
|
|
61
|
+
last_modified: last_modified)
|
|
64
62
|
end
|
|
65
|
-
|
|
63
|
+
|
|
66
64
|
# Load configuration a yaml encoded configuration file
|
|
67
65
|
def self.from_yaml(file_path, digest_algs)
|
|
68
66
|
File.open(file_path, 'r:bom|utf-8') do |f|
|
|
69
67
|
contents = f.read
|
|
70
|
-
|
|
68
|
+
|
|
71
69
|
verify_digests(file_path, contents, digest_algs)
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
YAML.safe_load(contents, [], [], true)
|
|
73
|
+
rescue => err
|
|
74
|
+
raise Longleaf::MetadataError.new("Failed to parse metadata file #{file_path}: #{err.message}")
|
|
75
|
+
end
|
|
74
76
|
end
|
|
75
77
|
end
|
|
76
|
-
|
|
78
|
+
|
|
77
79
|
def self.verify_digests(file_path, contents, digest_algs)
|
|
78
80
|
return if digest_algs.nil? || digest_algs.empty?
|
|
79
|
-
|
|
81
|
+
|
|
80
82
|
digest_algs.each do |alg|
|
|
81
83
|
if file_path.respond_to?(:path)
|
|
82
84
|
path = file_path.path
|
|
@@ -88,18 +90,18 @@ module Longleaf
|
|
|
88
90
|
logger.warn("Missing expected #{alg} digest for #{path}")
|
|
89
91
|
next
|
|
90
92
|
end
|
|
91
|
-
|
|
93
|
+
|
|
92
94
|
digest = DigestHelper::start_digest(alg)
|
|
93
95
|
result = digest.hexdigest(contents)
|
|
94
96
|
existing_digest = IO.read(digest_path)
|
|
95
|
-
|
|
97
|
+
|
|
96
98
|
if result == existing_digest
|
|
97
99
|
logger.info("Metadata fixity check using algorithm '#{alg}' succeeded for file #{path}")
|
|
98
100
|
else
|
|
99
101
|
raise ChecksumMismatchError.new("Metadata digest of type #{alg} did not match the contents of #{path}:" \
|
|
100
|
-
|
|
102
|
+
+ " expected #{existing_digest}, calculated #{result}")
|
|
101
103
|
end
|
|
102
104
|
end
|
|
103
105
|
end
|
|
104
106
|
end
|
|
105
|
-
end
|
|
107
|
+
end
|