dop_common 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +176 -0
  6. data/Gemfile +11 -0
  7. data/LICENSE.txt +177 -0
  8. data/README.md +48 -0
  9. data/Rakefile +49 -0
  10. data/Vagrantfile +25 -0
  11. data/bin/dop-puppet-autosign +56 -0
  12. data/doc/examples/example_deploment_plan_v0.0.1.yaml +302 -0
  13. data/doc/plan_format_v0.0.1.md +919 -0
  14. data/doc/plan_format_v0.0.2_snippets.md +56 -0
  15. data/dop_common.gemspec +44 -0
  16. data/lib/dop_common/affinity_group.rb +57 -0
  17. data/lib/dop_common/cli/global_options.rb +37 -0
  18. data/lib/dop_common/cli/log.rb +51 -0
  19. data/lib/dop_common/cli/node_selection.rb +62 -0
  20. data/lib/dop_common/command.rb +125 -0
  21. data/lib/dop_common/config/helper.rb +39 -0
  22. data/lib/dop_common/config.rb +66 -0
  23. data/lib/dop_common/configuration.rb +37 -0
  24. data/lib/dop_common/credential.rb +152 -0
  25. data/lib/dop_common/data_disk.rb +62 -0
  26. data/lib/dop_common/dns.rb +55 -0
  27. data/lib/dop_common/hash_parser.rb +241 -0
  28. data/lib/dop_common/hooks.rb +81 -0
  29. data/lib/dop_common/infrastructure.rb +160 -0
  30. data/lib/dop_common/infrastructure_properties.rb +185 -0
  31. data/lib/dop_common/interface.rb +113 -0
  32. data/lib/dop_common/log.rb +78 -0
  33. data/lib/dop_common/network.rb +85 -0
  34. data/lib/dop_common/node/config.rb +159 -0
  35. data/lib/dop_common/node.rb +442 -0
  36. data/lib/dop_common/node_filter.rb +74 -0
  37. data/lib/dop_common/plan.rb +188 -0
  38. data/lib/dop_common/plan_cache.rb +83 -0
  39. data/lib/dop_common/plan_store.rb +263 -0
  40. data/lib/dop_common/pre_processor.rb +73 -0
  41. data/lib/dop_common/run_options.rb +56 -0
  42. data/lib/dop_common/signal_handler.rb +58 -0
  43. data/lib/dop_common/state_store.rb +95 -0
  44. data/lib/dop_common/step.rb +200 -0
  45. data/lib/dop_common/step_set.rb +41 -0
  46. data/lib/dop_common/thread_context_logger.rb +77 -0
  47. data/lib/dop_common/utils.rb +106 -0
  48. data/lib/dop_common/validator.rb +53 -0
  49. data/lib/dop_common/version.rb +3 -0
  50. data/lib/dop_common.rb +32 -0
  51. data/lib/hiera/backend/dop_backend.rb +94 -0
  52. data/lib/hiera/dop_logger.rb +20 -0
  53. data/spec/data/fake_hook_file_invalid +1 -0
  54. data/spec/data/fake_hook_file_valid +5 -0
  55. data/spec/data/fake_keyfile +1 -0
  56. data/spec/dop-puppet-autosign_spec_disable.rb +33 -0
  57. data/spec/dop_common/affinity_group_spec.rb +41 -0
  58. data/spec/dop_common/command_spec.rb +83 -0
  59. data/spec/dop_common/credential_spec.rb +73 -0
  60. data/spec/dop_common/data_disk_spec.rb +165 -0
  61. data/spec/dop_common/dns_spec.rb +33 -0
  62. data/spec/dop_common/hash_parser_spec.rb +181 -0
  63. data/spec/dop_common/hooks_spec.rb +33 -0
  64. data/spec/dop_common/infrastructure_properties_spec.rb +224 -0
  65. data/spec/dop_common/infrastructure_spec.rb +77 -0
  66. data/spec/dop_common/interface_spec.rb +192 -0
  67. data/spec/dop_common/network_spec.rb +92 -0
  68. data/spec/dop_common/node_filter_spec.rb +70 -0
  69. data/spec/dop_common/node_spec.rb +623 -0
  70. data/spec/dop_common/plan_cache_spec.rb +46 -0
  71. data/spec/dop_common/plan_spec.rb +136 -0
  72. data/spec/dop_common/plan_store_spec.rb +194 -0
  73. data/spec/dop_common/pre_processor_spec.rb +27 -0
  74. data/spec/dop_common/run_options_spec.rb +65 -0
  75. data/spec/dop_common/signal_handler_spec.rb +31 -0
  76. data/spec/dop_common/step_set_spec.rb +21 -0
  77. data/spec/dop_common/step_spec.rb +175 -0
  78. data/spec/dop_common/utils_spec.rb +27 -0
  79. data/spec/dop_common/validator_spec.rb +47 -0
  80. data/spec/example_plans_spec.rb +16 -0
  81. data/spec/fixtures/example_ssh_key +27 -0
  82. data/spec/fixtures/example_ssh_key.pub +1 -0
  83. data/spec/fixtures/incl/root_part.yaml +1 -0
  84. data/spec/fixtures/incl/some_list.yaml +2 -0
  85. data/spec/fixtures/other_plan_same_nodes.yaml +19 -0
  86. data/spec/fixtures/simple_include.yaml +6 -0
  87. data/spec/fixtures/simple_include_with_errors.yaml +4 -0
  88. data/spec/fixtures/simple_plan.yaml +19 -0
  89. data/spec/fixtures/simple_plan_invalid.yaml +18 -0
  90. data/spec/fixtures/simple_plan_modified.yaml +21 -0
  91. data/spec/spec_helper.rb +106 -0
  92. 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