longleaf 0.1.0 → 1.1.1

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.
Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +94 -0
  3. data/.editorconfig +13 -0
  4. data/.gitignore +4 -1
  5. data/.rubocop.yml +44 -0
  6. data/.rubocop_todo.yml +834 -0
  7. data/.yardopts +1 -0
  8. data/Gemfile +16 -1
  9. data/README.md +98 -12
  10. data/Rakefile +6 -0
  11. data/bin/setup +16 -1
  12. data/docs/aboutlongleaf.md +28 -0
  13. data/docs/extra.css +32 -0
  14. data/docs/img/change-file.png +0 -0
  15. data/docs/img/ll-example-preserved.png +0 -0
  16. data/docs/index.md +19 -0
  17. data/docs/install.md +66 -0
  18. data/docs/ll-example/config-example-relative.yml +33 -0
  19. data/docs/ll-example/files-dir/LLexample-PDF.pdf +0 -0
  20. data/docs/ll-example/files-dir/LLexample-TOCHANGE.txt +15 -0
  21. data/docs/ll-example/files-dir/LLexample-tokeep.txt +10 -0
  22. data/docs/ll-example/metadata-dir/.gitkeep +0 -0
  23. data/docs/ll-example/replica-files/.gitkeep +0 -0
  24. data/docs/ll-example/replica-metadata/.gitkeep +0 -0
  25. data/docs/quickstart.md +270 -0
  26. data/docs/rdocs/Longleaf.html +135 -0
  27. data/docs/rdocs/Longleaf/AppFields.html +178 -0
  28. data/docs/rdocs/Longleaf/ApplicationConfigDeserializer.html +631 -0
  29. data/docs/rdocs/Longleaf/ApplicationConfigManager.html +610 -0
  30. data/docs/rdocs/Longleaf/ApplicationConfigValidator.html +238 -0
  31. data/docs/rdocs/Longleaf/CLI.html +909 -0
  32. data/docs/rdocs/Longleaf/ChecksumMismatchError.html +151 -0
  33. data/docs/rdocs/Longleaf/ConfigBuilder.html +1339 -0
  34. data/docs/rdocs/Longleaf/ConfigurationError.html +143 -0
  35. data/docs/rdocs/Longleaf/ConfigurationValidator.html +227 -0
  36. data/docs/rdocs/Longleaf/DeregisterCommand.html +420 -0
  37. data/docs/rdocs/Longleaf/DeregisterEvent.html +453 -0
  38. data/docs/rdocs/Longleaf/DeregistrationError.html +151 -0
  39. data/docs/rdocs/Longleaf/DigestHelper.html +419 -0
  40. data/docs/rdocs/Longleaf/EventError.html +147 -0
  41. data/docs/rdocs/Longleaf/EventNames.html +163 -0
  42. data/docs/rdocs/Longleaf/EventStatusTracking.html +656 -0
  43. data/docs/rdocs/Longleaf/FileCheckService.html +540 -0
  44. data/docs/rdocs/Longleaf/FileHelpers.html +520 -0
  45. data/docs/rdocs/Longleaf/FileRecord.html +716 -0
  46. data/docs/rdocs/Longleaf/FileSelector.html +901 -0
  47. data/docs/rdocs/Longleaf/FixityCheckService.html +691 -0
  48. data/docs/rdocs/Longleaf/IndexManager.html +1155 -0
  49. data/docs/rdocs/Longleaf/InvalidDigestAlgorithmError.html +143 -0
  50. data/docs/rdocs/Longleaf/InvalidStoragePathError.html +143 -0
  51. data/docs/rdocs/Longleaf/Logging.html +405 -0
  52. data/docs/rdocs/Longleaf/Logging/RedirectingLogger.html +1213 -0
  53. data/docs/rdocs/Longleaf/LongleafError.html +139 -0
  54. data/docs/rdocs/Longleaf/MDFields.html +193 -0
  55. data/docs/rdocs/Longleaf/MetadataBuilder.html +787 -0
  56. data/docs/rdocs/Longleaf/MetadataDeserializer.html +537 -0
  57. data/docs/rdocs/Longleaf/MetadataError.html +143 -0
  58. data/docs/rdocs/Longleaf/MetadataPersistenceManager.html +539 -0
  59. data/docs/rdocs/Longleaf/MetadataRecord.html +1411 -0
  60. data/docs/rdocs/Longleaf/MetadataSerializer.html +786 -0
  61. data/docs/rdocs/Longleaf/PreservationServiceError.html +147 -0
  62. data/docs/rdocs/Longleaf/PreserveCommand.html +410 -0
  63. data/docs/rdocs/Longleaf/PreserveEvent.html +491 -0
  64. data/docs/rdocs/Longleaf/RegisterCommand.html +428 -0
  65. data/docs/rdocs/Longleaf/RegisterEvent.html +628 -0
  66. data/docs/rdocs/Longleaf/RegisteredFileSelector.html +446 -0
  67. data/docs/rdocs/Longleaf/RegistrationError.html +151 -0
  68. data/docs/rdocs/Longleaf/ReindexCommand.html +576 -0
  69. data/docs/rdocs/Longleaf/RsyncReplicationService.html +1180 -0
  70. data/docs/rdocs/Longleaf/SequelIndexDriver.html +1978 -0
  71. data/docs/rdocs/Longleaf/ServiceCandidateFilesystemIterator.html +572 -0
  72. data/docs/rdocs/Longleaf/ServiceCandidateIndexIterator.html +532 -0
  73. data/docs/rdocs/Longleaf/ServiceCandidateLocator.html +333 -0
  74. data/docs/rdocs/Longleaf/ServiceClassCache.html +725 -0
  75. data/docs/rdocs/Longleaf/ServiceDateHelper.html +425 -0
  76. data/docs/rdocs/Longleaf/ServiceDefinition.html +683 -0
  77. data/docs/rdocs/Longleaf/ServiceDefinitionManager.html +371 -0
  78. data/docs/rdocs/Longleaf/ServiceDefinitionValidator.html +269 -0
  79. data/docs/rdocs/Longleaf/ServiceFields.html +173 -0
  80. data/docs/rdocs/Longleaf/ServiceManager.html +1229 -0
  81. data/docs/rdocs/Longleaf/ServiceMappingManager.html +410 -0
  82. data/docs/rdocs/Longleaf/ServiceMappingValidator.html +347 -0
  83. data/docs/rdocs/Longleaf/ServiceRecord.html +821 -0
  84. data/docs/rdocs/Longleaf/StorageLocation.html +985 -0
  85. data/docs/rdocs/Longleaf/StorageLocationManager.html +729 -0
  86. data/docs/rdocs/Longleaf/StorageLocationUnavailableError.html +143 -0
  87. data/docs/rdocs/Longleaf/StorageLocationValidator.html +373 -0
  88. data/docs/rdocs/Longleaf/StoragePathValidator.html +253 -0
  89. data/docs/rdocs/Longleaf/SystemConfigBuilder.html +441 -0
  90. data/docs/rdocs/Longleaf/SystemConfigFields.html +163 -0
  91. data/docs/rdocs/Longleaf/ValidateConfigCommand.html +451 -0
  92. data/docs/rdocs/Longleaf/ValidateMetadataCommand.html +408 -0
  93. data/docs/rdocs/_index.html +660 -0
  94. data/docs/rdocs/class_list.html +51 -0
  95. data/docs/rdocs/css/common.css +1 -0
  96. data/docs/rdocs/css/full_list.css +58 -0
  97. data/docs/rdocs/css/style.css +496 -0
  98. data/docs/rdocs/file.README.html +165 -0
  99. data/docs/rdocs/file_list.html +56 -0
  100. data/docs/rdocs/frames.html +17 -0
  101. data/docs/rdocs/index.html +165 -0
  102. data/docs/rdocs/js/app.js +303 -0
  103. data/docs/rdocs/js/full_list.js +216 -0
  104. data/docs/rdocs/js/jquery.js +4 -0
  105. data/docs/rdocs/method_list.html +2051 -0
  106. data/docs/rdocs/top-level-namespace.html +110 -0
  107. data/lib/longleaf/candidates/file_selector.rb +150 -0
  108. data/lib/longleaf/candidates/manifest_digest_provider.rb +17 -0
  109. data/lib/longleaf/candidates/physical_path_provider.rb +17 -0
  110. data/lib/longleaf/candidates/registered_file_selector.rb +67 -0
  111. data/lib/longleaf/candidates/service_candidate_filesystem_iterator.rb +93 -0
  112. data/lib/longleaf/candidates/service_candidate_index_iterator.rb +84 -0
  113. data/lib/longleaf/candidates/service_candidate_locator.rb +23 -0
  114. data/lib/longleaf/candidates/single_digest_provider.rb +13 -0
  115. data/lib/longleaf/cli.rb +252 -46
  116. data/lib/longleaf/commands/deregister_command.rb +51 -0
  117. data/lib/longleaf/commands/preserve_command.rb +50 -0
  118. data/lib/longleaf/commands/register_command.rb +34 -43
  119. data/lib/longleaf/commands/reindex_command.rb +92 -0
  120. data/lib/longleaf/commands/validate_config_command.rb +33 -8
  121. data/lib/longleaf/commands/validate_metadata_command.rb +51 -0
  122. data/lib/longleaf/errors.rb +26 -7
  123. data/lib/longleaf/events/deregister_event.rb +53 -0
  124. data/lib/longleaf/events/event_names.rb +9 -0
  125. data/lib/longleaf/events/event_status_tracking.rb +59 -0
  126. data/lib/longleaf/events/preserve_event.rb +82 -0
  127. data/lib/longleaf/events/register_event.rb +59 -51
  128. data/lib/longleaf/helpers/case_insensitive_hash.rb +38 -0
  129. data/lib/longleaf/helpers/digest_helper.rb +56 -0
  130. data/lib/longleaf/helpers/s3_uri_helper.rb +86 -0
  131. data/lib/longleaf/helpers/selection_options_parser.rb +215 -0
  132. data/lib/longleaf/helpers/service_date_helper.rb +78 -0
  133. data/lib/longleaf/indexing/index_manager.rb +101 -0
  134. data/lib/longleaf/indexing/sequel_index_driver.rb +306 -0
  135. data/lib/longleaf/logging.rb +5 -4
  136. data/lib/longleaf/logging/redirecting_logger.rb +30 -25
  137. data/lib/longleaf/models/app_fields.rb +7 -2
  138. data/lib/longleaf/models/file_record.rb +31 -8
  139. data/lib/longleaf/models/filesystem_metadata_location.rb +56 -0
  140. data/lib/longleaf/models/filesystem_storage_location.rb +52 -0
  141. data/lib/longleaf/models/md_fields.rb +3 -1
  142. data/lib/longleaf/models/metadata_location.rb +47 -0
  143. data/lib/longleaf/models/metadata_record.rb +43 -16
  144. data/lib/longleaf/models/s3_storage_location.rb +138 -0
  145. data/lib/longleaf/models/service_definition.rb +7 -6
  146. data/lib/longleaf/models/service_fields.rb +7 -1
  147. data/lib/longleaf/models/service_record.rb +10 -6
  148. data/lib/longleaf/models/storage_location.rb +24 -19
  149. data/lib/longleaf/models/storage_types.rb +9 -0
  150. data/lib/longleaf/models/system_config_fields.rb +9 -0
  151. data/lib/longleaf/preservation_services/file_check_service.rb +59 -0
  152. data/lib/longleaf/preservation_services/fixity_check_service.rb +124 -0
  153. data/lib/longleaf/preservation_services/rsync_replication_service.rb +198 -0
  154. data/lib/longleaf/preservation_services/s3_replication_service.rb +131 -0
  155. data/lib/longleaf/services/application_config_deserializer.rb +81 -24
  156. data/lib/longleaf/services/application_config_manager.rb +20 -6
  157. data/lib/longleaf/services/application_config_validator.rb +19 -9
  158. data/lib/longleaf/services/configuration_validator.rb +67 -4
  159. data/lib/longleaf/services/filesystem_location_validator.rb +16 -0
  160. data/lib/longleaf/services/metadata_deserializer.rb +115 -42
  161. data/lib/longleaf/services/metadata_persistence_manager.rb +47 -0
  162. data/lib/longleaf/services/metadata_serializer.rb +156 -23
  163. data/lib/longleaf/services/metadata_validator.rb +76 -0
  164. data/lib/longleaf/services/s3_location_validator.rb +19 -0
  165. data/lib/longleaf/services/service_class_cache.rb +112 -0
  166. data/lib/longleaf/services/service_definition_manager.rb +10 -7
  167. data/lib/longleaf/services/service_definition_validator.rb +25 -18
  168. data/lib/longleaf/services/service_manager.rb +86 -11
  169. data/lib/longleaf/services/service_mapping_manager.rb +13 -12
  170. data/lib/longleaf/services/service_mapping_validator.rb +36 -26
  171. data/lib/longleaf/services/storage_location_manager.rb +76 -15
  172. data/lib/longleaf/services/storage_location_validator.rb +49 -35
  173. data/lib/longleaf/specs/config_builder.rb +47 -23
  174. data/lib/longleaf/specs/config_validator_helpers.rb +16 -0
  175. data/lib/longleaf/specs/custom_matchers.rb +9 -0
  176. data/lib/longleaf/specs/file_helpers.rb +61 -0
  177. data/lib/longleaf/specs/metadata_builder.rb +98 -0
  178. data/lib/longleaf/specs/system_config_builder.rb +27 -0
  179. data/lib/longleaf/version.rb +1 -1
  180. data/longleaf.gemspec +20 -7
  181. data/mkdocs.yml +21 -0
  182. metadata +308 -24
  183. data/.travis.yml +0 -4
  184. data/lib/longleaf/commands/abstract_command.rb +0 -37
  185. data/lib/longleaf/services/storage_path_validator.rb +0 -16
