longleaf 0.1.0.pre.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/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +43 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/longleaf +3 -0
- data/lib/longleaf/cli.rb +78 -0
- data/lib/longleaf/commands/abstract_command.rb +37 -0
- data/lib/longleaf/commands/register_command.rb +59 -0
- data/lib/longleaf/commands/validate_config_command.rb +29 -0
- data/lib/longleaf/errors.rb +15 -0
- data/lib/longleaf/events/register_event.rb +98 -0
- data/lib/longleaf/logging/redirecting_logger.rb +131 -0
- data/lib/longleaf/logging.rb +26 -0
- data/lib/longleaf/models/app_fields.rb +10 -0
- data/lib/longleaf/models/file_record.rb +25 -0
- data/lib/longleaf/models/md_fields.rb +18 -0
- data/lib/longleaf/models/metadata_record.rb +57 -0
- data/lib/longleaf/models/service_definition.rb +21 -0
- data/lib/longleaf/models/service_fields.rb +10 -0
- data/lib/longleaf/models/service_record.rb +27 -0
- data/lib/longleaf/models/storage_location.rb +37 -0
- data/lib/longleaf/services/application_config_deserializer.rb +46 -0
- data/lib/longleaf/services/application_config_manager.rb +24 -0
- data/lib/longleaf/services/application_config_validator.rb +18 -0
- data/lib/longleaf/services/configuration_validator.rb +8 -0
- data/lib/longleaf/services/metadata_deserializer.rb +68 -0
- data/lib/longleaf/services/metadata_serializer.rb +76 -0
- data/lib/longleaf/services/service_definition_manager.rb +36 -0
- data/lib/longleaf/services/service_definition_validator.rb +32 -0
- data/lib/longleaf/services/service_manager.rb +21 -0
- data/lib/longleaf/services/service_mapping_manager.rb +47 -0
- data/lib/longleaf/services/service_mapping_validator.rb +49 -0
- data/lib/longleaf/services/storage_location_manager.rb +37 -0
- data/lib/longleaf/services/storage_location_validator.rb +60 -0
- data/lib/longleaf/services/storage_path_validator.rb +16 -0
- data/lib/longleaf/specs/config_builder.rb +102 -0
- data/lib/longleaf/version.rb +3 -0
- data/lib/longleaf.rb +4 -0
- data/longleaf.gemspec +34 -0
- metadata +188 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative 'md_fields'
|
2
|
+
require_relative 'service_record'
|
3
|
+
|
4
|
+
# Metadata record for a single file
|
5
|
+
module Longleaf
|
6
|
+
class MetadataRecord
|
7
|
+
attr_reader :deregistered, :registered
|
8
|
+
attr_reader :checksums
|
9
|
+
attr_reader :properties
|
10
|
+
attr_accessor :file_size, :last_modified
|
11
|
+
|
12
|
+
# @param properties [Hash] initial data properties for this record
|
13
|
+
# @param services [Hash] initial service property tree
|
14
|
+
# @param deregistered [String] deregistered timestamp
|
15
|
+
# @param registered [String] registered timestamp
|
16
|
+
# @param checksums [Hash] hash of checksum values
|
17
|
+
# @param file_size [Integer] size of file in bytes
|
18
|
+
# @param last_modified [String] iso8601 representation of the last modified date of file
|
19
|
+
def initialize(properties: Hash.new, services: Hash.new, deregistered: nil, registered: nil, checksums: Hash.new,
|
20
|
+
file_size: nil, last_modified: nil)
|
21
|
+
@properties = properties
|
22
|
+
@registered = registered
|
23
|
+
@deregistered = deregistered
|
24
|
+
@checksums = checksums
|
25
|
+
@services = services
|
26
|
+
@file_size = file_size
|
27
|
+
@last_modified = last_modified
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Boolean] true if the record is deregistered
|
31
|
+
def deregistered?
|
32
|
+
!@deregistered.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Adds a service to this record
|
36
|
+
#
|
37
|
+
# @param name [String] identifier for the service being added
|
38
|
+
# @param service_properties [ServiceRecord] properties for populating the new service
|
39
|
+
def add_service(name, service = Longleaf::ServiceRecord.new)
|
40
|
+
raise ArgumentError.new("Value must be a ServiceRecord object when adding a service") unless service.class == Longleaf::ServiceRecord
|
41
|
+
raise IndexError.new("Service with name '#{name}' already exists") if @services.key?(name)
|
42
|
+
|
43
|
+
@services[name] = service
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param name [String] name identifier of the service to retrieve
|
47
|
+
# @return [ServiceRecord] the ServiceRecord for the service identified by name, or nil
|
48
|
+
def service(name)
|
49
|
+
@services[name]
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Array<String>] a list of name identifiers for services registered to this record
|
53
|
+
def list_services
|
54
|
+
@services.keys
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'service_fields'
|
2
|
+
|
3
|
+
# Definition of a preservation service
|
4
|
+
module Longleaf
|
5
|
+
class ServiceDefinition
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :work_script
|
8
|
+
attr_reader :frequency, :delay
|
9
|
+
attr_reader :properties
|
10
|
+
|
11
|
+
def initialize(name:, work_script:, frequency: nil, delay: nil, properties: Hash.new)
|
12
|
+
raise ArgumentError.new("Parameters name and work_script are required") unless name && work_script
|
13
|
+
|
14
|
+
@properties = properties
|
15
|
+
@name = name
|
16
|
+
@work_script = work_script
|
17
|
+
@frequency = frequency
|
18
|
+
@delay = delay
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Record for an individual service in a file's metadata record.
|
2
|
+
module Longleaf
|
3
|
+
class ServiceRecord
|
4
|
+
attr_reader :properties
|
5
|
+
attr_accessor :stale_replicas, :timestamp, :run_needed
|
6
|
+
|
7
|
+
# @param properties [Hash] initial properties for this service record
|
8
|
+
def initialize(properties: Hash.new, stale_replicas: false, timestamp: nil, run_needed: false)
|
9
|
+
raise ArgumentError.new("Service properties must be a hash") if properties.class != Hash
|
10
|
+
|
11
|
+
@properties = properties
|
12
|
+
@timestamp = timestamp
|
13
|
+
@stale_replicas = stale_replicas
|
14
|
+
@run_needed = run_needed
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return the value of a service property identified by key
|
18
|
+
def [](key)
|
19
|
+
@properties[key]
|
20
|
+
end
|
21
|
+
|
22
|
+
# set the value of a service property identified by key
|
23
|
+
def []=(key, value)
|
24
|
+
@properties[key] = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'longleaf/services/metadata_serializer'
|
2
|
+
|
3
|
+
module Longleaf
|
4
|
+
class StorageLocation
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :path
|
7
|
+
attr_reader :metadata_path
|
8
|
+
|
9
|
+
def initialize(name:, path:, metadata_path:)
|
10
|
+
raise ArgumentError.new("Parameters name, path and metadata_path are required") unless name && path && metadata_path
|
11
|
+
|
12
|
+
@path = path
|
13
|
+
@name = name
|
14
|
+
@metadata_path = metadata_path
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the path for the metadata file for the given file path located in this storage location.
|
18
|
+
# @param file_path [String] path of the file
|
19
|
+
# @raise [ArgumentError] if the file_path is not provided or is not in this storage location.
|
20
|
+
def get_metadata_path_for(file_path)
|
21
|
+
raise ArgumentError.new("A file_path parameter is required") if file_path.nil? || file_path.empty?
|
22
|
+
raise ArgumentError.new("Provided file path is not contained by storage location #{@name}: #{file_path}") \
|
23
|
+
unless file_path.start_with?(@path)
|
24
|
+
|
25
|
+
file_path.sub(/^#{@path}/, metadata_path) + MetadataSerializer::metadata_suffix
|
26
|
+
end
|
27
|
+
|
28
|
+
# Checks that the path and metadata path defined in this location are available
|
29
|
+
# @raise [StorageLocationUnavailableError] if the storage location is not available
|
30
|
+
def available?
|
31
|
+
raise StorageLocationUnavailableError.new("Path does not exist or is not a directory: #{@path}")\
|
32
|
+
unless Dir.exist?(@path)
|
33
|
+
raise StorageLocationUnavailableError.new("Metadata path does not exist or is not a directory: #{@metadata_path}")\
|
34
|
+
unless Dir.exist?(@metadata_path)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'longleaf/services/application_config_validator'
|
2
|
+
require 'longleaf/services/application_config_manager'
|
3
|
+
|
4
|
+
# Deserializer for application configuration files
|
5
|
+
module Longleaf
|
6
|
+
class ApplicationConfigDeserializer
|
7
|
+
|
8
|
+
# Deserializes a valid application configuration file as a ApplicationConfigManager option
|
9
|
+
# @param config_path [String] file path to the application configuration file
|
10
|
+
# @param format [String] encoding format of the config file
|
11
|
+
# return [Longleaf::ApplicationConfigManager] manager for the loaded configuration
|
12
|
+
def self.deserialize(config_path, format: 'yaml')
|
13
|
+
config = load(config_path, format: format)
|
14
|
+
|
15
|
+
Longleaf::ApplicationConfigValidator.validate(config)
|
16
|
+
Longleaf::ApplicationConfigManager.new(config)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Deserialize a configuration file into a hash
|
20
|
+
# @param config_path [String] file path to the application configuration file
|
21
|
+
# @param format [String] encoding format of the config file
|
22
|
+
# return [Hash] hash containing the configuration
|
23
|
+
def self.load(config_path, format: 'yaml')
|
24
|
+
case format
|
25
|
+
when 'yaml'
|
26
|
+
from_yaml(config_path)
|
27
|
+
else
|
28
|
+
raise ArgumentError.new('Invalid deserialization format #{format} specified')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def self.from_yaml(config_path)
|
34
|
+
begin
|
35
|
+
YAML.load_file(config_path)
|
36
|
+
rescue Errno::ENOENT => err
|
37
|
+
raise Longleaf::ConfigurationError.new(
|
38
|
+
"Cannot load application configuration, file #{config_path} does not exist.")
|
39
|
+
rescue => err
|
40
|
+
raise Longleaf::ConfigurationError.new(
|
41
|
+
%Q(Failed to load application configuration due to the following reason:
|
42
|
+
#{err.message}))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'storage_location_validator'
|
2
|
+
require_relative 'storage_location_manager'
|
3
|
+
require_relative 'service_definition_validator'
|
4
|
+
require_relative 'service_definition_manager'
|
5
|
+
require_relative 'service_mapping_validator'
|
6
|
+
require_relative 'service_mapping_manager'
|
7
|
+
require_relative 'service_manager'
|
8
|
+
|
9
|
+
# Manager which loads and provides access to the configuration of the application
|
10
|
+
module Longleaf
|
11
|
+
class ApplicationConfigManager
|
12
|
+
attr_reader :service_manager
|
13
|
+
attr_reader :location_manager
|
14
|
+
|
15
|
+
def initialize(config)
|
16
|
+
@location_manager = Longleaf::StorageLocationManager.new(config)
|
17
|
+
|
18
|
+
definition_manager = Longleaf::ServiceDefinitionManager.new(config)
|
19
|
+
mapping_manager = Longleaf::ServiceMappingManager.new(config)
|
20
|
+
@service_manager = Longleaf::ServiceManager.new(
|
21
|
+
definition_manager: definition_manager, mapping_manager: mapping_manager)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'storage_location_validator'
|
2
|
+
require_relative 'service_definition_validator'
|
3
|
+
require_relative 'service_mapping_validator'
|
4
|
+
|
5
|
+
# Validator for Longleaf application configuration
|
6
|
+
module Longleaf
|
7
|
+
class ApplicationConfigValidator
|
8
|
+
|
9
|
+
# Validates the application configuration provided. Will raise ConfigurationError
|
10
|
+
# if any portion of the configuration is not syntactically or semantically valid.
|
11
|
+
# @param config [Hash] application configuration
|
12
|
+
def self.validate(config)
|
13
|
+
Longleaf::StorageLocationValidator::validate_config(config)
|
14
|
+
Longleaf::ServiceDefinitionValidator::validate_config(config)
|
15
|
+
Longleaf::ServiceMappingValidator::validate_config(config)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require_relative '../models/metadata_record'
|
3
|
+
require_relative '../models/md_fields'
|
4
|
+
require_relative '../errors'
|
5
|
+
|
6
|
+
# Service which deserializes metadata files into MetadataRecord objects
|
7
|
+
module Longleaf
|
8
|
+
class MetadataDeserializer
|
9
|
+
MDF = Longleaf::MDFields
|
10
|
+
|
11
|
+
# Deserialize a file into a MetadataRecord object
|
12
|
+
#
|
13
|
+
# @param file_path [String] path of the file to read. Required.
|
14
|
+
# @param format [String] format the file is stored in. Default is 'yaml'.
|
15
|
+
def self.deserialize(file_path:, format: 'yaml')
|
16
|
+
case format
|
17
|
+
when 'yaml'
|
18
|
+
md = from_yaml(file_path)
|
19
|
+
else
|
20
|
+
raise ArgumentError.new('Invalid deserialization format #{format} specified')
|
21
|
+
end
|
22
|
+
|
23
|
+
if !md || !md.key?(MDF::DATA) || !md.key?(MDF::SERVICES)
|
24
|
+
raise Longleaf::MetadataError.new("Invalid metadata file, did not contain data or services fields: #{file_path}")
|
25
|
+
end
|
26
|
+
|
27
|
+
data = Hash.new.merge(md[MDF::DATA])
|
28
|
+
# Extract reserved properties for submission as separate parameters
|
29
|
+
registered = data.delete(MDFields::REGISTERED_TIMESTAMP)
|
30
|
+
deregistered = data.delete(MDFields::DEREGISTERED_TIMESTAMP)
|
31
|
+
checksums = data.delete(MDFields::CHECKSUMS)
|
32
|
+
file_size = data.delete(MDFields::FILE_SIZE)
|
33
|
+
last_modified = data.delete(MDFields::LAST_MODIFIED)
|
34
|
+
|
35
|
+
services = md[MDF::SERVICES]
|
36
|
+
service_records = Hash.new
|
37
|
+
unless services.nil?
|
38
|
+
services.each do |name, props|
|
39
|
+
raise Longleaf::MetadataError.new("Value of service #{name} must be a hash") unless props.class == Hash
|
40
|
+
|
41
|
+
service_props = Hash.new.merge(props)
|
42
|
+
|
43
|
+
stale_replicas = service_props.delete(MDFields::STALE_REPLICAS)
|
44
|
+
timestamp = service_props.delete(MDFields::SERVICE_TIMESTAMP)
|
45
|
+
run_needed = service_props.delete(MDFields::RUN_NEEDED)
|
46
|
+
|
47
|
+
service_records[name] = ServiceRecord.new(
|
48
|
+
properties: service_props,
|
49
|
+
stale_replicas: stale_replicas,
|
50
|
+
timestamp: timestamp,
|
51
|
+
run_needed: run_needed)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
MetadataRecord.new(properties: data,
|
56
|
+
services: service_records,
|
57
|
+
registered: registered,
|
58
|
+
deregistered: deregistered,
|
59
|
+
checksums: checksums,
|
60
|
+
file_size: file_size,
|
61
|
+
last_modified: last_modified)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.from_yaml(file_path)
|
65
|
+
YAML.load_file(file_path)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'longleaf/models/metadata_record'
|
3
|
+
require 'longleaf/models/md_fields'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
# Service which serializes MetadataRecord objects
|
7
|
+
module Longleaf
|
8
|
+
class MetadataSerializer
|
9
|
+
MDF = Longleaf::MDFields
|
10
|
+
|
11
|
+
# Serialize the contents of the provided metadata record to the specified path
|
12
|
+
#
|
13
|
+
# @param metadata [MetadataRecord] metadata record to serialize. Required.
|
14
|
+
# @param file_path [String] path to write the file to. Required.
|
15
|
+
# @param format [String] format to serialize the metadata in. Default is 'yaml'.
|
16
|
+
def self.write(metadata:, file_path:, format: 'yaml')
|
17
|
+
raise ArgumentError.new('metadata parameter must be a MetadataRecord') \
|
18
|
+
unless metadata.class == Longleaf::MetadataRecord
|
19
|
+
|
20
|
+
case format
|
21
|
+
when 'yaml'
|
22
|
+
content = to_yaml(metadata)
|
23
|
+
else
|
24
|
+
raise ArgumentError.new('Invalid serialization format #{format} specified')
|
25
|
+
end
|
26
|
+
|
27
|
+
# Fill in parent directories if they do not exist
|
28
|
+
parent_dir = Pathname(file_path).parent
|
29
|
+
parent_dir.mkpath unless parent_dir.exist?
|
30
|
+
|
31
|
+
File.write(file_path, content)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param metadata [MetadataRecord] metadata record to transform
|
35
|
+
# @return [String] a yaml representation of the provided MetadataRecord
|
36
|
+
def self.to_yaml(metadata)
|
37
|
+
props = to_hash(metadata)
|
38
|
+
props.to_yaml
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.to_hash(metadata)
|
42
|
+
props = Hash.new
|
43
|
+
|
44
|
+
data = Hash.new.merge(metadata.properties)
|
45
|
+
data[MDF::REGISTERED_TIMESTAMP] = metadata.registered if metadata.registered
|
46
|
+
data[MDF::DEREGISTERED_TIMESTAMP] = metadata.deregistered if metadata.deregistered
|
47
|
+
data[MDF::CHECKSUMS] = metadata.checksums unless metadata.checksums&.empty?
|
48
|
+
data[MDF::FILE_SIZE] = metadata.file_size unless metadata.file_size.nil?
|
49
|
+
data[MDF::LAST_MODIFIED] = metadata.last_modified if metadata.last_modified
|
50
|
+
|
51
|
+
props[MDF::DATA] = data
|
52
|
+
|
53
|
+
services = Hash.new
|
54
|
+
metadata.list_services.each do |name|
|
55
|
+
service = metadata.service(name)
|
56
|
+
service[MDF::STALE_REPLICAS] = service.stale_replicas if service.stale_replicas
|
57
|
+
service[MDF::SERVICE_TIMESTAMP] = service.timestamp unless service.timestamp.nil?
|
58
|
+
service[MDF::RUN_NEEDED] = service.run_needed if service.run_needed
|
59
|
+
services[name] = service.properties
|
60
|
+
end
|
61
|
+
|
62
|
+
props[MDF::SERVICES] = services
|
63
|
+
|
64
|
+
props
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.metadata_suffix(format: 'yaml')
|
68
|
+
case format
|
69
|
+
when 'yaml'
|
70
|
+
'-llmd.yaml'
|
71
|
+
else
|
72
|
+
raise ArgumentError.new('Invalid serialization format #{format} specified')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative '../models/app_fields'
|
2
|
+
require_relative '../models/service_definition'
|
3
|
+
|
4
|
+
# Manager which loads and provides access to Longleaf::ServiceDefinition objects
|
5
|
+
module Longleaf
|
6
|
+
class ServiceDefinitionManager
|
7
|
+
SF ||= Longleaf::ServiceFields
|
8
|
+
AF ||= Longleaf::AppFields
|
9
|
+
|
10
|
+
attr_reader :services
|
11
|
+
|
12
|
+
def initialize(config)
|
13
|
+
raise ArgumentError.new("Configuration must be provided") if config.nil? || config.empty?
|
14
|
+
|
15
|
+
services_config = config[AF::SERVICES]
|
16
|
+
raise ArgumentError.new("Services configuration must be provided") if services_config.nil?
|
17
|
+
|
18
|
+
@services = Hash.new
|
19
|
+
config[AF::SERVICES].each do |name, properties|
|
20
|
+
work_script = properties.delete(SF::WORK_SCRIPT)
|
21
|
+
frequency = properties.delete(SF::FREQUENCY)
|
22
|
+
delay = properties.delete(SF::DELAY)
|
23
|
+
service = Longleaf::ServiceDefinition.new(
|
24
|
+
name: name,
|
25
|
+
work_script: work_script,
|
26
|
+
frequency: frequency,
|
27
|
+
delay: delay,
|
28
|
+
properties: properties)
|
29
|
+
|
30
|
+
@services[name] = service
|
31
|
+
end
|
32
|
+
@services.freeze
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require_relative '../models/service_fields'
|
3
|
+
require_relative '../models/app_fields'
|
4
|
+
require_relative '../errors'
|
5
|
+
require_relative 'configuration_validator'
|
6
|
+
|
7
|
+
# Validates application configuration of service definitions
|
8
|
+
module Longleaf
|
9
|
+
class ServiceDefinitionValidator < ConfigurationValidator
|
10
|
+
SF ||= Longleaf::ServiceFields
|
11
|
+
AF ||= Longleaf::AppFields
|
12
|
+
|
13
|
+
# Validates configuration to ensure that it is syntactically correct and does not violate
|
14
|
+
# schema requirements.
|
15
|
+
# @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]
|
20
|
+
assert("'#{AF::SERVICES}' must be a hash of services", services.class == Hash)
|
21
|
+
|
22
|
+
existing_paths = Array.new
|
23
|
+
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
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Manager which provides preservation service definitions based on their mappings
|
2
|
+
module Longleaf
|
3
|
+
class ServiceManager
|
4
|
+
|
5
|
+
def initialize(definition_manager:, mapping_manager:)
|
6
|
+
raise ArgumentError.new('Service definition manager required') if definition_manager.nil?
|
7
|
+
raise ArgumentError.new('Service mappings manager required') if mapping_manager.nil?
|
8
|
+
@definition_manager = definition_manager
|
9
|
+
@mapping_manager = mapping_manager
|
10
|
+
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)
|
17
|
+
service_names = @mapping_manager.list_services(location)
|
18
|
+
service_names.collect { |name| @definition_manager.services[name] }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../models/app_fields'
|
2
|
+
require_relative '../models/service_definition'
|
3
|
+
|
4
|
+
# Manager which loads and provides access to location to service mappings
|
5
|
+
module Longleaf
|
6
|
+
class ServiceMappingManager
|
7
|
+
AF ||= Longleaf::AppFields
|
8
|
+
|
9
|
+
def initialize(config)
|
10
|
+
raise ArgumentError.new("Configuration must be provided") if config.nil? || config.empty?
|
11
|
+
|
12
|
+
mappings_config = config[AF::SERVICE_MAPPINGS]
|
13
|
+
raise ArgumentError.new("Service mappings configuration must be provided") if mappings_config.nil?
|
14
|
+
|
15
|
+
@loc_to_services = Hash.new
|
16
|
+
|
17
|
+
mappings_config.each do |mapping|
|
18
|
+
locations = mapping[AF::LOCATIONS]
|
19
|
+
services = mapping[AF::SERVICES]
|
20
|
+
|
21
|
+
locations = [locations] if locations.is_a?(String)
|
22
|
+
services = [services] if services.is_a?(String)
|
23
|
+
|
24
|
+
locations.each do |loc_name|
|
25
|
+
@loc_to_services[loc_name] = Array.new unless @loc_to_services.key?(loc_name)
|
26
|
+
|
27
|
+
service_set = @loc_to_services[loc_name]
|
28
|
+
if services.is_a?(String)
|
29
|
+
service_set.push(services)
|
30
|
+
else
|
31
|
+
service_set.concat(services)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@loc_to_services.each { |loc, services| services.uniq! }
|
37
|
+
@loc_to_services.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
# Gets a list of service names associated with the given location
|
41
|
+
# @param loc_name [String] name of the location to lookup
|
42
|
+
# @return [Array] a list of service names associated with the location
|
43
|
+
def list_services(loc_name)
|
44
|
+
@loc_to_services[loc_name] || []
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require_relative '../models/service_fields'
|
3
|
+
require_relative '../models/app_fields'
|
4
|
+
require_relative '../errors'
|
5
|
+
require_relative 'configuration_validator'
|
6
|
+
|
7
|
+
# Validates application configuration of service to location mappings
|
8
|
+
module Longleaf
|
9
|
+
class ServiceMappingValidator < ConfigurationValidator
|
10
|
+
AF ||= Longleaf::AppFields
|
11
|
+
|
12
|
+
# Validates service mapping configuration to ensure that it is syntactically and referentially correct.
|
13
|
+
# @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]
|
19
|
+
return if mappings.nil? || mappings.empty?
|
20
|
+
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
|
+
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
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def self.validate_mapping_field(field, mapping, valid_values)
|
36
|
+
assert("Mapping must contain a '#{field}' field", mapping.key?(field))
|
37
|
+
field_values = mapping[field]
|
38
|
+
assert("Mapping '#{field}' field must be either a string or an array, but received '#{field_values.inspect}' instead",
|
39
|
+
field_values.is_a?(Array) || field_values.is_a?(String))
|
40
|
+
assert("Mapping must specify one or more value in the '#{field}' field", !field_values.empty?)
|
41
|
+
|
42
|
+
check_values = field_values.is_a?(String) ? [field_values] : field_values
|
43
|
+
check_values.each do |value|
|
44
|
+
assert("Mapping '#{field}' specifies value '#{value}', but no #{field} with that name exist",
|
45
|
+
valid_values.include?(value))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative '../models/app_fields'
|
2
|
+
require_relative '../models/storage_location'
|
3
|
+
|
4
|
+
# Manager which loads and provides access to Longleaf::StorageLocation objects
|
5
|
+
module Longleaf
|
6
|
+
class StorageLocationManager
|
7
|
+
AF ||= Longleaf::AppFields
|
8
|
+
|
9
|
+
attr_reader :locations
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
raise ArgumentError.new("Configuration must be provided") if config&.empty?
|
13
|
+
|
14
|
+
@locations = Hash.new
|
15
|
+
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
|
21
|
+
end
|
22
|
+
@locations.freeze
|
23
|
+
end
|
24
|
+
|
25
|
+
# Get the StorageLocation object which should contain the given path
|
26
|
+
# @return [Longleaf::StorageLocation] location containing the given path
|
27
|
+
# or nil if the path is not contained by a registered location.
|
28
|
+
def get_location_by_path(path)
|
29
|
+
raise ArgumentError.new("Path parameter is required") if path.nil? || path.empty?
|
30
|
+
@locations.each do |name, location|
|
31
|
+
return location if path.start_with?(location.path)
|
32
|
+
end
|
33
|
+
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|