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.
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