@@ -1,36 +1,39 @@
1
1
  require_relative '../models/app_fields'
2
2
  require_relative '../models/service_definition'
3
3
 
4
- # Manager which loads and provides access to Longleaf::ServiceDefinition objects
5
4
  module Longleaf
5
+ # Manager which loads and provides access to Longleaf::ServiceDefinition objects
6
6
  class ServiceDefinitionManager
7
7
  SF ||= Longleaf::ServiceFields
8
8
  AF ||= Longleaf::AppFields
9
-
9
+
10
+ # Hash containing the set of configured services, represented as {ServiceDefinition} objects
10
11
  attr_reader :services
11
-
12
+
13
+ # @param config [Hash] hash representation of the application configuration
12
14
  def initialize(config)
13
15
  raise ArgumentError.new("Configuration must be provided") if config.nil? || config.empty?
14
16
 
15
17
  services_config = config[AF::SERVICES]
16
18
  raise ArgumentError.new("Services configuration must be provided") if services_config.nil?
17
-
19
+
18
20
  @services = Hash.new
19
21
  config[AF::SERVICES].each do |name, properties|
20
22
  work_script = properties.delete(SF::WORK_SCRIPT)
23
+ work_class = properties.delete(SF::WORK_CLASS)
21
24
  frequency = properties.delete(SF::FREQUENCY)
