dop_common 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|