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')
20 # Confirm that module compiles
22 it { should compile.with_all_deps }
24 # Confirm presence of classes
25 it { should contain_class('opendaylight') }
26 it { should contain_class('opendaylight::params') }
27 it { should contain_class('opendaylight::install') }
28 it { should contain_class('opendaylight::config') }
29 it { should contain_class('opendaylight::post_config') }
30 it { should contain_class('opendaylight::service') }
32 # Confirm relationships between classes
33 it { should contain_class('opendaylight::install').that_comes_before('Class[opendaylight::config]') }
34 it { should contain_class('opendaylight::config').that_requires('Class[opendaylight::install]') }
35 it { should contain_class('opendaylight::config').that_notifies('Class[opendaylight::service]') }
36 it { should contain_class('opendaylight::service').that_subscribes_to('Class[opendaylight::config]') }
37 it { should contain_class('opendaylight::service').that_comes_before('Class[opendaylight]') }
38 it { should contain_class('opendaylight::post_config').that_requires('Class[opendaylight::service]') }
39 it { should contain_class('opendaylight::post_config').that_comes_before('Class[opendaylight]') }
40 it { should contain_class('opendaylight').that_requires('Class[opendaylight::service]') }
42 # Confirm presence of generic resources
43 it { should contain_service('opendaylight') }
44 it { should contain_file('org.apache.karaf.features.cfg') }
46 # Confirm properties of generic resources
47 # NB: These hashes don't work with Ruby 1.8.7, but we
48 # don't support 1.8.7 so that's okay. See issue #36.
50 should contain_service('opendaylight').with(
51 'ensure' => 'running',
53 'hasstatus' => 'true',
54 'hasrestart' => 'true',
58 should contain_file('org.apache.karaf.features.cfg').with(
60 'path' => '/opt/opendaylight/etc/org.apache.karaf.features.cfg',
67 if odl_bind_ip =~ /.*:.*/
68 java_options = '-Djava.net.preferIPv6Addresses=true'
70 java_options = '-Djava.net.preferIPv4Stack=true'
73 should contain_file_line('Karaf Java Options').with(
74 'ensure' => 'present',
75 'path' => '/opt/opendaylight/bin/karaf',
76 'line' => "EXTRA_JAVA_OPTS=#{java_options}",
77 'match' => '^EXTRA_JAVA_OPTS=.*$',
78 'after' => '^PROGNAME=.*$'
83 should contain_file('org.opendaylight.ovsdb.library.cfg').with(
85 'path' => '/opt/opendaylight/etc/org.opendaylight.ovsdb.library.cfg',
88 'content' => /ovsdb-listener-ip = #{odl_bind_ip}/
93 should contain_file('default-openflow-connection-config.xml').with(
95 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/default-openflow-connection-config.xml',
98 'content' => /<address>#{odl_bind_ip}<\/address>/
104 # Shared tests that specialize in testing log file size and rollover
105 def log_settings(options = {})
106 # Extraxt params. The dafault value should be same as in opendaylight::params
107 log_max_size = options.fetch(:log_max_size, '10GB')
108 log_max_rollover = options.fetch(:log_max_rollover, 2)
109 log_rollover_fileindex = options.fetch(:log_rollover_fileindex, 'min')
110 log_mechanism = options.fetch(:log_mechanism, 'file')
112 if log_mechanism == 'console'
114 should contain_file_line('consoleappender').with(
115 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
116 'line' => 'karaf.log.console=INFO',
117 'after' => 'log4j2.rootLogger.appenderRef.Console.filter.threshold.type = ThresholdFilter',
118 'match' => '^karaf.log.console.*$'
122 should contain_file_line('direct').with(
123 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
124 'line' => 'log4j2.appender.console.direct = true',
125 'after' => 'karaf.log.console=INFO',
126 'match' => '^log4j2.appender.console.direct.*$'
132 should contain_file_line('logmaxsize').with(
133 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
134 'line' => "log4j2.appender.rolling.policies.size.size = #{log_max_size}",
135 'match' => '^log4j2.appender.rolling.policies.size.size.*$',
139 should contain_file_line('rolloverstrategy').with(
140 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
141 'line' => 'log4j2.appender.rolling.strategy.type = DefaultRolloverStrategy'
145 should contain_file_line('logmaxrollover').with(
146 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
147 'line' => "log4j2.appender.rolling.strategy.max = #{log_max_rollover}",
148 'match' => '^log4j2.appender.rolling.strategy.max.*$',
152 should contain_file_line('logrolloverfileindex').with(
153 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
154 'line' => "log4j2.appender.rolling.strategy.fileIndex = #{log_rollover_fileindex}",
155 'match' => '^log4j2.appender.rolling.strategy.fileIndex.*$',
161 # Shared tests that specialize in testing Karaf feature installs
162 def karaf_feature_tests(options = {})
164 # NB: This default list should be the same as the one in opendaylight::params
165 # TODO: Remove this possible source of bugs^^
166 default_features = options.fetch(:default_features, ['standard', 'wrap', 'ssh'])
167 extra_features = options.fetch(:extra_features, [])
169 # The order of this list concat matters
170 features = default_features + extra_features
171 features_csv = features.join(',')
173 # Confirm properties of Karaf features config file
174 # NB: These hashes don't work with Ruby 1.8.7, but we
175 # don't support 1.8.7 so that's okay. See issue #36.
177 should contain_file('org.apache.karaf.features.cfg').with(
179 'path' => '/opt/opendaylight/etc/org.apache.karaf.features.cfg',
185 should contain_file_line('featuresBoot').with(
186 'path' => '/opt/opendaylight/etc/org.apache.karaf.features.cfg',
187 'line' => "featuresBoot=#{features_csv}",
188 'match' => '^featuresBoot=.*$',
193 # Shared tests that specialize in testing ODL's REST port config
194 def odl_rest_port_tests(options = {})
196 # NB: This default value should be the same as one in opendaylight::params
197 # TODO: Remove this possible source of bugs^^
198 odl_rest_port = options.fetch(:odl_rest_port, 8181)
199 odl_bind_ip = options.fetch(:odl_bind_ip, '0.0.0.0')
200 # Confirm properties of ODL REST port config file
201 # NB: These hashes don't work with Ruby 1.8.7, but we
202 # don't support 1.8.7 so that's okay. See issue #36.
204 should contain_augeas('ODL REST Port')
207 if not odl_bind_ip.eql? '0.0.0.0'
209 should contain_augeas('ODL REST IP')
210 should contain_file_line('set pax bind IP').with(
211 'ensure' => 'present',
212 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
213 'line' => "org.ops4j.pax.web.listening.addresses = #{odl_bind_ip}",
214 'require' => 'File[org.ops4j.pax.web.cfg]'
216 should contain_file_line('set karaf IP').with(
217 'ensure' => 'present',
218 'path' => '/opt/opendaylight/etc/org.apache.karaf.shell.cfg',
219 'line' => "sshHost = #{odl_bind_ip}",
220 'match' => '^sshHost\s*=.*$',
225 should_not contain_augeas('ODL REST IP')
230 should contain_file_line('set pax bind port').with(
231 'ensure' => 'present',
232 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
233 'line' => "org.osgi.service.http.port = #{odl_rest_port}",
234 'match' => '^#?org.osgi.service.http.port\s.*$',
235 'require' => 'File[org.ops4j.pax.web.cfg]'
240 def log_level_tests(options = {})
242 # NB: This default value should be the same as one in opendaylight::params
243 # TODO: Remove this possible source of bugs^^
244 log_levels = options.fetch(:log_levels, {})
247 # Should contain log level config file
249 should_not contain_file_line('logger-org.opendaylight.ovsdb-level')
252 should_not contain_file_line('logger-org.opendaylight.ovsdb-name')
255 # Verify each custom log level config entry
256 log_levels.each_pair do |logger, level|
257 underscored_version = "#{logger}".gsub('.', '_')
259 should contain_file_line("logger-#{logger}-level").with(
260 'ensure' => 'present',
261 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
262 'line' => "log4j2.logger.#{underscored_version}.level = #{level}",
263 'match' => "log4j2.logger.#{underscored_version}.level = .*$"
265 should contain_file_line("logger-#{logger}-name").with(
266 'ensure' => 'present',
267 'path' => '/opt/opendaylight/etc/org.ops4j.pax.logging.cfg',
268 'line' => "log4j2.logger.#{underscored_version}.name = #{logger}",
269 'match' => "log4j2.logger.#{underscored_version}.name = .*$"
276 def enable_ha_tests(options = {})
278 enable_ha = options.fetch(:enable_ha, false)
279 odl_bind_ip = options.fetch(:odl_bind_ip, '0.0.0.0')
280 ha_node_ips = options.fetch(:ha_node_ips, [])
281 ha_db_modules = options.fetch(:ha_db_modules, { 'default' => false })
283 ha_node_count = ha_node_ips.size
285 if (enable_ha) && (ha_node_count < 2)
286 # Check for HA_NODE_COUNT < 2
287 fail("Number of HA nodes less than 2: #{ha_node_count} and HA Enabled")
291 ha_node_index = ha_node_ips.index(odl_bind_ip)
293 should contain_file('akka.conf').with(
294 'path' => '/opt/opendaylight/configuration/initial/akka.conf',
298 'content' => /roles\s*=\s*\["member-#{ha_node_index}"\]/
302 ha_db_modules.each do |mod, urn|
303 it { should contain_file('module-shards.conf').with(
304 'path' => '/opt/opendaylight/configuration/initial/module-shards.conf',
308 'content' => /name = "#{mod}"/
311 it { should contain_file('modules.conf').with(
312 'path' => '/opt/opendaylight/configuration/initial/modules.conf',
318 it { should contain_file('modules.conf').with(
319 'path' => '/opt/opendaylight/configuration/initial/modules.conf',
323 'content' => /name = "#{mod}"/,
329 should_not contain_file('akka.conf')
330 should_not contain_file('module-shards.conf')
331 should_not contain_file('modules.conf')
336 def rpm_install_tests(options = {})
338 rpm_repo = options.fetch(:rpm_repo, 'https://nexus.opendaylight.org/content/repositories/opendaylight-fluorine-epel-7-$basearch-devel')
341 # Default to CentOS 7 Yum repo URL
343 # Confirm presence of RPM-related resources
344 it { should contain_yumrepo('opendaylight') }
345 it { should contain_package('opendaylight') }
347 # Confirm relationships between RPM-related resources
348 it { should contain_package('opendaylight').that_requires('Yumrepo[opendaylight]') }
349 it { should contain_yumrepo('opendaylight').that_comes_before('Package[opendaylight]') }
351 # Confirm properties of RPM-related resources
352 # NB: These hashes don't work with Ruby 1.8.7, but we
353 # don't support 1.8.7 so that's okay. See issue #36.
355 should contain_yumrepo('opendaylight').with(
358 'descr' => 'OpenDaylight SDN Controller',
359 'baseurl' => "#{rpm_repo}",
363 should contain_package('opendaylight').with(
364 'ensure' => 'present',
369 def deb_install_tests(options = {})
371 deb_repo = options.fetch(:deb_repo, 'ppa:odl-team/nitrogen')
373 # Confirm the presence of Deb-related resources
374 it { should contain_apt__ppa(deb_repo) }
375 it { should contain_package('opendaylight') }
377 # Confirm relationships between Deb-related resources
378 it { should contain_package('opendaylight').that_requires("Apt::Ppa[#{deb_repo}]") }
379 it { should contain_apt__ppa(deb_repo).that_comes_before('Package[opendaylight]') }
381 # Confirm presence of Deb-related resources
383 should contain_package('opendaylight').with(
384 'ensure' => 'present',
389 # Shared tests for unsupported OSs
390 def unsupported_os_tests(options = {})
392 expected_msg = options.fetch(:expected_msg)
393 rpm_repo = options.fetch(:rpm_repo, 'https://nexus.opendaylight.org/content/repositories/opendaylight-fluorine-epel-7-$basearch-devel')
395 # Confirm that classes fail on unsupported OSs
396 it { expect { should contain_class('opendaylight') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
397 it { expect { should contain_class('opendaylight::install') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
398 it { expect { should contain_class('opendaylight::config') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
399 it { expect { should contain_class('opendaylight::service') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
401 # Confirm that other resources fail on unsupported OSs
402 it { expect { should contain_yumrepo('opendaylight') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
403 it { expect { should contain_package('opendaylight') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
404 it { expect { should contain_service('opendaylight') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
405 it { expect { should contain_file('org.apache.karaf.features.cfg') }.to raise_error(Puppet::Error, /#{expected_msg}/) }
408 # Shared tests that specialize in testing SNAT mechanism
409 def snat_mechanism_tests(snat_mechanism='controller')
410 it { should contain_file('/opt/opendaylight/etc/opendaylight') }
411 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore')}
412 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore/initial')}
413 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore/initial/config')}
415 # Confirm snat_mechanism
417 should contain_file('netvirt-natservice-config.xml').with(
419 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/netvirt-natservice-config.xml',
422 'content' => /<nat-mode>#{snat_mechanism}<\/nat-mode>/
427 # Shared tests that specialize in testing SFC Config
428 def sfc_tests(options = {})
429 extra_features = options.fetch(:extra_features, [])
431 if extra_features.include? 'odl-netvirt-sfc'
437 it { should contain_file('/opt/opendaylight/etc/opendaylight') }
438 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore')}
439 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore/initial')}
440 it { should contain_file('/opt/opendaylight/etc/opendaylight/datastore/initial/config')}
443 should contain_file('genius-itm-config.xml').with(
445 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/genius-itm-config.xml',
448 'content' => /<gpe-extension-enabled>#{sfc_enabled}<\/gpe-extension-enabled>/
453 # Shared tests that specialize in testing DSCP marking config
454 def dscp_tests(options = {})
455 inherit_dscp_marking = options.fetch(:inherit_dscp_marking, false)
457 if inherit_dscp_marking
459 should contain_file('genius-itm-config.xml').with(
461 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/genius-itm-config.xml',
464 'content' => /<default-tunnel-tos>inherit<\/default-tunnel-tos>/
469 should contain_file('genius-itm-config.xml').with(
471 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/genius-itm-config.xml',
474 'content' => /<default-tunnel-tos>0<\/default-tunnel-tos>/
480 # Shared tests that specialize in testing VPP routing node config
481 def vpp_routing_node_tests(options = {})
483 # NB: This default list should be the same as the one in opendaylight::params
484 # TODO: Remove this possible source of bugs^^
485 routing_node = options.fetch(:routing_node, '')
487 if routing_node.empty?
488 it { should_not contain_file('org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.startup.cfg') }
489 it { should_not contain_file_line('routing-node') }
491 # Confirm properties of Karaf config file
492 # NB: These hashes don't work with Ruby 1.8.7, but we
493 # don't support 1.8.7 so that's okay. See issue #36.
495 should contain_file('org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.startup.cfg').with(
497 'path' => '/opt/opendaylight/etc/org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.startup.cfg',
503 should contain_file_line('routing-node').with(
504 'path' => '/opt/opendaylight/etc/org.opendaylight.groupbasedpolicy.neutron.vpp.mapper.startup.cfg',
505 'line' => "routing-node=#{routing_node}",
506 'match' => '^routing-node=.*$',
512 # ODL username/password tests
513 def username_password_tests(username, password)
516 should contain_odl_user(username).with(
517 :password => password
522 # ODL websocket address tests
523 def odl_websocket_address_tests(options = {})
525 # NB: This default value should be the same as one in opendaylight::params
526 # TODO: Remove this possible source of bugs^^
527 odl_bind_ip = options.fetch(:odl_bind_ip, '0.0.0.0')
528 # Confirm properties of ODL REST port config file
529 # NB: These hashes don't work with Ruby 1.8.7, but we
530 # don't support 1.8.7 so that's okay. See issue #36.
532 if not odl_bind_ip.eql? '0.0.0.0'
534 should contain_file('/opt/opendaylight/etc/org.opendaylight.restconf.cfg').with(
536 'path' => '/opt/opendaylight/etc/org.opendaylight.restconf.cfg',
542 should contain_file_line('websocket-address').with(
543 'path' => '/opt/opendaylight/etc/org.opendaylight.restconf.cfg',
544 'line' => "websocket-address=#{odl_bind_ip}",
545 'match' => '^websocket-address=.*$',
550 should_not contain_file_line('websocket-address')
555 def odl_tls_tests(options = {})
556 enable_tls = options.fetch(:enable_tls, false)
557 tls_keystore_password = options.fetch(:tls_keystore_password, nil)
558 tls_trusted_certs = options.fetch(:tls_trusted_certs, [])
559 tls_keystore_password = options.fetch(:tls_keystore_password, nil)
560 tls_key_file = options.fetch(:tls_key_file, nil)
561 tls_cert_file = options.fetch(:tls_cert_file, nil)
562 tls_ca_cert_file = options.fetch(:tls_ca_cert_file, nil)
563 odl_rest_port = options.fetch(:odl_rest_port, 8181)
566 if tls_keystore_password.nil?
567 it { expect { should contain_class('opendaylight::config') }.to raise_error(Puppet::PreformattedError) }
571 if tls_key_file or tls_cert_file
572 if tls_key_file and tls_cert_file
574 should contain_odl_keystore('controller')
577 it { expect { should contain_class('opendaylight::config') }.to raise_error(Puppet::PreformattedError) }
581 should contain_augeas('Remove HTTP ODL REST Port')
582 should contain_augeas('ODL SSL REST Port')
583 should contain_file_line('set pax TLS port').with(
584 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
585 'line' => "org.osgi.service.http.port.secure = #{odl_rest_port}",
586 'match' => '^#?org.osgi.service.http.port.secure.*$',
588 should contain_file_line('set pax TLS keystore location').with(
589 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
590 'line' => 'org.ops4j.pax.web.ssl.keystore = configuration/ssl/ctl.jks',
591 'match' => '^#?org.ops4j.pax.web.ssl.keystore.*$',
593 should contain_file_line('set pax TLS keystore integrity password').with(
594 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
595 'line' => "org.ops4j.pax.web.ssl.password = #{tls_keystore_password}",
596 'match' => '^#?org.ops4j.pax.web.ssl.password.*$',
598 should contain_file_line('set pax TLS keystore password').with(
599 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
600 'line' => "org.ops4j.pax.web.ssl.keypassword = #{tls_keystore_password}",
601 'match' => '^#?org.ops4j.pax.web.ssl.keypassword.*$',
603 should contain_file('aaa-cert-config.xml').with(
605 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/aaa-cert-config.xml',
609 should contain_file('org.opendaylight.ovsdb.library.cfg').with(
611 'path' => '/opt/opendaylight/etc/org.opendaylight.ovsdb.library.cfg',
614 'content' => /use-ssl = true/
616 should contain_file('/opt/opendaylight/configuration/ssl').with(
617 'ensure' => 'directory',
618 'path' => '/opt/opendaylight/configuration/ssl',
623 should contain_file_line('enable pax TLS').with(
624 'ensure' => 'present',
625 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
626 'line' => 'org.osgi.service.http.secure.enabled = true',
627 'match' => '^#?org.osgi.service.http.secure.enabled.*$',
629 should contain_file_line('disable pax HTTP').with(
630 'ensure' => 'present',
631 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
632 'line' => 'org.osgi.service.http.enabled = false',
633 'match' => '^#?org.osgi.service.http.enabled.*$',
635 should contain_file('org.ops4j.pax.web.cfg').with(
637 'path' => '/opt/opendaylight/etc/org.ops4j.pax.web.cfg',
641 should contain_file('default-openflow-connection-config.xml').with(
643 'path' => '/opt/opendaylight/etc/opendaylight/datastore/initial/config/default-openflow-connection-config.xml',
646 'content' => /<transport-protocol>TLS<\/transport-protocol>/
652 def stats_polling_enablement_tests(options = {})
654 # NB: This default value should be the same as one in opendaylight::params
655 # TODO: Remove this possible source of bugs^^
656 stats_polling_enabled = options.fetch(:stats_polling_enabled, false)
657 # Confirm properties of ODL REST port config file
658 # NB: These hashes don't work with Ruby 1.8.7, but we
659 # don't support 1.8.7 so that's okay. See issue #36.
661 should contain_file('openflowplugin.cfg').with(
663 'path' => '/opt/opendaylight/etc/org.opendaylight.openflowplugin.cfg',
667 should contain_file_line('stats-polling').with(
668 'ensure' => 'present',
669 'path' => '/opt/opendaylight/etc/org.opendaylight.openflowplugin.cfg',
670 'line' => "is-statistics-polling-on=#{stats_polling_enabled}",
671 'match' => '^is-statistics-polling-on=.*$',