22
25
  delay = properties.delete(SF::DELAY)
23
26
  service = Longleaf::ServiceDefinition.new(
24
27
  name: name,
25
28
  work_script: work_script,
29
+ work_class: work_class,
26
30
  frequency: frequency,
27
31
  delay: delay,
28
32
  properties: properties)
29
-
33
+
30
34
  @services[name] = service
31
35
  end
32
36
  @services.freeze
33
37
  end
34
-
35
38
  end
36
- end
39
+ end
@@ -1,32 +1,39 @@
1
1
  require 'pathname'
2
- require_relative '../models/service_fields'
3
- require_relative '../models/app_fields'
4
- require_relative '../errors'
2
+ require 'longleaf/models/service_fields'
3
+ require 'longleaf/models/app_fields'
4
+ require 'longleaf/errors'
5
5
  require_relative 'configuration_validator'
6
6
 
7
- # Validates application configuration of service definitions
8
7
  module Longleaf
8
+ # Validates application configuration of service definitions
9
9
  class ServiceDefinitionValidator < ConfigurationValidator
10
10
  SF ||= Longleaf::ServiceFields
11
11
  AF ||= Longleaf::AppFields
12
-
13
- # Validates configuration to ensure that it is syntactically correct and does not violate
12
+
13
+ # @param config [Hash] hash containing the application configuration
14
+ def initialize(config)
15
+ super(config)
16
+ end
17
+
18
+ protected
19
+ # Validates configuration to ensure that it is syntactically correct and does not violate
14
20
  # schema requirements.
