Fix bind IP config for REST
[integration/packaging/puppet-opendaylight.git] / spec / spec_helper_acceptance.rb
1 require 'beaker-rspec/spec_helper'
2 require 'beaker-rspec/helpers/serverspec'
3
4 # Install Puppet on all Beaker hosts
5 unless ENV['BEAKER_provision'] == 'no'
6   hosts.each do |host|
7     # Install Puppet
8     install_puppet_agent_on(host, puppet_collection: "pc1")
9   end
10 end
11
12 RSpec.configure do |c|
13   # Project root
14   proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
15
16   # Readable test descriptions
17   c.formatter = :documentation
18
19   # Configure all nodes in nodeset
20   c.before :suite do
21     # Install opendaylight module on any/all Beaker hosts
22     # TODO: Should this be done in host.each loop?
23     puppet_module_install(:source => proj_root, :module_name => 'opendaylight')
24     hosts.each do |host|
25       # Install stdlib, a dependency of the odl mod
26       on host, puppet('module', 'install', 'puppetlabs-stdlib'), { :acceptable_exit_codes => [0] }
27       # Install apt, a dependency of the deb install method
28       on host, puppet('module', 'install', 'puppetlabs-apt'), { :acceptable_exit_codes => [0] }
29     end
30   end
31 end
32
33 #
34 # NB: These are a library of helper fns used by the Beaker tests
35 #
36
37 # NB: There are a large number of helper functions used in these tests.
38 # They make this code much more friendly, but may need to be referenced.
39 # The serverspec helpers (`should`, `be_running`...) are documented here:
40 #   http://serverspec.org/resource_types.html
41
42 def install_odl(options = {})
43   # Install params are passed via environment var, set in Rakefile
44   # Changing the installed version of ODL via `puppet apply` is not supported
45   # by puppet-odl, so it's not possible to vary these params in the same
46   # Beaker test run. Do a different run passing different env vars.
47   rpm_repo = ENV['RPM_REPO']
48   deb_repo = ENV['DEB_REPO']
49
50   # NB: These param defaults should match the ones used by the opendaylight
51   #   class, which are defined in opendaylight::params
52   # TODO: Remove this possible source of bugs^^
53   # Extract params if given, defaulting to odl class defaults if not
54   extra_features = options.fetch(:extra_features, [])
55   default_features = options.fetch(:default_features,
56     ['config', 'standard', 'region', 'package', 'kar', 'ssh', 'management'])
57   odl_rest_port = options.fetch(:odl_rest_port, 8080)
58   odl_bind_ip = options.fetch(:odl_bind_ip, '0.0.0.0')
59   log_levels = options.fetch(:log_levels, {})
60   enable_ha = options.fetch(:enable_ha, false)
61   ha_node_ips = options.fetch(:ha_node_ips, [])
62   ha_node_index = options.fetch(:ha_node_index, 0)
63   ha_db_modules = options.fetch(:ha_db_modules, { 'default' => false })
64   username = options.fetch(:username, 'admin')
65   password = options.fetch(:password, 'admin')
66   log_max_size = options.fetch(:log_max_size, '10GB')
67   log_max_rollover = options.fetch(:log_max_rollover, 2)
68   snat_mechanism = options.fetch(:snat_mechanism, 'controller')
69
70   # Build script for consumption by Puppet apply
71   it 'should work idempotently with no errors' do
72     pp = <<-EOS
73     class { 'opendaylight':
74       rpm_repo => '#{rpm_repo}',
75       deb_repo => '#{deb_repo}',
76       default_features => #{default_features},
77       extra_features => #{extra_features},
78       odl_rest_port => #{odl_rest_port},
79       odl_bind_ip => '#{odl_bind_ip}',
80       enable_ha => #{enable_ha},
81       ha_node_ips => #{ha_node_ips},
82       ha_node_index => #{ha_node_index},
83       ha_db_modules => #{ha_db_modules},
84       log_levels => #{log_levels},
85       username => #{username},
86       password => #{password},
87       log_max_size => '#{log_max_size}',
88       log_max_rollover => #{log_max_rollover},
89       snat_mechanism => #{snat_mechanism},
90     }
91     EOS
92
93     # Apply our Puppet manifest on the Beaker host
94     apply_manifest(pp, :catch_failures => true)
95
96     # Not checking for idempotence because of false failures
97     # related to package manager cache updates outputting to
98     # stdout and different IDs for the puppet manifest apply.
99     # I think this is a limitation in how Beaker can check
100     # for changes, not a problem with the Puppet module.
101     end
102 end
103
104 # Shared function that handles generic validations
105 # These should be common for all odl class param combos
106 def generic_validations()
107   # Verify ODL's directory
108   describe file('/opt/opendaylight/') do
109     it { should be_directory }
110     it { should be_owned_by 'odl' }
111     it { should be_grouped_into 'odl' }
112   end
113
114   # Verify ODL's systemd service
115   describe service('opendaylight') do
116     it { should be_enabled }
117     it { should be_enabled.with_level(3) }
118     it { should be_running.under('systemd') }
119   end
120
121   # Creation handled by RPM or Deb
122   describe user('odl') do
123     it { should exist }
124     it { should belong_to_group 'odl' }
125     # NB: This really shouldn't have a slash at the end!
126     #     The home dir set by the RPM is `/opt/opendaylight`.
127     #     Since we use the trailing slash elsewhere else, this
128     #     may look like a style issue. It isn't! It will make
129     #     Beaker tests fail if it ends with a `/`. A future
130     #     version of the ODL RPM may change this.
131     it { should have_home_directory '/opt/opendaylight' }
132   end
133
134   # Creation handled by RPM or Deb
135   describe group('odl') do
136     it { should exist }
137   end
138
139   # This should not be the odl user's home dir
140   describe file('/home/odl') do
141     # Home dir shouldn't be created for odl user
142     it { should_not be_directory }
143   end
144
145   # OpenDaylight will appear as a Java process
146   describe process('java') do
147     it { should be_running }
148   end
149
150   # Should contain Karaf features config file
151   describe file('/opt/opendaylight/etc/org.apache.karaf.features.cfg') do
152     it { should be_file }
153     it { should be_owned_by 'odl' }
154     it { should be_grouped_into 'odl' }
155   end
156
157   # Should contain ODL NB port config file
158   describe file('/opt/opendaylight/etc/jetty.xml') do
159     it { should be_file }
160     it { should be_owned_by 'odl' }
161     it { should be_grouped_into 'odl' }
162   end
163
164   # Should contain log level config file
165   describe file('/opt/opendaylight/etc/org.ops4j.pax.logging.cfg') do
166     it { should be_file }
167     it { should be_owned_by 'odl' }
168     it { should be_grouped_into 'odl' }
169   end
170
171   if ['centos-7', 'centos-7-docker'].include? ENV['RS_SET']
172     # Validations for modern Red Hat family OSs
173
174     # Verify ODL systemd .service file
175     describe file('/usr/lib/systemd/system/opendaylight.service') do
176       it { should be_file }
177       it { should be_owned_by 'root' }
178       it { should be_grouped_into 'root' }
179       it { should be_mode '644' }
180     end
181
182     # Java 8 should be installed
183     describe package('java-1.8.0-openjdk') do
184       it { should be_installed }
185     end
186
187   # Ubuntu 16.04 specific validation
188   elsif ['ubuntu-16', 'ubuntu-16-docker'].include? ENV['RS_SET']
189
190     # Verify ODL systemd .service file
191     describe file('/lib/systemd/system/opendaylight.service') do
192       it { should be_file }
193       it { should be_owned_by 'root' }
194       it { should be_grouped_into 'root' }
195       it { should be_mode '644' }
196     end
197
198     # Java 8 should be installed
199     describe package('openjdk-8-jre-headless') do
200       it { should be_installed }
201     end
202
203   else
204     fail("Unexpected RS_SET (host OS): #{ENV['RS_SET']}")
205   end
206 end
207
208 # Shared function for validations related to log file settings
209 def log_file_settings_validations(options = {})
210   # Should contain log level config file with correct file size and rollover values
211   log_max_size = options.fetch(:log_max_size, '10GB')
212   log_max_rollover = options.fetch(:log_max_rollover, 2)
213
214   describe file('/opt/opendaylight/etc/org.ops4j.pax.logging.cfg') do
215     it { should be_file }
216     it { should be_owned_by 'odl' }
217     it { should be_grouped_into 'odl' }
218     its(:content) { should match /^log4j.appender.out.maxFileSize=#{log_max_size}/ }
219     its(:content) { should match /^log4j.appender.out.maxBackupIndex=#{log_max_rollover}/ }
220   end
221 end
222
223 # Shared function for validations related to the Karaf config file
224 def karaf_config_validations(options = {})
225   # NB: These param defaults should match the ones used by the opendaylight
226   #   class, which are defined in opendaylight::params
227   # TODO: Remove this possible source of bugs^^
228   extra_features = options.fetch(:extra_features, [])
229   default_features = options.fetch(:default_features, ['config', 'standard', 'region',
230                                   'package', 'kar', 'ssh', 'management'])
231
232   # Create one list of all of the features
233   features = default_features + extra_features
234
235   describe file('/opt/opendaylight/etc/org.apache.karaf.features.cfg') do
236     it { should be_file }
237     it { should be_owned_by 'odl' }
238     it { should be_grouped_into 'odl' }
239     its(:content) { should match /^featuresBoot=#{features.join(",")}/ }
240   end
241 end
242
243 # Shared function for validations related to the ODL REST port config file
244 def port_config_validations(options = {})
245   # NB: This param default should match the one used by the opendaylight
246   #   class, which is defined in opendaylight::params
247   # TODO: Remove this possible source of bugs^^
248   odl_rest_port = options.fetch(:odl_rest_port, 8080)
249
250   describe file('/opt/opendaylight/etc/jetty.xml') do
251     it { should be_file }
252     it { should be_owned_by 'odl' }
253     it { should be_grouped_into 'odl' }
254     its(:content) { should match /Property name="jetty.port" default="#{odl_rest_port}"/ }
255   end
256 end
257
258 # Shared function for validations related to custom logging verbosity
259 def log_level_validations(options = {})
260   # NB: This param default should match the one used by the opendaylight
261   #   class, which is defined in opendaylight::params
262   # TODO: Remove this possible source of bugs^^
263   log_levels = options.fetch(:log_levels, {})
264
265   if log_levels.empty?
266     # Should contain log level config file
267     describe file('/opt/opendaylight/etc/org.ops4j.pax.logging.cfg') do
268       it { should be_file }
269       it { should be_owned_by 'odl' }
270       it { should be_grouped_into 'odl' }
271     end
272   else
273     # Should contain log level config file
274     describe file('/opt/opendaylight/etc/org.ops4j.pax.logging.cfg') do
275       it { should be_file }
276       it { should be_owned_by 'odl' }
277       it { should be_grouped_into 'odl' }
278     end
279     # Verify each custom log level config entry
280     log_levels.each_pair do |logger, level|
281       describe file('/opt/opendaylight/etc/org.ops4j.pax.logging.cfg') do
282         it { should be_file }
283         it { should be_owned_by 'odl' }
284         it { should be_grouped_into 'odl' }
285         its(:content) { should match /^log4j.logger.#{logger}=#{level}/ }
286       end
287     end
288   end
289 end
290
291 # Shared function for validations related to ODL OVSDB HA config
292 def enable_ha_validations(options = {})
293   # NB: This param default should match the one used by the opendaylight
294   #   class, which is defined in opendaylight::params
295   # TODO: Remove this possible source of bugs^^
296   enable_ha = options.fetch(:enable_ha, false)
297   ha_node_ips = options.fetch(:ha_node_ips, [])
298   odl_bind_ip = options.fetch(:odl_bind_ip, '0.0.0.0')
299   ha_db_modules = options.fetch(:ha_db_modules, { 'default' => false })
300   # HA_NODE_IPS size
301   ha_node_count = ha_node_ips.size
302
303   if (enable_ha) && (ha_node_count < 2)
304     # Check for HA_NODE_COUNT < 2
305     fail("Number of HA nodes less than 2: #{ha_node_count} and HA Enabled")
306   end
307
308   if enable_ha
309     ha_node_index = ha_node_ips.index(odl_bind_ip)
310     describe file('/opt/opendaylight/configuration/initial/akka.conf') do
311       it { should be_file }
312       it { should be_owned_by 'odl' }
313       it { should be_grouped_into 'odl' }
314       its(:content) { should match /roles\s*=\s*\["member-#{ha_node_index}"\]/ }
315     end
316
317     ha_db_modules.each do |mod, urn|
318       describe file('/opt/opendaylight/configuration/initial/module-shards.conf') do
319         it { should be_file }
320         it { should be_owned_by 'odl' }
321         it { should be_grouped_into 'odl' }
322         its(:content) { should match /name = "#{mod}"/ }
323       end
324
325       if mod == 'default'
326         describe file('/opt/opendaylight/configuration/initial/modules.conf') do
327           it { should be_file }
328           it { should be_owned_by 'odl' }
329           it { should be_grouped_into 'odl' }
330         end
331       else
332         describe file('/opt/opendaylight/configuration/initial/modules.conf') do
333           it { should be_file }
334           it { should be_owned_by 'odl' }
335           it { should be_grouped_into 'odl' }
336           its(:content) { should match /name = "#{mod}"/ }
337           its(:content) { should match /namespace = "#{urn}"/ }
338         end
339       end
340     end
341   end
342 end
343
344 # Shared function that handles validations specific to RPM-type installs
345 def rpm_validations()
346   rpm_repo = ENV['RPM_REPO']
347
348   describe yumrepo(rpm_repo) do
349     it { should exist }
350     it { should be_enabled }
351   end
352
353   describe package('opendaylight') do
354     it { should be_installed }
355   end
356 end
357
358 # Shared function that handles validations specific to Deb-type installs
359 def deb_validations()
360   deb_repo = ENV['DEB_REPO']
361   # Check ppa
362   # Docs: http://serverspec.org/resource_types.html#ppa
363   describe ppa(deb_repo) do
364     it { should exist }
365     it { should be_enabled }
366   end
367
368   describe package('opendaylight') do
369     it { should be_installed }
370   end
371 end
372
373 # Shared function for validations related to username/password
374 def username_password_validations(options = {})
375   # NB: This param default should match the one used by the opendaylight
376   #   class, which is defined in opendaylight::params
377   # TODO: Remove this possible source of bugs^^
378   odl_username = options.fetch(:username, 'admin')
379   odl_password = options.fetch(:password, 'admin')
380   odl_check_url = 'http://127.0.0.1:8080/restconf'
381
382   describe file('/opt/opendaylight/data/idmlight.db.mv.db') do
383     it { should be_file }
384   end
385
386   describe command("sleep 180 && curl -o /dev/null --fail --silent --head -u #{odl_username}:#{odl_password} #{odl_check_url}") do
387     its(:exit_status) { should eq 0 }
388   end
389 end
390
391 # Shared function for validations related to the SNAT config file
392 def snat_mechanism_validations(options = {})
393   # NB: This param default should match the one used by the opendaylight
394   #   class, which is defined in opendaylight::params
395   # TODO: Remove this possible source of bugs^^
396   snat_mechanism = options.fetch(:snat_mechanism, 'controller')
397
398   describe file('/opt/opendaylight/etc/opendaylight/datastore/initial/config/netvirt-natservice-config.xml') do
399     it { should be_file }
400     it { should be_owned_by 'odl' }
401     it { should be_grouped_into 'odl' }
402     its(:content) { should match /<nat-mode>#{snat_mechanism}<\/nat-mode>/ }
403   end
404 end
405
406 # Shared function for validations related to SFC
407 def sfc_validations()
408   # NB: This param default should match the one used by the opendaylight
409   #   class, which is defined in opendaylight::params
410   # TODO: Remove this possible source of bugs^^
411
412   describe file('/opt/opendaylight/etc/opendaylight/datastore/initial/config/netvirt-elanmanager-config.xml') do
413     it { should be_file }
414     it { should be_owned_by 'odl' }
415     it { should be_grouped_into 'odl' }
416     its(:content) { should match /<use-of-tunnels>true<\/use-of-tunnels>/ }
417   end
418
419   describe file('/opt/opendaylight/etc/opendaylight/datastore/initial/config/genius-itm-config.xml') do
420     it { should be_file }
421     it { should be_owned_by 'odl' }
422     it { should be_grouped_into 'odl' }
423     its(:content) { should match /<gpe-extension-enabled>true<\/gpe-extension-enabled>/ }
424   end
425 end