dop_common 0.13.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 +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +176 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +177 -0
- data/README.md +48 -0
- data/Rakefile +49 -0
- data/Vagrantfile +25 -0
- data/bin/dop-puppet-autosign +56 -0
- data/doc/examples/example_deploment_plan_v0.0.1.yaml +302 -0
- data/doc/plan_format_v0.0.1.md +919 -0
- data/doc/plan_format_v0.0.2_snippets.md +56 -0
- data/dop_common.gemspec +44 -0
- data/lib/dop_common/affinity_group.rb +57 -0
- data/lib/dop_common/cli/global_options.rb +37 -0
- data/lib/dop_common/cli/log.rb +51 -0
- data/lib/dop_common/cli/node_selection.rb +62 -0
- data/lib/dop_common/command.rb +125 -0
- data/lib/dop_common/config/helper.rb +39 -0
- data/lib/dop_common/config.rb +66 -0
- data/lib/dop_common/configuration.rb +37 -0
- data/lib/dop_common/credential.rb +152 -0
- data/lib/dop_common/data_disk.rb +62 -0
- data/lib/dop_common/dns.rb +55 -0
- data/lib/dop_common/hash_parser.rb +241 -0
- data/lib/dop_common/hooks.rb +81 -0
- data/lib/dop_common/infrastructure.rb +160 -0
- data/lib/dop_common/infrastructure_properties.rb +185 -0
- data/lib/dop_common/interface.rb +113 -0
- data/lib/dop_common/log.rb +78 -0
- data/lib/dop_common/network.rb +85 -0
- data/lib/dop_common/node/config.rb +159 -0
- data/lib/dop_common/node.rb +442 -0
- data/lib/dop_common/node_filter.rb +74 -0
- data/lib/dop_common/plan.rb +188 -0
- data/lib/dop_common/plan_cache.rb +83 -0
- data/lib/dop_common/plan_store.rb +263 -0
- data/lib/dop_common/pre_processor.rb +73 -0
- data/lib/dop_common/run_options.rb +56 -0
- data/lib/dop_common/signal_handler.rb +58 -0
- data/lib/dop_common/state_store.rb +95 -0
- data/lib/dop_common/step.rb +200 -0
- data/lib/dop_common/step_set.rb +41 -0
- data/lib/dop_common/thread_context_logger.rb +77 -0
- data/lib/dop_common/utils.rb +106 -0
- data/lib/dop_common/validator.rb +53 -0
- data/lib/dop_common/version.rb +3 -0
- data/lib/dop_common.rb +32 -0
- data/lib/hiera/backend/dop_backend.rb +94 -0
- data/lib/hiera/dop_logger.rb +20 -0
- data/spec/data/fake_hook_file_invalid +1 -0
- data/spec/data/fake_hook_file_valid +5 -0
- data/spec/data/fake_keyfile +1 -0
- data/spec/dop-puppet-autosign_spec_disable.rb +33 -0
- data/spec/dop_common/affinity_group_spec.rb +41 -0
- data/spec/dop_common/command_spec.rb +83 -0
- data/spec/dop_common/credential_spec.rb +73 -0
- data/spec/dop_common/data_disk_spec.rb +165 -0
- data/spec/dop_common/dns_spec.rb +33 -0
- data/spec/dop_common/hash_parser_spec.rb +181 -0
- data/spec/dop_common/hooks_spec.rb +33 -0
- data/spec/dop_common/infrastructure_properties_spec.rb +224 -0
- data/spec/dop_common/infrastructure_spec.rb +77 -0
- data/spec/dop_common/interface_spec.rb +192 -0
- data/spec/dop_common/network_spec.rb +92 -0
- data/spec/dop_common/node_filter_spec.rb +70 -0
- data/spec/dop_common/node_spec.rb +623 -0
- data/spec/dop_common/plan_cache_spec.rb +46 -0
- data/spec/dop_common/plan_spec.rb +136 -0
- data/spec/dop_common/plan_store_spec.rb +194 -0
- data/spec/dop_common/pre_processor_spec.rb +27 -0
- data/spec/dop_common/run_options_spec.rb +65 -0
- data/spec/dop_common/signal_handler_spec.rb +31 -0
- data/spec/dop_common/step_set_spec.rb +21 -0
- data/spec/dop_common/step_spec.rb +175 -0
- data/spec/dop_common/utils_spec.rb +27 -0
- data/spec/dop_common/validator_spec.rb +47 -0
- data/spec/example_plans_spec.rb +16 -0
- data/spec/fixtures/example_ssh_key +27 -0
- data/spec/fixtures/example_ssh_key.pub +1 -0
- data/spec/fixtures/incl/root_part.yaml +1 -0
- data/spec/fixtures/incl/some_list.yaml +2 -0
- data/spec/fixtures/other_plan_same_nodes.yaml +19 -0
- data/spec/fixtures/simple_include.yaml +6 -0
- data/spec/fixtures/simple_include_with_errors.yaml +4 -0
- data/spec/fixtures/simple_plan.yaml +19 -0
- data/spec/fixtures/simple_plan_invalid.yaml +18 -0
- data/spec/fixtures/simple_plan_modified.yaml +21 -0
- data/spec/spec_helper.rb +106 -0
- metadata +381 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
#
|
2
|
+
# Node configuration parts for hiera variable lookups and facts
|
3
|
+
#
|
4
|
+
|
5
|
+
module DopCommon
|
6
|
+
class Node
|
7
|
+
module Config
|
8
|
+
@@mutex_hiera = Mutex.new
|
9
|
+
@@mutex_lookup = Mutex.new
|
10
|
+
@@hiera = nil
|
11
|
+
@@hiera_config = nil
|
12
|
+
|
13
|
+
def has_name?(pattern)
|
14
|
+
pattern_match?(name, pattern)
|
15
|
+
end
|
16
|
+
|
17
|
+
def config(variable)
|
18
|
+
resolve_external(variable) || resolve_internal(variable)
|
19
|
+
end
|
20
|
+
|
21
|
+
def has_config?(variable, pattern)
|
22
|
+
pattern_match?(config(variable), pattern)
|
23
|
+
end
|
24
|
+
|
25
|
+
def config_includes?(variable, pattern)
|
26
|
+
[config(variable)].flatten.any?{|v| pattern_match?(v, pattern)}
|
27
|
+
end
|
28
|
+
|
29
|
+
def fact(variable)
|
30
|
+
scope[ensure_global_namespace(variable)]
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_fact?(variable, pattern)
|
34
|
+
pattern_match?(fact(variable), pattern)
|
35
|
+
end
|
36
|
+
|
37
|
+
def role
|
38
|
+
config(DopCommon.config.role_variable) || role_default
|
39
|
+
end
|
40
|
+
|
41
|
+
def has_role?(pattern)
|
42
|
+
pattern_match?(role, pattern)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def pattern_match?(value, pattern)
|
48
|
+
case pattern
|
49
|
+
when Regexp then value =~ pattern
|
50
|
+
else value == pattern
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def basic_scope
|
55
|
+
@basic_scope ||= {
|
56
|
+
'::fqdn' => fqdn,
|
57
|
+
'::clientcert' => fqdn,
|
58
|
+
'::hostname' => hostname,
|
59
|
+
'::domain' => domainname
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def facts
|
64
|
+
return {} unless DopCommon.config.load_facts
|
65
|
+
facts_yaml = File.join(DopCommon.config.facts_dir, fqdn + '.yaml')
|
66
|
+
if File.exists? facts_yaml
|
67
|
+
YAML.load_file(facts_yaml).values
|
68
|
+
else
|
69
|
+
DopCommon.log.warn("No facts found for node #{name} at #{facts_yaml}")
|
70
|
+
{}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def ensure_global_namespace(fact)
|
75
|
+
fact =~ /^::/ ? fact : '::' + fact
|
76
|
+
end
|
77
|
+
|
78
|
+
def scope
|
79
|
+
merged_scope = basic_scope.merge(facts)
|
80
|
+
Hash[merged_scope.map {|fact,value| [ensure_global_namespace(fact), value ]}]
|
81
|
+
end
|
82
|
+
|
83
|
+
def hiera
|
84
|
+
@@mutex_hiera.synchronize do
|
85
|
+
# Create a new Hiera object if the config has changed
|
86
|
+
unless DopCommon.config.hiera_yaml == @@hiera_config
|
87
|
+
DopCommon.log.debug("Hiera config location changed from #{@@hiera_config.to_s} to #{DopCommon.config.hiera_yaml.to_s}")
|
88
|
+
@@hiera_config = DopCommon.config.hiera_yaml
|
89
|
+
config = {}
|
90
|
+
if File.exists?(@@hiera_config)
|
91
|
+
config = YAML.load_file(@@hiera_config)
|
92
|
+
else
|
93
|
+
DopCommon.log.error("Hiera config #{@@hiera_config} not found! Using empty config")
|
94
|
+
end
|
95
|
+
# set the plan_store defaults
|
96
|
+
config[:dop] ||= { }
|
97
|
+
unless config[:dop].has_key?(:plan_store_dir)
|
98
|
+
config[:dop][:plan_store_dir] = DopCommon.config.plan_store_dir
|
99
|
+
end
|
100
|
+
config[:logger] = 'dop'
|
101
|
+
@@hiera = Hiera.new(:config => config)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
@@hiera
|
105
|
+
end
|
106
|
+
|
107
|
+
def role_default
|
108
|
+
if DopCommon.config.role_default
|
109
|
+
DopCommon.config.role_default
|
110
|
+
else
|
111
|
+
DopCommon.log.warn("No role found for #{name} and no default role defined.")
|
112
|
+
'-'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# This will try to resolve the config variable from the plan configuration hash.
|
117
|
+
# This is needed in case the plan is not yet added to the plan cache
|
118
|
+
# (in case of validation) and hiera can't resolve it over the plugin,
|
119
|
+
# but we still need the information about the node config.
|
120
|
+
def resolve_internal(variable)
|
121
|
+
return nil unless DopCommon.config.use_hiera
|
122
|
+
@@mutex_lookup.synchronize do
|
123
|
+
begin
|
124
|
+
hiera # make sure hiera is initialized
|
125
|
+
answer = nil
|
126
|
+
Hiera::Backend.datasources(scope) do |source|
|
127
|
+
DopCommon.log.debug("Hiera internal: Looking for data source #{source}")
|
128
|
+
data = nil
|
129
|
+
begin
|
130
|
+
data = @parsed_configuration.lookup(source, variable, scope)
|
131
|
+
rescue DopCommon::ConfigurationValueNotFound
|
132
|
+
next
|
133
|
+
else
|
134
|
+
break if answer = Hiera::Backend.parse_answer(data, scope)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
rescue StandardError => e
|
138
|
+
DopCommon.log.debug(e.message)
|
139
|
+
end
|
140
|
+
DopCommon.log.debug("Hiera internal: answer for variable #{variable} : #{answer}")
|
141
|
+
return answer
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# this will try to resolve the variable over hiera directly
|
146
|
+
def resolve_external(variable)
|
147
|
+
return nil unless DopCommon.config.use_hiera
|
148
|
+
@@mutex_lookup.synchronize do
|
149
|
+
begin hiera.lookup(variable, nil, scope)
|
150
|
+
rescue Psych::SyntaxError => e
|
151
|
+
DopCommon.log.error("YAML parsing error in hiera data. Make sure you hiera yaml files are valid")
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,442 @@
|
|
1
|
+
#
|
2
|
+
# DOP common node hash parser
|
3
|
+
#
|
4
|
+
require 'dop_common/node/config'
|
5
|
+
|
6
|
+
module DopCommon
|
7
|
+
class Node
|
8
|
+
include Validator
|
9
|
+
include HashParser
|
10
|
+
include Utils
|
11
|
+
include Node::Config
|
12
|
+
|
13
|
+
attr_reader :name
|
14
|
+
alias_method :nodename, :name
|
15
|
+
|
16
|
+
DEFAULT_DIGITS = 2
|
17
|
+
|
18
|
+
VALID_FLAVOR_TYPES = {
|
19
|
+
:tiny => {
|
20
|
+
:cores => 1,
|
21
|
+
:memory => 536870912,
|
22
|
+
:storage => 1073741824
|
23
|
+
},
|
24
|
+
:small => {
|
25
|
+
:cores => 1,
|
26
|
+
:memory => 2147483648,
|
27
|
+
:storage => 10737418240
|
28
|
+
},
|
29
|
+
:medium => {
|
30
|
+
:cores => 2,
|
31
|
+
:memory => 4294967296,
|
32
|
+
:storage => 10737418240
|
33
|
+
},
|
34
|
+
:large => {
|
35
|
+
:cores => 4,
|
36
|
+
:memory => 8589934592,
|
37
|
+
:storage => 10737418240
|
38
|
+
},
|
39
|
+
:xlarge => {
|
40
|
+
:cores => 8,
|
41
|
+
:memory => 17179869184,
|
42
|
+
:storage => 10737418240
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
DEFAULT_OPENSTACK_FLAVOR = 'm1.medium'
|
47
|
+
|
48
|
+
DEFAULT_CORES = VALID_FLAVOR_TYPES[:medium][:cores]
|
49
|
+
DEFAULT_MEMORY = VALID_FLAVOR_TYPES[:medium][:memory]
|
50
|
+
DEFAULT_STORAGE = VALID_FLAVOR_TYPES[:medium][:storage]
|
51
|
+
|
52
|
+
def initialize(name, hash, parent={})
|
53
|
+
@name = name
|
54
|
+
@hash = symbolize_keys(hash)
|
55
|
+
@parsed_infrastructures = parent[:parsed_infrastructures]
|
56
|
+
@parsed_credentials = parent[:parsed_credentials]
|
57
|
+
@parsed_hooks = parent[:parsed_hooks]
|
58
|
+
@parsed_configuration = parent[:parsed_configuration]
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate
|
62
|
+
log_validation_method('digits_valid?')
|
63
|
+
log_validation_method('range_valid?')
|
64
|
+
log_validation_method('fqdn_valid?')
|
65
|
+
log_validation_method('infrastructure_valid?')
|
66
|
+
log_validation_method('infrastructure_properties_valid?')
|
67
|
+
log_validation_method('image_valid?')
|
68
|
+
log_validation_method('full_clone_valid?')
|
69
|
+
log_validation_method('interfaces_valid?')
|
70
|
+
log_validation_method('flavor_valid?')
|
71
|
+
log_validation_method('cores_valid?')
|
72
|
+
log_validation_method('memory_valid?')
|
73
|
+
log_validation_method('storage_valid?')
|
74
|
+
log_validation_method('timezone_valid?')
|
75
|
+
log_validation_method('product_id_valid?')
|
76
|
+
log_validation_method('organization_name_valid?')
|
77
|
+
log_validation_method('credentials_valid?')
|
78
|
+
log_validation_method('dns_valid?')
|
79
|
+
log_validation_method('data_disks_valid?')
|
80
|
+
try_validate_obj("Node #{@name}: Can't validate the interfaces part because of a previous error"){interfaces}
|
81
|
+
try_validate_obj("Node #{@name}: Can't validate the infrastructure_properties part because of a previous error"){infrastructure_properties}
|
82
|
+
try_validate_obj("Node #{@name}: Can't validate the dns part because of a previous error"){dns}
|
83
|
+
try_validate_obj("Node #{@name}: Can't validate data_disks part because of a previous error"){data_disks}
|
84
|
+
# Memory and storage may be to nil.
|
85
|
+
try_validate_obj("Node #{@name}: Can't validate the memory part because of a previous error"){memory} unless @hash[:memory].nil?
|
86
|
+
try_validate_obj("Node #{@name}: Can't validate storage part because of a previous error"){storage} unless @hash[:storage].nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
def digits
|
90
|
+
@digits ||= digits_valid? ?
|
91
|
+
@hash[:digits] : DEFAULT_DIGITS
|
92
|
+
end
|
93
|
+
|
94
|
+
def range
|
95
|
+
@range ||= range_valid? ?
|
96
|
+
Range.new(*@hash[:range].scan(/\d+/)) : nil
|
97
|
+
end
|
98
|
+
|
99
|
+
# Check if the node describes a series of nodes.
|
100
|
+
def inflatable?
|
101
|
+
@name.include?('{i}')
|
102
|
+
end
|
103
|
+
|
104
|
+
# Create and return all the nodes in the series
|
105
|
+
def inflate
|
106
|
+
range.map do |node_number|
|
107
|
+
@node_copy = clone
|
108
|
+
@node_copy.name = @name.gsub('{i}', "%0#{digits}d" % node_number)
|
109
|
+
@node_copy
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def fqdn
|
114
|
+
@fqdn ||= fqdn_valid? ? create_fqdn : nil
|
115
|
+
end
|
116
|
+
|
117
|
+
def hostname
|
118
|
+
@hostname ||= fqdn.split('.').first
|
119
|
+
end
|
120
|
+
|
121
|
+
def domainname
|
122
|
+
@domainname ||= fqdn.split('.', 2).last
|
123
|
+
end
|
124
|
+
|
125
|
+
def infrastructure
|
126
|
+
@infrastructure ||= infrastructure_valid? ? create_infrastructure : nil
|
127
|
+
end
|
128
|
+
|
129
|
+
def infrastructure_properties
|
130
|
+
@infrastructure_properties ||= infrastructure_properties_valid? ?
|
131
|
+
create_infrastructure_properties : {}
|
132
|
+
end
|
133
|
+
|
134
|
+
def image
|
135
|
+
@image ||= image_valid? ? @hash[:image] : nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def full_clone?
|
139
|
+
@full_clone ||= full_clone_valid? ? @hash[:full_clone] : true
|
140
|
+
end
|
141
|
+
alias_method :full_clone, :full_clone?
|
142
|
+
|
143
|
+
def interfaces
|
144
|
+
@interfaces ||= interfaces_valid? ? create_interfaces : []
|
145
|
+
end
|
146
|
+
|
147
|
+
def flavor
|
148
|
+
@flavor ||= flavor_valid? ? create_flavor : nil
|
149
|
+
end
|
150
|
+
|
151
|
+
def cores
|
152
|
+
@cores ||= cores_valid? ? create_cores : nil
|
153
|
+
end
|
154
|
+
|
155
|
+
def memory
|
156
|
+
@memory ||= memory_valid? ? create_memory : nil
|
157
|
+
end
|
158
|
+
|
159
|
+
def storage
|
160
|
+
@storage ||= storage_valid? ? create_storage : nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def timezone
|
164
|
+
@timezone ||= timezone_valid? ? @hash[:timezone] : nil
|
165
|
+
end
|
166
|
+
|
167
|
+
def product_id
|
168
|
+
@product_id ||= product_id_valid? ? @hash[:product_id] : nil
|
169
|
+
end
|
170
|
+
|
171
|
+
def organization_name
|
172
|
+
@organization_name ||= organization_name_valid? ? @hash[:organization_name] : nil
|
173
|
+
end
|
174
|
+
|
175
|
+
def credentials
|
176
|
+
@credentials ||= credentials_valid? ? create_credentials : []
|
177
|
+
end
|
178
|
+
|
179
|
+
def dns
|
180
|
+
@dns ||= dns_valid? ? create_dns : nil
|
181
|
+
end
|
182
|
+
|
183
|
+
def data_disks
|
184
|
+
@data_disks ||= data_disks_valid? ? create_data_disks : []
|
185
|
+
end
|
186
|
+
|
187
|
+
def hooks
|
188
|
+
@parsed_hooks
|
189
|
+
end
|
190
|
+
|
191
|
+
protected
|
192
|
+
|
193
|
+
attr_writer :name
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def digits_valid?
|
198
|
+
return false unless inflatable?
|
199
|
+
return false if @hash[:digits].nil? # digits is optional
|
200
|
+
@hash[:digits].kind_of?(Fixnum) or
|
201
|
+
raise PlanParsingError, "Node #{@name}: 'digits' has to be a number"
|
202
|
+
@hash[:digits] > 0 or
|
203
|
+
raise PlanParsingError, "Node #{@name}: 'digits' has to be greater than zero"
|
204
|
+
end
|
205
|
+
|
206
|
+
def range_valid?
|
207
|
+
if inflatable?
|
208
|
+
@hash[:range] or
|
209
|
+
raise PlanParsingError, "Node #{@name}: 'range' has to be specified if the node is inflatable"
|
210
|
+
else
|
211
|
+
return false # range is only needed if inflatable
|
212
|
+
end
|
213
|
+
@hash[:range].class == String or
|
214
|
+
raise PlanParsingError, "Node #{@name}: 'range' has to be a string"
|
215
|
+
range_array = @hash[:range].scan(/\d+/)
|
216
|
+
range_array and range_array.length == 2 or
|
217
|
+
raise PlanParsingError, "Node #{@name}: 'range' has to be a string which contains exactly two numbers"
|
218
|
+
range_array[0] < range_array[1] or
|
219
|
+
raise PlanParsingError, "Node #{@name}: the first number has to be smaller than the second in 'range'"
|
220
|
+
end
|
221
|
+
|
222
|
+
def fqdn_valid?
|
223
|
+
nodename = @hash[:fqdn] || @name # FQDN is implicitly derived from a node name
|
224
|
+
raise PlanParsingError, "Node #{@name}: FQDN must be a string" unless nodename.kind_of?(String)
|
225
|
+
raise PlanParsingError, "Node #{@name}: FQDN must not exceed 255 characters" if nodename.size > 255
|
226
|
+
# f.q.dn. is a valid FQDN
|
227
|
+
nodename = nodename[0...-1] if nodename[-1] == '.'
|
228
|
+
raise PlanParsingError, "Node #{@name}: FQDN has invalid format" unless
|
229
|
+
nodename.split('.').collect do |tok|
|
230
|
+
!tok.empty? && tok.size <= 63 && tok[0] != '-' && tok[-1] != '-' && !tok.scan(/[^a-z\d-]/i).any?
|
231
|
+
end.all?
|
232
|
+
true
|
233
|
+
end
|
234
|
+
|
235
|
+
def infrastructure_valid?
|
236
|
+
@hash[:infrastructure].kind_of?(String) or
|
237
|
+
raise PlanParsingError, "Node #{@name}: The 'infrastructure' pointer must be a string"
|
238
|
+
@parsed_infrastructures.find { |i| i.name == @hash[:infrastructure] } or
|
239
|
+
raise PlanParsingError, "Node #{@name}: No such infrastructure"
|
240
|
+
end
|
241
|
+
|
242
|
+
def infrastructure_properties_valid?
|
243
|
+
return false unless @hash.has_key?(:infrastructure_properties)
|
244
|
+
raise PlanParsingError, "Node #{@name}: The 'infrastructure_properties' must be a hash" unless
|
245
|
+
@hash[:infrastructure_properties].kind_of?(Hash)
|
246
|
+
true
|
247
|
+
end
|
248
|
+
|
249
|
+
def image_valid?
|
250
|
+
return false if infrastructure.provides?(:baremetal) && @hash[:image].nil?
|
251
|
+
raise PlanParsingError, "Node #{@name}: The 'image' must be a string" unless @hash[:image].kind_of?(String)
|
252
|
+
true
|
253
|
+
end
|
254
|
+
|
255
|
+
def full_clone_valid?
|
256
|
+
return false if @hash[:full_clone].nil?
|
257
|
+
raise PlanParsingError, "Node #{@node}: The 'full_clone' can be used only for OVirt/RHEVm providers" unless
|
258
|
+
infrastructure.provides?(:ovirt)
|
259
|
+
raise PlanParsingError, "Node #{@node}: The 'full_clone', if defined, must be true or false" unless
|
260
|
+
@hash.has_key?(:full_clone) && (@hash[:full_clone].kind_of?(TrueClass) || @hash[:full_clone].kind_of?(FalseClass))
|
261
|
+
true
|
262
|
+
end
|
263
|
+
|
264
|
+
def interfaces_valid?
|
265
|
+
return false if @hash[:interfaces].nil?
|
266
|
+
@hash[:interfaces].kind_of?(Hash) or
|
267
|
+
raise PlanParsingError, "Node #{@name}: The value of 'interfaces' has to be a hash"
|
268
|
+
@hash[:interfaces].keys.all?{|i| i.kind_of?(String)} or
|
269
|
+
raise PlanParsingError, "Node #{@name}: The keys in the 'interface' hash have to be strings"
|
270
|
+
@hash[:interfaces].values.all?{|v| v.kind_of?(Hash)} or
|
271
|
+
raise PlanParsingError, "Node #{@name}: The values in the 'interface' hash have to be hashes"
|
272
|
+
true
|
273
|
+
end
|
274
|
+
|
275
|
+
def flavor_valid?
|
276
|
+
raise PlanParsingError, "Node #{@name}: flavor is mutually exclusive with any of cores, memory and storage" if
|
277
|
+
@hash.has_key?(:flavor) && @hash.keys.any? { |k| [:cores, :memory, :storage].include?(k) }
|
278
|
+
raise PlanParsingError, "Node #{@name}: flavor must be a string" if
|
279
|
+
@hash.has_key?(:flavor) && !@hash[:flavor].kind_of?(String)
|
280
|
+
return true if infrastructure.provides?(:openstack)
|
281
|
+
raise PlanParsingError, "Node #{@name}: Invalid flavor '#{@hash[:flavor]}'" if
|
282
|
+
!@hash[:flavor].nil? && !VALID_FLAVOR_TYPES.has_key?(@hash[:flavor].to_sym)
|
283
|
+
false
|
284
|
+
end
|
285
|
+
|
286
|
+
def cores_valid?
|
287
|
+
if infrastructure.provides?(:openstack)
|
288
|
+
raise PlanParsingError, "Node #{@name}: cores can't be specified if openstack is a provider" if
|
289
|
+
@hash.has_key?(:cores)
|
290
|
+
return false
|
291
|
+
end
|
292
|
+
raise PlanParsingError, "Node #{@name}: cores must be a non-zero positive number" if
|
293
|
+
@hash.has_key?(:cores) && !(@hash[:cores].kind_of?(Fixnum) && @hash[:cores] > 0)
|
294
|
+
true
|
295
|
+
end
|
296
|
+
|
297
|
+
def memory_valid?
|
298
|
+
if infrastructure.provides?(:openstack)
|
299
|
+
raise PlanParsingError, "Node #{@name}: memory can't be specified if openstack is a provider" if
|
300
|
+
@hash.has_key?(:memory)
|
301
|
+
return false
|
302
|
+
end
|
303
|
+
true
|
304
|
+
end
|
305
|
+
|
306
|
+
def storage_valid?
|
307
|
+
if infrastructure.provides?(:openstack)
|
308
|
+
raise PlanParsingError, "Node #{@name}: storage can't be specified if openstack is a provider" if
|
309
|
+
@hash.has_key?(:storage)
|
310
|
+
return false
|
311
|
+
end
|
312
|
+
true
|
313
|
+
end
|
314
|
+
|
315
|
+
# TODO: Do a better format validation
|
316
|
+
def timezone_valid?
|
317
|
+
raise PlanParsingError, "Node #{name}: 'timezone' is a required for VSphere-based node" if
|
318
|
+
infrastructure.provides?(:vsphere) && @hash[:timezone].nil?
|
319
|
+
return false if @hash[:timezone].nil?
|
320
|
+
raise PlanParsingError, "Node #{name}: 'timezone', if specified, must be a non-empty string" if
|
321
|
+
!@hash[:timezone].kind_of?(String) || @hash[:timezone].empty?
|
322
|
+
true
|
323
|
+
end
|
324
|
+
|
325
|
+
def product_id_valid?
|
326
|
+
return false if @hash[:product_id].nil?
|
327
|
+
raise PlanParsingError, "Node #{name}: 'product_id' must be a string" unless
|
328
|
+
@hash[:product_id].kind_of?(String)
|
329
|
+
true
|
330
|
+
end
|
331
|
+
|
332
|
+
def organization_name_valid?
|
333
|
+
return false if @hash[:organization_name].nil?
|
334
|
+
raise PlanParsingError, "Node #{name}: 'organization_name' must be a non-empty string" if
|
335
|
+
!@hash[:organization_name].kind_of?(String) || @hash[:organization_name].empty?
|
336
|
+
true
|
337
|
+
end
|
338
|
+
|
339
|
+
def credentials_valid?
|
340
|
+
return false if @hash[:credentials].nil?
|
341
|
+
[String, Symbol, Array].include?(@hash[:credentials].class) or
|
342
|
+
raise PlanParsingError, "Node #{name}: 'credentials' has to be a string, symbol or array"
|
343
|
+
[@hash[:credentials]].flatten.each do |credential|
|
344
|
+
[String, Symbol].include?(credential.class) or
|
345
|
+
raise PlanParsingError, "Node #{name}: the 'credentials' array should only contain strings, symbols"
|
346
|
+
@parsed_credentials.keys.include?(credential) or
|
347
|
+
raise PlanParsingError, "Node #{name}: the credential #{credential.to_s} in 'credentials' does not exist"
|
348
|
+
real_credential = @parsed_credentials[credential]
|
349
|
+
case real_credential.type
|
350
|
+
when :ssh_key
|
351
|
+
real_credential.public_key or
|
352
|
+
raise PlanParsingError, "Node #{name}: the ssh_key credential #{credential.to_s} in 'credentials' requires a public key"
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def dns_valid?
|
358
|
+
raise PlanParsingError, "Node #{@name}: The 'dns', if specified, must be a hash" if
|
359
|
+
@hash.has_key?(:dns) && !@hash[:dns].kind_of?(Hash)
|
360
|
+
true
|
361
|
+
end
|
362
|
+
|
363
|
+
def data_disks_valid?
|
364
|
+
return false unless @hash.has_key?(:disks)
|
365
|
+
raise PlanParsingError, "Node #{@name}: The 'disks', if specified, must be a hash" unless
|
366
|
+
@hash[:disks].kind_of?(Hash)
|
367
|
+
raise PlanParsingError, "Node #{@name}: Each value of 'disks' must be a hash" unless
|
368
|
+
@hash[:disks].values.all? { |d| d.kind_of?(Hash) }
|
369
|
+
true
|
370
|
+
end
|
371
|
+
|
372
|
+
def create_fqdn
|
373
|
+
node_name = (@hash[:fqdn] || @name)
|
374
|
+
node_name[-1] == '.'[0] ? node_name[0...-1] : node_name
|
375
|
+
end
|
376
|
+
|
377
|
+
def create_interfaces
|
378
|
+
@hash[:interfaces].map do |interface_name, interface_hash|
|
379
|
+
DopCommon::Interface.new(
|
380
|
+
interface_name,
|
381
|
+
interface_hash,
|
382
|
+
:parsed_networks => infrastructure.networks
|
383
|
+
)
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def create_infrastructure
|
388
|
+
@parsed_infrastructures.find { |i| i.name == @hash[:infrastructure] }
|
389
|
+
end
|
390
|
+
|
391
|
+
def create_infrastructure_properties
|
392
|
+
DopCommon::InfrastructureProperties.new(
|
393
|
+
@hash[:infrastructure_properties],
|
394
|
+
infrastructure
|
395
|
+
)
|
396
|
+
end
|
397
|
+
|
398
|
+
def create_flavor
|
399
|
+
@hash[:flavor].nil? ? DEFAULT_OPENSTACK_FLAVOR : @hash[:flavor]
|
400
|
+
end
|
401
|
+
|
402
|
+
def create_cores
|
403
|
+
@hash.has_key?(:cores) ? @hash[:cores] : @hash.has_key?(:flavor) ?
|
404
|
+
VALID_FLAVOR_TYPES[@hash[:flavor].to_sym][:cores] : DEFAULT_CORES
|
405
|
+
end
|
406
|
+
|
407
|
+
def create_memory
|
408
|
+
DopCommon::Utils::DataSize.new(
|
409
|
+
@hash.has_key?(:memory) ? @hash[:memory] : @hash.has_key?(:flavor) ?
|
410
|
+
VALID_FLAVOR_TYPES[@hash[:flavor].to_sym][:memory] : DEFAULT_MEMORY
|
411
|
+
)
|
412
|
+
end
|
413
|
+
|
414
|
+
def create_storage
|
415
|
+
DopCommon::Utils::DataSize.new(
|
416
|
+
@hash.has_key?(:storage) ? @hash[:storage] : @hash.has_key?(:flavor) ?
|
417
|
+
VALID_FLAVOR_TYPES[@hash[:flavor].to_sym][:storage] : DEFAULT_STORAGE
|
418
|
+
)
|
419
|
+
end
|
420
|
+
|
421
|
+
def create_credentials
|
422
|
+
[@hash[:credentials]].flatten.map do |credential|
|
423
|
+
@parsed_credentials[credential]
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def create_dns
|
428
|
+
DopCommon::DNS.new(@hash[:dns])
|
429
|
+
end
|
430
|
+
|
431
|
+
def create_data_disks
|
432
|
+
@hash[:disks].map do |disk_name, disk_hash|
|
433
|
+
DopCommon::DataDisk.new(
|
434
|
+
disk_name,
|
435
|
+
disk_hash,
|
436
|
+
:parsed_infrastructure => infrastructure,
|
437
|
+
:parsed_infrastructure_properties => infrastructure_properties
|
438
|
+
)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#
|
2
|
+
# This module provides a method to filter list of nodes.
|
3
|
+
#
|
4
|
+
module DopCommon
|
5
|
+
module NodeFilter
|
6
|
+
|
7
|
+
# filter a list of nodes
|
8
|
+
def filter_nodes(nodes, filters)
|
9
|
+
return nodes if filters == :all
|
10
|
+
include_list = []
|
11
|
+
exclude_list = []
|
12
|
+
|
13
|
+
[:nodes, :roles, :nodes_by_config].each do |filter_type|
|
14
|
+
pattern_variable_pairs(filters, filter_type) do |pattern, variable|
|
15
|
+
include_list += create_node_list(nodes, filter_type, pattern, variable)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
[:exclude_nodes, :exclude_roles, :exclude_nodes_by_config].each do |filter_type|
|
20
|
+
pattern_variable_pairs(filters, filter_type) do |pattern, variable|
|
21
|
+
exclude_list += create_node_list(nodes, filter_type, pattern, variable)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
(include_list - exclude_list).uniq
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def pattern_variable_pairs(filters, filter_type)
|
31
|
+
if filters.respond_to?(filter_type)
|
32
|
+
filter = filters.send(filter_type)
|
33
|
+
normalize_patterns(filter).each do |variable, patterns|
|
34
|
+
[patterns].flatten.collect do |pattern|
|
35
|
+
yield(pattern, variable)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# returns a variable and patterns Array for a filter
|
42
|
+
def normalize_patterns(filter)
|
43
|
+
case filter
|
44
|
+
when String, Symbol, Array then [[nil, filter]]
|
45
|
+
when Hash then filter.to_a
|
46
|
+
else []
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_node_list(nodes, filter_type, pattern, variable = nil)
|
51
|
+
case pattern
|
52
|
+
when :all then nodes
|
53
|
+
else
|
54
|
+
nodes_list = nodes.select do |node|
|
55
|
+
case filter_type
|
56
|
+
when :nodes, :exclude_nodes then node.has_name?(pattern)
|
57
|
+
when :roles, :exclude_roles then node.has_role?(pattern)
|
58
|
+
when :nodes_by_config, :exclude_nodes_by_config then node.config_includes?(variable, pattern)
|
59
|
+
when :nodes_by_fact, :exclude_nodes_by_fact then node.has_fact?(variable, pattern)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
unused_pattern_warning(filter_type, pattern, variable) if nodes_list.empty?
|
63
|
+
nodes_list
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def unused_pattern_warning(filter_type, pattern, variable = nil)
|
68
|
+
pattern_s = pattern.kind_of?(Regexp) ? "/#{pattern.source}/" : pattern.to_s
|
69
|
+
msg = variable.nil? ? "'#{pattern_s}'" : "{'#{variable.to_s}' => '#{pattern_s}'}"
|
70
|
+
DopCommon.log.warn("Step '#{name}': #{filter_type.to_s} => #{msg} does not match any node!")
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|