15
21
  # @param config [Hash] hash containing the application configuration
16
- def self.validate_config(config)
17
- assert("Configuration must be a hash, but a #{config.class} was provided", config.class == Hash)
18
- assert("Configuration must contain a root '#{AF::SERVICES}' key", config.key?(AF::SERVICES))
19
- services = config[AF::SERVICES]
22
+ def validate
23
+ assert("Configuration must be a hash, but a #{@config.class} was provided", @config.class == Hash)
24
+ assert("Configuration must contain a root '#{AF::SERVICES}' key", @config.key?(AF::SERVICES))
25
+ services = @config[AF::SERVICES]
20
26
  assert("'#{AF::SERVICES}' must be a hash of services", services.class == Hash)
21
-
22
- existing_paths = Array.new
27
+
23
28
  services.each do |name, properties|
24
- assert("Name of service definition must be a string, but was of type #{name.class}", name.instance_of?(String))
25
- assert("Service definition '#{name}' must be a hash, but a #{properties.class} was provided", properties.is_a?(Hash))
26
-
27
- work_script = properties[SF::WORK_SCRIPT]
28
- assert("Service definition '#{name}' must specify a '#{SF::WORK_SCRIPT}' property", !work_script.nil? && !work_script.empty?)
29
+ register_on_failure do
30
+ assert("Name of service definition must be a string, but was of type #{name.class}", name.instance_of?(String))
31
+ assert("Service definition '#{name}' must be a hash, but a #{properties.class} was provided", properties.is_a?(Hash))
32
+
33
+ work_script = properties[SF::WORK_SCRIPT]
34
+ assert("Service definition '#{name}' must specify a '#{SF::WORK_SCRIPT}' property", !work_script.nil? && !work_script.empty?)
35
+ end
29
36
  end
