1 require 'puppetlabs_spec_helper/module_spec_helper'
2 require 'rspec-puppet-facts'
3 include RspecPuppetFacts
5 # Customize filters to ignore 3rd-party code
6 # If the rspec coverage report shows not-our-code results, add it here
9 RSpec::Puppet::Coverage.filters.push(*custom_filters)
12 # NB: This is a library of helper fns used by the rspec-puppet tests
15 # Tests that are common to all possible configurations
16 def generic_tests(options = {})
17 java_opts = options.fetch(:java_opts, '')
18 odl_bind_ip = options.fetch(:odl_bind_ip, '0.0.0.0')
19 inactivity_probe = options.fetch(:inactivity_probe, :undef)
21 # Confirm that module compiles
23 it { should compile.with_all_deps }
25 # Confirm presence of classes
26 it { should contain_class('opendaylight') }
27 it { should contain_class('opendaylight::params') }
28 it { should contain_class('opendaylight::install') }
29 it { should contain_class('opendaylight::config') }
30 it { should contain_class('opendaylight::post_config') }
31 it { should contain_class('opendaylight::service') }
33 # Confirm relationships between classes
34 it { should contain_class('opendaylight::install').that_comes_before('Class[opendaylight::config]') }
35 it { should contain_class('opendaylight::config').that_requires('Class[opendaylight::install]') }
36 it { should contain_class('opendaylight::config').that_notifies('Class[opendaylight::service]') }
37 it { should contain_class('opendaylight::service').that_subscribes_to('Class[opendaylight::config]') }
38 it { should contain_class('opendaylight::service').that_comes_before('Class[opendaylight]') }
39 it { should contain_class('opendaylight::post_config').that_requires('Class[opendaylight::service]') }
40 it { should contain_class('opendaylight::post_config').that_comes_before('Class[opendaylight]') }
41 it { should contain_class('opendaylight').that_requires('Class[opendaylight::service]') }
43 # Confirm presence of generic resources
44 it { should contain_service('opendaylight') }
45 it { should contain_file('org.apache.karaf.features.cfg') }
47 # Confirm properties of generic resources
48 # NB: These hashes don't work with Ruby 1.8.7, but we
49 # don't support 1.8.7 so that's okay. See issue #36.
51 should contain_service('opendaylight').with(
52 'ensure' => 'running',
54 'hasstatus' => 'true',
55 'hasrestart' => 'true',
59 should contain_file('org.apache.karaf.features.cfg').with(
61 'path' => '/opt/opendaylight/etc/org.apache.karaf.features.cfg',
68 if odl_bind_ip =~ /.*:.*/
69 java_options = '-Djava.net.preferIPv6Addresses=true'
71 java_options = '-Djava.net.preferIPv4Stack=true'
74 should contain_file_line('Karaf Java Options').with(
75 'ensure' => 'present',
76 'path' => '/opt/opendaylight/bin/karaf',
77 'line' => "EXTRA_JAVA_OPTS=\"#{java_options}\"",
78 'match' => '^EXTRA_JAVA_OPTS=.*$',
79 'after' => '^PROGNAME=.*$'
84 should contain_file('org.opendaylight.ovsdb.library.cfg').with(
86 'path' => '/opt/opendaylight/etc/org.opendaylight.ovsdb.library.cfg',
89 'content' => /ovsdb-listener-ip = #{odl_bind_ip}/
94 should contain_file('default-openflow-connection-config.xml').with(
96 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/default-openflow-connection-config.xml',
99 'content' => /<address>#{odl_bind_ip}<\/address>/
103 unless inactivity_probe == :undef
105 should contain_file('Configure inactivity probe timer').with(
107 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/netvirt-elanmanager-config.xml',
110 'content' => /<controller-inactivity-probe>#{inactivity_probe}<\/controller-inactivity-probe>/
117 # Shared tests that specialize in testing log file size and rollover
118 def log_settings(options = {})
119 # Extraxt params. The dafault value should be same as in opendaylight::params
120 log_max_size = options.fetch(:log_max_size, '10GB')
121 log_max_rollover = options.fetch(:log_max_rollover, 2)
122 log_rollover_fileindex = options.fetch(:log_rollover_fileindex, 'min')
123 log_mechanism = options.fetch(:log_mechanism, 'file')
125 if log_mechanism == 'console'
127 should contain_file_line('consoleappender').with(
128 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
129 'line' => 'karaf.log.console=INFO',
130 'after' => 'log4j2.rootLogger.appenderRef.Console.filter.threshold.type = ThresholdFilter',
131 'match' => '^karaf.log.console.*$'
135 should contain_file_line('direct').with(
136 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
137 'line' => 'log4j2.appender.console.direct = true',
138 'after' => 'karaf.log.console=INFO',
139 'match' => '^log4j2.appender.console.direct.*$'
145 should contain_file_line('logmaxsize').with(
146 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
147 'line' => "log4j2.appender.rolling.policies.size.size = #{log_max_size}",
148 'match' => '^log4j2.appender.rolling.policies.size.size.*$',
152 should contain_file_line('rolloverstrategy').with(
153 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
154 'line' => 'log4j2.appender.rolling.strategy.type = DefaultRolloverStrategy'
158 should contain_file_line('logmaxrollover').with(
159 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
160 'line' => "log4j2.appender.rolling.strategy.max = #{log_max_rollover}",
161 'match' => '^log4j2.appender.rolling.strategy.max.*$',
165 should contain_file_line('logrolloverfileindex').with(
166 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
167 'line' => "log4j2.appender.rolling.strategy.fileIndex = #{log_rollover_fileindex}",
168 'match' => '^log4j2.appender.rolling.strategy.fileIndex.*$',
174 # Shared tests that specialize in testing Karaf feature installs
175 def karaf_feature_tests(options = {})
177 # NB: This default list should be the same as the one in opendaylight::params
178 # TODO: Remove this possible source of bugs^^
179 default_features = options.fetch(:default_features, ['standard', 'wrap', 'ssh'])
180 extra_features = options.fetch(:extra_features, [])
182 # The order of this list concat matters
183 features = default_features + extra_features
184 features_csv = features.join(',')
186 # Confirm properties of Karaf features config file
187 # NB: These hashes don't work with Ruby 1.8.7, but we
188 # don't support 1.8.7 so that's okay. See issue #36.
190 should contain_file('org.apache.karaf.features.cfg').with(
192 'path' => '/opt/opendaylight/etc/org.apache.karaf.features.cfg',
198 should contain_file_line('featuresBoot').with(
199 'path' => '/opt/opendaylight/etc/org.apache.karaf.features.cfg',
200 'line' => "featuresBoot=#{features_csv}",
201 'match' => '^featuresBoot=.*$',
206 # Shared tests that specialize in testing ODL's REST port config
207 def odl_rest_port_tests(options = {})
209 # NB: This default value should be the same as one in opendaylight::params
210 # TODO: Remove this possible source of bugs^^
211 odl_rest_port = options.fetch(:odl_rest_port, 8181)
212 odl_bind_ip = options.fetch(:odl_bind_ip, '0.0.0.0')
213 # Confirm properties of ODL REST port config file
214 # NB: These hashes don't work with Ruby 1.8.7, but we
215 # don't support 1.8.7 so that's okay. See issue #36.
217 should contain_augeas('ODL REST Port')
220 if not odl_bind_ip.eql? '0.0.0.0'
222 should contain_augeas('ODL REST IP')
223 should contain_file_line('set pax bind IP').with(
224 'ensure' => 'present',
225 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
226 'line' => "org.ops4j.pax.web.listening.addresses = #{odl_bind_ip}",
227 'require' => 'File[org.ops4j.pax.web.cfg]'
229 should contain_file_line('set karaf IP').with(
230 'ensure' => 'present',
231 'path' => '/opt/opendaylight/etc/org.apache.karaf.shell.cfg',
232 'line' => "sshHost = #{odl_bind_ip}",
233 'match' => '^sshHost\s*=.*$',
238 should_not contain_augeas('ODL REST IP')
243 should contain_file_line('set pax bind port').with(
244 'ensure' => 'present',
245 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
246 'line' => "org.osgi.service.http.port = #{odl_rest_port}",
247 'match' => '^#?org.osgi.service.http.port\s.*$',
248 'require' => 'File[org.ops4j.pax.web.cfg]'
253 def log_level_tests(options = {})
255 # NB: This default value should be the same as one in opendaylight::params
256 # TODO: Remove this possible source of bugs^^
257 log_levels = options.fetch(:log_levels, {})
260 # Should contain log level config file
262 should_not contain_file_line('logger-org.opendaylight.ovsdb-level')
265 should_not contain_file_line('logger-org.opendaylight.ovsdb-name')
268 # Verify each custom log level config entry
269 log_levels.each_pair do |logger, level|
270 underscored_version = "#{logger}".gsub('.', '_')
272 should contain_file_line("logger-#{logger}-level").with(
273 'ensure' => 'present',
274 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
275 'line' => "log4j2.logger.#{underscored_version}.level = #{level}",
276 'match' => "log4j2.logger.#{underscored_version}.level = .*$"
278 should contain_file_line("logger-#{logger}-name").with(
279 'ensure' => 'present',
280 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
281 'line' => "log4j2.logger.#{underscored_version}.name = #{logger}",
282 'match' => "log4j2.logger.#{underscored_version}.name = .*$"
289 def enable_ha_tests(options = {})
291 enable_ha = options.fetch(:enable_ha, false)
292 odl_bind_ip = options.fetch(:odl_bind_ip, '0.0.0.0')
293 ha_node_ips = options.fetch(:ha_node_ips, [])
294 ha_db_modules = options.fetch(:ha_db_modules, { 'default' => false })
296 ha_node_count = ha_node_ips.size
298 if (enable_ha) && (ha_node_count < 2)
299 # Check for HA_NODE_COUNT < 2
300 fail("Number of HA nodes less than 2: #{ha_node_count} and HA Enabled")
304 ha_node_index = ha_node_ips.index(odl_bind_ip)
306 should contain_file('akka.conf').with(
307 'path' => '/opt/opendaylight/configuration/initial/akka.conf',
311 'content' => /roles\s*=\s*\["member-#{ha_node_index}"\]/
315 ha_db_modules.each do |mod, urn|
316 it { should contain_file('module-shards.conf').with(
317 'path' => '/opt/opendaylight/configuration/initial/module-shards.conf',
321 'content' => /name = "#{mod}"/
324 it { should contain_file('modules.conf').with(
325 'path' => '/opt/opendaylight/configuration/initial/modules.conf',
331 it { should contain_file('modules.conf').with(
332 'path' => '/opt/opendaylight/configuration/initial/modules.conf',
336 'content' => /name = "#{mod}"/,
342 should_not contain_file('akka.conf')
343 should_not contain_file('module-shards.conf')
344 should_not contain_file('modules.conf')
349 def rpm_install_tests(options = {})
351 rpm_repo = options.fetch(:rpm_repo, 'https://nexus.opendaylight.org/content/repositories/opendaylight-fluorine-epel-7-$basearch-devel')
354 # Default to CentOS 7 Yum repo URL
356 # Confirm presence of RPM-related resources
357 it { should contain_yumrepo('opendaylight') }
358 it { should contain_package('opendaylight') }
360 # Confirm relationships between RPM-related resources
361 it { should contain_package('opendaylight').that_requires('Yumrepo[opendaylight]') }
362 it { should contain_yumrepo('opendaylight').that_comes_before('Package[opendaylight]') }
364 # Confirm properties of RPM-related resources
365 # NB: These hashes don't work with Ruby 1.8.7, but we
366 # don't support 1.8.7 so that's okay. See issue #36.
368 should contain_yumrepo('opendaylight').with(
371 'descr' => 'OpenDaylight SDN Controller',
372 'baseurl' => "#{rpm_repo}",
376 should contain_package('opendaylight').with(
377 'ensure' => 'present',
382 def deb_install_tests(options = {})
384 deb_repo = options.fetch(:deb_repo, 'ppa:odl-team/nitrogen')
386 # Confirm the presence of Deb-related resources
387 it { should contain_apt__ppa(deb_repo) }
388 it { should contain_package('opendaylight') }
390 # Confirm relationships between Deb-related resources
391 it { should contain_package('opendaylight').that_requires("Apt::Ppa[#{deb_repo}]") }
392 it { should contain_apt__ppa(deb_repo).that_comes_before('Package[opendaylight]') }
394 # Confirm presence of Deb-related resources
396 should contain_package('opendaylight').with(
397 'ensure' => 'present',
402 # Shared tests for unsupported OSs
403 def unsupported_os_tests(options = {})
405 expected_msg = options.fetch(:expected_msg)
406 rpm_repo = options.fetch(:rpm_repo, 'https://nexus.opendaylight.org/content/repositories/opendaylight-fluorine-epel-7-$basearch-devel')
408 # Confirm that classes fail on unsupported OSs
409 it { expect { should contain_class('opendaylight') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
410 it { expect { should contain_class('opendaylight::install') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
411 it { expect { should contain_class('opendaylight::config') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
412 it { expect { should contain_class('opendaylight::service') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
414 # Confirm that other resources fail on unsupported OSs
415 it { expect { should contain_yumrepo('opendaylight') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
416 it { expect { should contain_package('opendaylight') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
417 it { expect { should contain_service('opendaylight') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
418 it { expect { should contain_file('org.apache.karaf.features.cfg') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
421 # Shared tests that specialize in testing SNAT mechanism
422 def snat_mechanism_tests(snat_mechanism='controller')
423 it { should contain_file('/opt/opendaylight/etc/opendaylight') }
424 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore')}
425 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore/initial')}
426 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore/initial/config')}
428 # Confirm snat_mechanism
430 should contain_file('netvirt-natservice-config.xml').with(
432 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/netvirt-natservice-config.xml',
435 'content' => /<nat-mode>#{snat_mechanism}<\/nat-mode>/
440 # Shared tests that specialize in testing SFC Config
441 def sfc_tests(options = {})
442 extra_features = options.fetch(:extra_features, [])
444 if extra_features.include? 'odl-netvirt-sfc'
450 it { should contain_file('/opt/opendaylight/etc/opendaylight') }
451 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore')}
452 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore/initial')}
453 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore/initial/config')}
456 should contain_file('genius-itm-config.xml').with(
458 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/genius-itm-config.xml',
461 'content' => /<gpe-extension-enabled>#{sfc_enabled}<\/gpe-extension-enabled>/
466 # Shared tests that specialize in testing DSCP marking config
467 def dscp_tests(options = {})
468 inherit_dscp_marking = options.fetch(:inherit_dscp_marking, false)
470 if inherit_dscp_marking
472 should contain_file('genius-itm-config.xml').with(
474 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/genius-itm-config.xml',
477 'content' => /<default-tunnel-tos>inherit<\/default-tunnel-tos>/
482 should contain_file('genius-itm-config.xml').with(
484 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/genius-itm-config.xml',
487 'content' => /<default-tunnel-tos>0<\/default-tunnel-tos>/
493 # Shared tests that specialize in testing VPP routing node config
494 def vpp_routing_node_tests(options = {})
496 # NB: This default list should be the same as the one in opendaylight::params
497 # TODO: Remove this possible source of bugs^^
498 routing_node = options.fetch(:routing_node, '')
500 if routing_node.empty?
501 it { should_not contain_file('org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.startup.cfg') }
502 it { should_not contain_file_line('routing-node') }
504 # Confirm properties of Karaf config file
505 # NB: These hashes don't work with Ruby 1.8.7, but we
506 # don't support 1.8.7 so that's okay. See issue #36.
508 should contain_file('org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.startup.cfg').with(
510 'path' => '/opt/opendaylight/etc/org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.startup.cfg',
516 should contain_file_line('routing-node').with(
517 'path' => '/opt/opendaylight/etc/org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.startup.cfg',
518 'line' => "routing-node=#{routing_node}",
519 'match' => '^routing-node=.*$',
525 # ODL username/password tests
526 def username_password_tests(username, password)
529 should contain_odl_user(username).with(
530 :password => password
535 # ODL websocket address tests
536 def odl_websocket_address_tests(options = {})
538 # NB: This default value should be the same as one in opendaylight::params
539 # TODO: Remove this possible source of bugs^^
540 odl_bind_ip = options.fetch(:odl_bind_ip, '0.0.0.0')
541 # Confirm properties of ODL REST port config file
542 # NB: These hashes don't work with Ruby 1.8.7, but we
543 # don't support 1.8.7 so that's okay. See issue #36.
545 if not odl_bind_ip.eql? '0.0.0.0'
547 should contain_file('/opt/opendaylight/etc/org.opendaylight.restconf.cfg').with(
549 'path' => '/opt/opendaylight/etc/org.opendaylight.restconf.cfg',
555 should contain_file_line('websocket-address').with(
556 'path' => '/opt/opendaylight/etc/org.opendaylight.restconf.cfg',
557 'line' => "websocket-address=#{odl_bind_ip}",
558 'match' => '^websocket-address=.*$',
563 should_not contain_file_line('websocket-address')
568 def odl_tls_tests(options = {})
569 enable_tls = options.fetch(:enable_tls, false)
570 tls_keystore_password = options.fetch(:tls_keystore_password, nil)
571 tls_trusted_certs = options.fetch(:tls_trusted_certs, [])
572 tls_keystore_password = options.fetch(:tls_keystore_password, nil)
573 tls_key_file = options.fetch(:tls_key_file, nil)
574 tls_cert_file = options.fetch(:tls_cert_file, nil)
575 tls_ca_cert_file = options.fetch(:tls_ca_cert_file, nil)
576 odl_rest_port = options.fetch(:odl_rest_port, 8181)
579 if tls_keystore_password.nil?
580 it { expect { should contain_class('opendaylight::config') }.to raise_error(Puppet::PreformattedError) }
584 if tls_key_file or tls_cert_file
585 if tls_key_file and tls_cert_file
587 should contain_odl_keystore('controller')
590 it { expect { should contain_class('opendaylight::config') }.to raise_error(Puppet::PreformattedError) }
594 should contain_augeas('Remove HTTP ODL REST Port')
595 should contain_augeas('ODL SSL REST Port')
596 should contain_file_line('set pax TLS port').with(
597 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
598 'line' => "org.osgi.service.http.port.secure = #{odl_rest_port}",
599 'match' => '^#?org.osgi.service.http.port.secure.*$',
601 should contain_file_line('set pax TLS keystore location').with(
602 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
603 'line' => 'org.ops4j.pax.web.ssl.keystore = configuration/ssl/ctl.jks',
604 'match' => '^#?org.ops4j.pax.web.ssl.keystore.*$',
606 should contain_file_line('set pax TLS keystore integrity password').with(
607 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
608 'line' => "org.ops4j.pax.web.ssl.password = #{tls_keystore_password}",
609 'match' => '^#?org.ops4j.pax.web.ssl.password.*$',
611 should contain_file_line('set pax TLS keystore password').with(
612 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
613 'line' => "org.ops4j.pax.web.ssl.keypassword = #{tls_keystore_password}",
614 'match' => '^#?org.ops4j.pax.web.ssl.keypassword.*$',
616 should contain_file('aaa-cert-config.xml').with(
618 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/aaa-cert-config.xml',
622 should contain_file('org.opendaylight.ovsdb.library.cfg').with(
624 'path' => '/opt/opendaylight/etc/org.opendaylight.ovsdb.library.cfg',
627 'content' => /use-ssl = true/
629 should contain_file('/opt/opendaylight/configuration/ssl').with(
630 'ensure' => 'directory',
631 'path' => '/opt/opendaylight/configuration/ssl',
636 should contain_file_line('enable pax TLS').with(
637 'ensure' => 'present',
638 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
639 'line' => 'org.osgi.service.http.secure.enabled = true',
640 'match' => '^#?org.osgi.service.http.secure.enabled.*$',
642 should contain_file_line('disable pax HTTP').with(
643 'ensure' => 'present',
644 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
645 'line' => 'org.osgi.service.http.enabled = false',
646 'match' => '^#?org.osgi.service.http.enabled.*$',
648 should contain_file('org.ops4j.pax.web.cfg').with(
650 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
654 should contain_file('default-openflow-connection-config.xml').with(
656 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/default-openflow-connection-config.xml',
659 'content' => /<transport-protocol>TLS<\/transport-protocol>/
665 def stats_polling_enablement_tests(options = {})
667 # NB: This default value should be the same as one in opendaylight::params
668 # TODO: Remove this possible source of bugs^^
669 stats_polling_enabled = options.fetch(:stats_polling_enabled, false)
670 # Confirm properties of ODL REST port config file
671 # NB: These hashes don't work with Ruby 1.8.7, but we
672 # don't support 1.8.7 so that's okay. See issue #36.
674 should contain_file('openflowplugin.cfg').with(
676 'path' => '/opt/opendaylight/etc/org.opendaylight.openflowplugin.cfg',
680 should contain_file_line('stats-polling').with(
681 'ensure' => 'present',
682 'path' => '/opt/opendaylight/etc/org.opendaylight.openflowplugin.cfg',
683 'line' => "is-statistics-polling-on=#{stats_polling_enabled}",
684 'match' => '^is-statistics-polling-on=.*$',