30
37
  end
31
38
  end
32
- end
39
+ end
@@ -1,21 +1,96 @@
1
- # Manager which provides preservation service definitions based on their mappings
1
+ require 'longleaf/helpers/service_date_helper'
2
+ require 'longleaf/services/service_class_cache'
3
+
2
4
  module Longleaf
5
+ # Manager which provides preservation service definitions based on their mappings
3
6
  class ServiceManager
4
-
5
- def initialize(definition_manager:, mapping_manager:)
7
+ attr_reader :definition_manager
8
+ attr_reader :mapping_manager
9
+
10
+ # @param definition_manager [ServiceDefinitionManager] the service definition manager
11
+ # @param mapping_manager [ServiceMappingManager] the mapping of services to locations
12
+ # @param app_manager [ApplicationConfigManager] manager for storage locations
13
+ def initialize(definition_manager:, mapping_manager:, app_manager:)
6
14
  raise ArgumentError.new('Service definition manager required') if definition_manager.nil?
7
15
  raise ArgumentError.new('Service mappings manager required') if mapping_manager.nil?
16
+ raise ArgumentError.new('Storage location manager required') if app_manager.nil?
8
17
  @definition_manager = definition_manager
9
18
  @mapping_manager = mapping_manager
19
+ @app_manager = app_manager
20
+ @service_class_cache = ServiceClassCache.new(app_manager)
10
21
  end
11
-
12
- # Gets a list of ServiceDefinition objects which match the given criteria
13
- # @param location [String] name of the location to lookup
14
- # @return [Array] a list of ServiceDefinition objects associated with the location,
15
- # or an empty list if no services match the criteria
16
- def list_service_definitions(location: nil)
22
+
23
+ # Return a service instance instance for provided service name.
24
+ # @param service_name [String] name of the service
25
+ # @return Preservation service class for the provided name
26
+ # @raise ArgumentError if service_name does not reference an existing service
27
+ def service(service_name)
28
+ raise ArgumentError.new('Service name is required') if service_name.nil? || service_name.empty?
29
+ raise ArgumentError.new("No service with name #{service_name}") unless @definition_manager.services.key?(service_name)
30
+ definition = @definition_manager.services[service_name]
31
+ @service_class_cache.service_instance(definition)
32
+ end
33
+
34
+ # List the names of services which are applicable to the given criteria
35
+ # @param location [String] name of the locations to lookup
36
+ # @param event [String] name of the preservation event taking place
37
+ # @return [Array] a list of service names which match the provided criteria
38
+ def list_services(location: nil, event: nil)
17
39
  service_names = @mapping_manager.list_services(location)
18
- service_names.collect { |name| @definition_manager.services[name] }
40
+ if !event.nil?
41
+ # Filter service names down by event
42
+ service_names.select { |name| applicable_for_event?(name, event) }
43
+ else
44
+ service_names
45
+ end
46
+ end
47
+
48
+ # List definitions for services which are applicable to the given criteria
49
+ # @param location [String] name of the locations to lookup
50
+ # @param event [String] name of the preservation event taking place
51
+ # @return [Array] List of service definitions which match the provided criteria
52
+ def list_service_definitions(location: nil, event: nil)
53
+ names = list_services(location: location, event: event)
54
+ names.map { |name| @definition_manager.services[name] }
55
+ end
56
+
57
+ # Determines if a service is applicable for a specific preservation event
58
+ # @param service_name [String] name of the service being evaluated
59
+ # @param event [String] name of the event to check against
60
+ # @return [Boolean] true if the service is applicable for the event
61
+ def applicable_for_event?(service_name, event)
62
+ service(service_name).is_applicable?(event)
63
+ end
64
+
65
+ # Determine if a service should run for a particular file based on the service's definition and
66
+ # the file's service related metadata.
67
+ # @param service_name [String] name of the service being evaluated
68
+ # @param md_rec [MetadataRecord] metadata record for the file being evaluated
69
+ # @return [Boolean] true if the service should be run.
70
+ def service_needed?(service_name, md_rec)
71
+ service_rec = md_rec.service(service_name)
72
+ return true if !service_rec.nil? && service_rec.run_needed
73
+
74
+ definition = @definition_manager.services[service_name]
75
+
76
+ next_run = ServiceDateHelper.next_run_needed(md_rec, definition)
77
+
78
+ return false if next_run.nil?
79
+
80
+ # If next run timestamp has passed then service is needed
81
+ now = ServiceDateHelper.formatted_timestamp
82
+ now >= next_run
83
+ end
84
+
85
+ # Perform the specified service on the file record, in the context of the specified event
86
+ # @param service_name [String] name of the service
87
+ # @param file_rec [FileRecord] file record to perform service upon
88
+ # @param event [String] name of the event service is being performed within.
89
+ def perform_service(service_name, file_rec, event)
90
+ definition = @definition_manager.services[service_name]
91
+
92
+ service = @service_class_cache.service_instance(definition)
93
+ service.perform(file_rec, event)
19
94
  end
20
95
  end
21
- end
96
+ end
@@ -1,29 +1,30 @@
1
- require_relative '../models/app_fields'
2
- require_relative '../models/service_definition'
1
+ require 'longleaf/models/app_fields'
2
+ require 'longleaf/models/service_definition'
3
3
 
4
- # Manager which loads and provides access to location to service mappings
5
4
  module Longleaf
5
+ # Manager which loads and provides access to location to service mappings
6
6
  class ServiceMappingManager
7
7
  AF ||= Longleaf::AppFields
8
-
8
+
9
+ # @param config [Hash] has representation of the application configuration
9
10
  def initialize(config)
10
11
  raise ArgumentError.new("Configuration must be provided") if config.nil? || config.empty?
11
12
 
12
13
  mappings_config = config[AF::SERVICE_MAPPINGS]
13
14
  raise ArgumentError.new("Service mappings configuration must be provided") if mappings_config.nil?
14
-
15
+
15
16
  @loc_to_services = Hash.new
16
-
17
+
17
18
  mappings_config.each do |mapping|
18
19
  locations = mapping[AF::LOCATIONS]
19
20
  services = mapping[AF::SERVICES]
20
-
21
+
21
22
  locations = [locations] if locations.is_a?(String)
22
23
  services = [services] if services.is_a?(String)
23
-
24
+
24
25
  locations.each do |loc_name|
25
26
  @loc_to_services[loc_name] = Array.new unless @loc_to_services.key?(loc_name)
26
-
27
+
27
28
  service_set = @loc_to_services[loc_name]
28
29
  if services.is_a?(String)
29
30
  service_set.push(services)
@@ -32,11 +33,11 @@ module Longleaf
32
33
  end
33
34
  end
34
35
  end
35
-
36
+
36
37
  @loc_to_services.each { |loc, services| services.uniq! }
37
38
  @loc_to_services.freeze
38
39
  end
39
-
40
+
40
41
  # Gets a list of service names associated with the given location
41
42
  # @param loc_name [String] name of the location to lookup
42
43
  # @return [Array] a list of service names associated with the location
@@ -44,4 +45,4 @@ module Longleaf
44
45
  @loc_to_services[loc_name] || []
45
46
  end
46
47
  end
47
- end
48
+ end
@@ -1,49 +1,59 @@
1
1
  require 'pathname'
2
- require_relative '../models/service_fields'
3
- require_relative '../models/app_fields'
4
- require_relative '../errors'
2
+ require 'longleaf/models/service_fields'
3
+ require 'longleaf/models/app_fields'
4
+ require 'longleaf/errors'
5
5
  require_relative 'configuration_validator'
6
6
 
7
- # Validates application configuration of service to location mappings
8
7
  module Longleaf
8
+ # Validates application configuration of service to location mappings
9
9
  class ServiceMappingValidator < ConfigurationValidator
10
10
  AF ||= Longleaf::AppFields
11
-
11
+
12
+ # @param config [Hash] hash containing the application configuration
13
+ def initialize(config)
14
+ super(config)
15
+ end
16
+
17
+ protected
12
18
  # Validates service mapping configuration to ensure that it is syntactically and referentially correct.
13
19
  # @param config [Hash] hash containing the application configuration
14
- def self.validate_config(config)
15
-
16
- assert("Configuration must be a hash, but a #{config.class} was provided", config.class == Hash)
17
- assert("Configuration must contain a root '#{AF::SERVICE_MAPPINGS}' key", config.key?(AF::SERVICE_MAPPINGS))
18
- mappings = config[AF::SERVICE_MAPPINGS]
20
+ def validate
21
+ assert("Configuration must be a hash, but a #{@config.class} was provided", @config.class == Hash)
22
+ assert("Configuration must contain a root '#{AF::SERVICE_MAPPINGS}' key", @config.key?(AF::SERVICE_MAPPINGS))
23
+ mappings = @config[AF::SERVICE_MAPPINGS]
19
24
  return if mappings.nil? || mappings.empty?
20
25
  assert("'#{AF::SERVICE_MAPPINGS}' must be an array of mappings", mappings.is_a?(Array))
21
-
22
- service_names = config[AF::SERVICES].keys
23
- location_names = config[AF::LOCATIONS].keys
24
-
25
- existing_paths = Array.new
26
+
27
+ service_names = @config[AF::SERVICES].keys
28
+ location_names = @config[AF::LOCATIONS].keys
29
+
26
30
  mappings.each do |mapping|
27
- assert("Mapping must be a hash, but received #{mapping.inspect} instead", mapping.is_a?(Hash))
28
-
29
- validate_mapping_field(AF::LOCATIONS, mapping, location_names)
30
- validate_mapping_field(AF::SERVICES, mapping, service_names)
31
+ register_on_failure do
32
+ assert("Mapping must be a hash, but received #{mapping.inspect} instead", mapping.is_a?(Hash))
33
+
34
+ register_on_failure { validate_mapping_field(AF::LOCATIONS, mapping, location_names) }
35
+ register_on_failure { validate_mapping_field(AF::SERVICES, mapping, service_names) }
36
+ end
31
37
  end
38
+
39
+ @result
32
40
  end
33
-
41
+
34
42
  private
35
- def self.validate_mapping_field(field, mapping, valid_values)
43
+ def validate_mapping_field(field, mapping, valid_values)
36
44
  assert("Mapping must contain a '#{field}' field", mapping.key?(field))
37
45
  field_values = mapping[field]
38
- assert("Mapping '#{field}' field must be either a string or an array, but received '#{field_values.inspect}' instead",
46
+ assert("Mapping '#{field}' field must be either a string or an array, but received '#{field_values.class}' instead",
39
47
  field_values.is_a?(Array) || field_values.is_a?(String))
40
48
  assert("Mapping must specify one or more value in the '#{field}' field", !field_values.empty?)
41
-
49
+
42
50
  check_values = field_values.is_a?(String) ? [field_values] : field_values
43
51
  check_values.each do |value|
44
- assert("Mapping '#{field}' specifies value '#{value}', but no #{field} with that name exist",
45
- valid_values.include?(value))
52
+ register_on_failure do
53
+ assert("Mapping specifies value '#{value}', but no #{field} with that name exist",
54
+ valid_values.include?(value))
55
+ end
46
56
  end
47
57
  end
48
58
  end
49
- end
59
+ end
@@ -1,37 +1,98 @@
1
- require_relative '../models/app_fields'
2
- require_relative '../models/storage_location'
1
+ require 'longleaf/models/app_fields'
2
+ require 'longleaf/models/storage_types'
3
+ require 'longleaf/models/filesystem_storage_location'
4
+ require 'longleaf/models/s3_storage_location'
5
+ require 'longleaf/models/filesystem_metadata_location'
6
+ require 'longleaf/errors'
3
7
 
4
- # Manager which loads and provides access to Longleaf::StorageLocation objects
5
8
  module Longleaf
9
+ # Manager which loads and provides access to {StorageLocation} objects
6
10
  class StorageLocationManager
7
11
  AF ||= Longleaf::AppFields
8
-
12
+ ST ||= Longleaf::StorageTypes
13
+
14
+ # Hash mapping storage location names to {StorageLocation} objects
9
15
  attr_reader :locations
10
-
16
+ # Mapping of storage types to storage location classes
17
+ @@storage_type_mappings = {
18
+ ST::FILESYSTEM_STORAGE_TYPE => Longleaf::FilesystemStorageLocation,
19
+ ST::S3_STORAGE_TYPE => Longleaf::S3StorageLocation
20
+ }
21
+ @@metadata_type_mappings = { ST::FILESYSTEM_STORAGE_TYPE => Longleaf::FilesystemMetadataLocation }
22
+
23
+ # @param config [Hash] has representation of the application configuration
11
24
  def initialize(config)
12
25
  raise ArgumentError.new("Configuration must be provided") if config&.empty?
13
26
 
14
27
  @locations = Hash.new
15
28
  config[AF::LOCATIONS].each do |name, properties|
16
- path = properties[AF::LOCATION_PATH]
17
- md_path = properties[AF::METADATA_PATH]
18
- location = Longleaf::StorageLocation.new(name: name, path: path, metadata_path: md_path)
19
-
20
- @locations[name] = location
29
+ md_loc = instantiate_metadata_location(properties)
30
+
31
+ @locations[name] = instantiate_storage_location(name, properties, md_loc)
21
32
  end
22
33
  @locations.freeze
23
34
  end
24
-
25
- # Get the StorageLocation object which should contain the given path
35
+
36
+ # Get the {StorageLocation} object which should contain the given path
26
37
  # @return [Longleaf::StorageLocation] location containing the given path
27
38
  # or nil if the path is not contained by a registered location.
28
39
  def get_location_by_path(path)
29
40
  raise ArgumentError.new("Path parameter is required") if path.nil? || path.empty?
30
41
  @locations.each do |name, location|
31
- return location if path.start_with?(location.path)
42
+ return location if location.contains?(path)
43
+ end
44
+
45
+ nil
46
+ end
47
+
48
+ # Get the {StorageLocation} object which should contain the given metadata path
49
+ # @return [Longleaf::StorageLocation] location containing the given metadata path
50
+ # or nil if the path is not contained by a registered location.
51
+ def get_location_by_metadata_path(md_path)
52
+ raise ArgumentError.new("Metadata path parameter is required") if md_path.nil? || md_path.empty?
53
+ @locations.each do |name, location|
54
+ return location if location.metadata_location.contains?(md_path)
32
55
  end
33
-
56
+
34
57
  nil
35
58
  end
59
+
60
+ # Raises a {StorageLocationUnavailableError} if the given path is not in a known storage location,
61
+ # or if it is not within the expected location if provided
62
+ # @param path [String] file path
63
+ # @param expected_loc [String] name of the storage location which path should be contained by
64
+ # @raise [StorageLocationUnavailableError] if the path is not in a known/expected storage location
65
+ # @return [StorageLocation] the storage location which contains path, if it was within one.
66
+ def verify_path_in_location(path, expected_loc = nil)
67
+ location = get_location_by_path(path)
68
+ if location.nil?
69
+ raise StorageLocationUnavailableError.new("Path #{path} is not from a known storage location.")
70
+ elsif !expected_loc.nil? && expected_loc != location.name
71
+ raise StorageLocationUnavailableError.new("Path #{path} is not contained by storage location #{expected_loc}.")
72
+ end
73
+ location
74
+ end
75
+
76
+ private
77
+ def instantiate_metadata_location(loc_properties)
78
+ m_config = loc_properties[AF::METADATA_CONFIG]
79
+ m_type = m_config[AF::STORAGE_TYPE]
80
+ m_type = ST::FILESYSTEM_STORAGE_TYPE if m_type.nil?
81
+
82
+ m_class = @@metadata_type_mappings[m_type]
83
+ raise ArgumentError.new("Unknown metadata location type #{m_type}") if m_class.nil?
84
+
85
+ m_class.new(m_config)
86
+ end
87
+
88
+ def instantiate_storage_location(name, properties, md_loc)
89
+ s_type = properties[AF::STORAGE_TYPE]
90
+ s_type = ST::FILESYSTEM_STORAGE_TYPE if s_type.nil?
91
+
92
+ s_class = @@storage_type_mappings[s_type]
93
+ raise ArgumentError.new("Unknown storage location type #{s_type}") if s_class.nil?
94
+
95
+ s_class.new(name, properties, md_loc)
96
+ end
36
97
  end
37
- end
98
+ end