/
tests.py
1181 lines (972 loc) · 45.3 KB
/
tests.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
# Copyright 2018 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Encapsulating testing of some `neutron-*` charms.
`neutron-api`, `neutron-gateway` and `neutron-openvswitch`
"""
import copy
import logging
import tenacity
from neutronclient.common import exceptions as neutronexceptions
import yaml
import zaza
import zaza.openstack.charm_tests.nova.utils as nova_utils
import zaza.openstack.charm_tests.test_utils as test_utils
import zaza.openstack.configure.guest as guest
import zaza.openstack.utilities.openstack as openstack_utils
class NeutronPluginApiSharedTests(test_utils.OpenStackBaseTest):
"""Shared tests for Neutron Plugin API Charms."""
@classmethod
def setUpClass(cls):
"""Run class setup for running Neutron Openvswitch tests."""
super(NeutronPluginApiSharedTests, cls).setUpClass()
cls.current_os_release = openstack_utils.get_os_release()
cls.bionic_stein = openstack_utils.get_os_release('bionic_stein')
cls.trusty_mitaka = openstack_utils.get_os_release('trusty_mitaka')
if cls.current_os_release >= cls.bionic_stein:
cls.pgrep_full = True
else:
cls.pgrep_full = False
def test_211_ovs_use_veth(self):
"""Verify proper handling of ovs-use-veth setting."""
current_release = openstack_utils.get_os_release()
xenial_mitaka = openstack_utils.get_os_release('xenial_mitaka')
if current_release < xenial_mitaka:
logging.info(
"Skipping OVS use veth test. ovs_use_veth is always True on "
"Trusty.")
return
conf_file = "/etc/neutron/dhcp_agent.ini"
expected = {"DEFAULT": {"ovs_use_veth": ["False"]}}
test_config = zaza.charm_lifecycle.utils.get_charm_config(fatal=False)
states = test_config.get("target_deploy_status", {})
alt_states = copy.deepcopy(states)
alt_states[self.application_name] = {
"workload-status": "blocked",
"workload-status-message":
"Mismatched existing and configured ovs-use-veth. See log."}
if "neutron-openvswitch" in self.application_name:
logging.info("Turning on DHCP and metadata")
zaza.model.set_application_config(
self.application_name,
{"enable-local-dhcp-and-metadata": "True"})
zaza.model.wait_for_application_states(states=states)
logging.info("Check for expected default ovs-use-veth setting of "
"False")
zaza.model.block_until_oslo_config_entries_match(
self.application_name,
conf_file,
expected,
)
logging.info("Setting conflicting ovs-use-veth to True")
zaza.model.set_application_config(
self.application_name,
{"ovs-use-veth": "True"})
logging.info("Wait to go into a blocked workload status")
zaza.model.wait_for_application_states(states=alt_states)
# Check the value stayed the same
logging.info("Check that the value of ovs-use-veth setting "
"remained False")
zaza.model.block_until_oslo_config_entries_match(
self.application_name,
conf_file,
expected,
)
logging.info("Setting ovs-use-veth to match existing.")
zaza.model.set_application_config(
self.application_name,
{"ovs-use-veth": "False"})
logging.info("Wait to go into unit ready workload status")
zaza.model.wait_for_application_states(states=states)
class NeutronGatewayTest(NeutronPluginApiSharedTests):
"""Test basic Neutron Gateway Charm functionality."""
@classmethod
def setUpClass(cls):
"""Run class setup for running Neutron Gateway tests."""
super(NeutronGatewayTest, cls).setUpClass()
cls.services = cls._get_services()
# set up clients
cls.neutron_client = (
openstack_utils.get_neutron_session_client(cls.keystone_session))
_APP_NAME = 'neutron-gateway'
def test_401_enable_qos(self):
"""Check qos settings set via neutron-api charm."""
if (self.current_os_release >=
openstack_utils.get_os_release('trusty_mitaka')):
logging.info('running qos check')
with self.config_change(
{'enable-qos': 'False'},
{'enable-qos': 'True'},
application_name="neutron-api"):
self._validate_openvswitch_agent_qos()
@tenacity.retry(wait=tenacity.wait_exponential(min=5, max=60),
reraise=True, stop=tenacity.stop_after_attempt(8))
def _validate_openvswitch_agent_qos(self):
"""Validate that the qos extension is enabled in the ovs agent."""
# obtain the dhcp agent to identify the neutron-gateway host
dhcp_agent = self.neutron_client.list_agents(
binary='neutron-dhcp-agent')['agents'][0]
neutron_gw_host = dhcp_agent['host']
logging.debug('neutron gw host: {}'.format(neutron_gw_host))
# check extensions on the ovs agent to validate qos
ovs_agent = self.neutron_client.list_agents(
binary='neutron-openvswitch-agent',
host=neutron_gw_host)['agents'][0]
self.assertIn('qos', ovs_agent['configurations']['extensions'])
def test_900_restart_on_config_change(self):
"""Checking restart happens on config change.
Change debug mode and assert that change propagates to the correct
file and that services are restarted as a result
"""
# Expected default and alternate values
current_value = zaza.model.get_application_config(
self._APP_NAME)['debug']['value']
new_value = str(not bool(current_value)).title()
current_value = str(current_value).title()
set_default = {'debug': current_value}
set_alternate = {'debug': new_value}
default_entry = {'DEFAULT': {'debug': [current_value]}}
alternate_entry = {'DEFAULT': {'debug': [new_value]}}
# Config file affected by juju set config change
conf_file = '/etc/neutron/neutron.conf'
# Make config change, check for service restarts
logging.info(
'Setting verbose on {} {}'.format(self._APP_NAME, set_alternate))
self.restart_on_changed(
conf_file,
set_default,
set_alternate,
default_entry,
alternate_entry,
self.services,
pgrep_full=self.pgrep_full)
def test_910_pause_and_resume(self):
"""Run pause and resume tests.
Pause service and check services are stopped then resume and check
they are started
"""
with self.pause_resume(
self.services,
pgrep_full=self.pgrep_full):
logging.info("Testing pause resume")
def test_920_change_aa_profile(self):
"""Test changing the Apparmor profile mode."""
services = ['neutron-openvswitch-agent',
'neutron-dhcp-agent',
'neutron-l3-agent',
'neutron-metadata-agent',
'neutron-metering-agent']
set_default = {'aa-profile-mode': 'disable'}
set_alternate = {'aa-profile-mode': 'complain'}
mtime = zaza.model.get_unit_time(
self.lead_unit,
model_name=self.model_name)
logging.debug('Remote unit timestamp {}'.format(mtime))
with self.config_change(set_default, set_alternate):
for unit in zaza.model.get_units(self._APP_NAME,
model_name=self.model_name):
logging.info('Checking number of profiles in complain '
'mode in {}'.format(unit.entity_id))
run = zaza.model.run_on_unit(
unit.entity_id,
'aa-status --complaining',
model_name=self.model_name)
output = run['Stdout']
self.assertTrue(int(output) >= len(services))
@classmethod
def _get_services(cls):
"""
Return the services expected in Neutron Gateway.
:returns: A list of services
:rtype: list[str]
"""
services = ['neutron-dhcp-agent',
'neutron-metadata-agent',
'neutron-metering-agent',
'neutron-openvswitch-agent']
trusty_icehouse = openstack_utils.get_os_release('trusty_icehouse')
xenial_newton = openstack_utils.get_os_release('xenial_newton')
bionic_train = openstack_utils.get_os_release('bionic_train')
if cls.current_os_release <= trusty_icehouse:
services.append('neutron-vpn-agent')
if cls.current_os_release < xenial_newton:
services.append('neutron-lbaas-agent')
if xenial_newton <= cls.current_os_release < bionic_train:
services.append('neutron-lbaasv2-agent')
return services
class NeutronGatewayShowActionsTest(test_utils.OpenStackBaseTest):
"""Test "show" actions of Neutron Gateway Charm.
actions:
* show-routers
* show-dhcp-networks
* show-loadbalancers
"""
SKIP_LBAAS_TESTS = True
@classmethod
def setUpClass(cls, application_name='neutron-gateway', model_alias=None):
"""Run class setup for running Neutron Gateway tests."""
super(NeutronGatewayShowActionsTest, cls).setUpClass(
application_name, model_alias)
# set up clients
cls.neutron_client = (
openstack_utils.get_neutron_session_client(cls.keystone_session))
# Loadbalancer tests not supported on Train and above and on
# releases Mitaka and below
current = openstack_utils.get_os_release()
bionic_train = openstack_utils.get_os_release('bionic_train')
xenial_mitaka = openstack_utils.get_os_release('xenial_mitaka')
cls.SKIP_LBAAS_TESTS = not (xenial_mitaka > current < bionic_train)
def _assert_result_match(self, action_result, resource_list,
resource_name):
"""Assert that action_result contains same data as resource_list."""
# make sure that action completed successfully
if action_result.status != 'completed':
self.fail('Juju Action failed: {}'.format(action_result.message))
# extract data from juju action
action_data = action_result.data.get('results', {}).get(resource_name)
resources_from_action = yaml.safe_load(action_data)
# pull resource IDs from expected resource list and juju action data
expected_resource_ids = {resource['id'] for resource in resource_list}
result_resource_ids = resources_from_action.keys()
# assert that juju action returned expected resources
self.assertEqual(result_resource_ids, expected_resource_ids)
def test_show_routers(self):
"""Test that show-routers action reports correct neutron routers."""
# fetch neutron routers using neutron client
ngw_unit = zaza.model.get_units(self.application_name,
model_name=self.model_name)[0]
routers_from_client = self.neutron_client.list_routers().get(
'routers', [])
if not routers_from_client:
self.fail('At least one router must be configured for this test '
'to pass.')
# fetch neutron routers using juju-action
result = zaza.model.run_action(ngw_unit.entity_id,
'show-routers',
model_name=self.model_name)
# assert that data from neutron client match data from juju action
self._assert_result_match(result, routers_from_client, 'router-list')
def test_show_dhcp_networks(self):
"""Test that show-dhcp-networks reports correct DHCP networks."""
# fetch DHCP networks using neutron client
ngw_unit = zaza.model.get_units(self.application_name,
model_name=self.model_name)[0]
networks_from_client = self.neutron_client.list_networks().get(
'networks', [])
if not networks_from_client:
self.fail('At least one network must be configured for this test '
'to pass.')
# fetch DHCP networks using juju-action
result = zaza.model.run_action(ngw_unit.entity_id,
'show-dhcp-networks',
model_name=self.model_name)
# assert that data from neutron client match data from juju action
self._assert_result_match(result, networks_from_client,
'dhcp-networks')
def test_show_load_balancers(self):
"""Test that show-loadbalancers reports correct loadbalancers."""
if self.SKIP_LBAAS_TESTS:
self.skipTest('LBaasV2 is not supported in this version.')
loadbalancer_id = None
try:
# create LBaasV2 for the purpose of this test
lbaas_name = 'test_lbaas'
subnet_list = self.neutron_client.list_subnets(
name='private_subnet').get('subnets', [])
if not subnet_list:
raise RuntimeError('Expected subnet "private_subnet" is not '
'configured.')
subnet = subnet_list[0]
loadbalancer_data = {'loadbalancer': {'name': lbaas_name,
'vip_subnet_id': subnet['id']
}
}
loadbalancer = self.neutron_client.create_loadbalancer(
body=loadbalancer_data)
loadbalancer_id = loadbalancer['loadbalancer']['id']
# test that client and action report same data
ngw_unit = zaza.model.get_units(self.application_name,
model_name=self.model_name)[0]
lbaas_from_client = self.neutron_client.list_loadbalancers().get(
'loadbalancers', [])
result = zaza.model.run_action(ngw_unit.entity_id,
'show-load-balancers',
model_name=self.model_name)
self._assert_result_match(result, lbaas_from_client,
'load-balancers')
finally:
if loadbalancer_id:
self.neutron_client.delete_loadbalancer(loadbalancer_id)
class NeutronCreateNetworkTest(test_utils.OpenStackBaseTest):
"""Test creating a Neutron network through the API.
This is broken out into a separate class as it can be useful as standalone
tests for Neutron plugin subordinate charms.
"""
@classmethod
def setUpClass(cls):
"""Run class setup for running Neutron Gateway tests."""
super(NeutronCreateNetworkTest, cls).setUpClass()
cls.current_os_release = openstack_utils.get_os_release()
# set up clients
cls.neutron_client = (
openstack_utils.get_neutron_session_client(cls.keystone_session))
cls.neutron_client.format = 'json'
_TEST_NET_NAME = 'test_net'
def test_400_create_network(self):
"""Create a network, verify that it exists, and then delete it."""
self._wait_for_neutron_ready()
self._assert_test_network_doesnt_exist()
self._create_test_network()
net_id = self._assert_test_network_exists_and_return_id()
self._delete_test_network(net_id)
self._assert_test_network_doesnt_exist()
@classmethod
def _wait_for_neutron_ready(cls):
logging.info('Waiting for Neutron to become ready...')
zaza.model.wait_for_application_states()
for attempt in tenacity.Retrying(
wait=tenacity.wait_fixed(5), # seconds
stop=tenacity.stop_after_attempt(12),
reraise=True):
with attempt:
cls.neutron_client.list_networks()
def _create_test_network(self):
logging.info('Creating neutron network...')
network = {'name': self._TEST_NET_NAME}
self.neutron_client.create_network({'network': network})
def _delete_test_network(self, net_id):
logging.info('Deleting neutron network...')
self.neutron_client.delete_network(net_id)
def _assert_test_network_exists_and_return_id(self):
logging.debug('Confirming new neutron network...')
networks = self.neutron_client.list_networks(name=self._TEST_NET_NAME)
logging.debug('Networks: {}'.format(networks))
net_len = len(networks['networks'])
assert net_len == 1, (
"Expected 1 network, found {}".format(net_len))
network = networks['networks'][0]
assert network['name'] == self._TEST_NET_NAME, \
"network {} not found".format(self._TEST_NET_NAME)
return network['id']
def _assert_test_network_doesnt_exist(self):
networks = self.neutron_client.list_networks(name=self._TEST_NET_NAME)
net_count = len(networks['networks'])
assert net_count == 0, (
"Expected zero networks, found {}".format(net_count))
class NeutronApiTest(NeutronCreateNetworkTest):
"""Test basic Neutron API Charm functionality."""
def test_900_restart_on_config_change(self):
"""Checking restart happens on config change.
Change debug mode and assert that change propagates to the correct
file and that services are restarted as a result
"""
# Expected default and alternate values
current_value = zaza.model.get_application_config(
'neutron-api')['debug']['value']
new_value = str(not bool(current_value)).title()
current_value = str(current_value).title()
set_default = {'debug': current_value}
set_alternate = {'debug': new_value}
default_entry = {'DEFAULT': {'debug': [current_value]}}
alternate_entry = {'DEFAULT': {'debug': [new_value]}}
# Config file affected by juju set config change
conf_file = '/etc/neutron/neutron.conf'
# Make config change, check for service restarts
logging.info(
'Setting verbose on neutron-api {}'.format(set_alternate))
bionic_stein = openstack_utils.get_os_release('bionic_stein')
if openstack_utils.get_os_release() >= bionic_stein:
pgrep_full = True
else:
pgrep_full = False
self.restart_on_changed(
conf_file,
set_default,
set_alternate,
default_entry,
alternate_entry,
['neutron-server'],
pgrep_full=pgrep_full)
def test_901_pause_resume(self):
"""Run pause and resume tests.
Pause service and check services are stopped then resume and check
they are started
"""
bionic_stein = openstack_utils.get_os_release('bionic_stein')
if openstack_utils.get_os_release() >= bionic_stein:
pgrep_full = True
else:
pgrep_full = False
with self.pause_resume(
["neutron-server", "apache2", "haproxy"],
pgrep_full=pgrep_full):
logging.info("Testing pause resume")
class SecurityTest(test_utils.OpenStackBaseTest):
"""Neutron Security Tests."""
def test_security_checklist(self):
"""Verify expected state with security-checklist."""
expected_failures = []
expected_passes = [
'validate-file-ownership',
'validate-file-permissions',
]
expected_to_pass = True
# override settings depending on application name so we can reuse
# the class for multiple charms
if self.application_name == 'neutron-api':
tls_checks = [
'validate-uses-tls-for-keystone',
]
expected_failures = [
'validate-enables-tls', # LP: #1851610
]
expected_passes.append('validate-uses-keystone')
if zaza.model.get_relation_id(
'neutron-api',
'vault',
remote_interface_name='certificates'):
expected_passes.extend(tls_checks)
else:
expected_failures.extend(tls_checks)
expected_to_pass = False
for unit in zaza.model.get_units(self.application_name,
model_name=self.model_name):
logging.info('Running `security-checklist` action'
' on unit {}'.format(unit.entity_id))
test_utils.audit_assertions(
zaza.model.run_action(
unit.entity_id,
'security-checklist',
model_name=self.model_name,
action_params={}),
expected_passes,
expected_failures,
expected_to_pass=expected_to_pass)
class NeutronOpenvSwitchTest(NeutronPluginApiSharedTests):
"""Test basic Neutron Openvswitch Charm functionality."""
@classmethod
def setUpClass(cls):
"""Run class setup for running Neutron Openvswitch tests."""
super(NeutronOpenvSwitchTest, cls).setUpClass()
# set up client
cls.neutron_client = (
openstack_utils.get_neutron_session_client(cls.keystone_session))
def test_101_neutron_sriov_config(self):
"""Verify data in the sriov agent config file."""
xenial_mitaka = openstack_utils.get_os_release('xenial_mitaka')
if self.current_os_release < xenial_mitaka:
logging.debug('Skipping test, sriov agent not supported on < '
'xenial/mitaka')
return
zaza.model.set_application_config(
self.application_name,
{'enable-sriov': 'True'})
zaza.model.wait_for_agent_status()
zaza.model.wait_for_application_states()
self._check_settings_in_config(
self.application_name,
'sriov-device-mappings',
'physical_device_mappings',
['', 'physnet42:eth42'],
'sriov_nic',
'/etc/neutron/plugins/ml2/sriov_agent.ini')
# the CI environment does not expose an actual SR-IOV NIC to the
# functional tests. consequently the neutron-sriov agent will not
# run, and the charm will update its status as such. this will prevent
# the success of pause/resume test.
#
# disable sriov after validation of config file is complete.
logging.info('Disabling SR-IOV after verifying config file data...')
zaza.model.set_application_config(
self.application_name,
{'enable-sriov': 'False'})
logging.info('Waiting for config-changes to complete...')
zaza.model.wait_for_agent_status()
zaza.model.wait_for_application_states()
logging.debug('OK')
def _check_settings_in_config(self, service, charm_key,
config_file_key, vpair,
section, conf_file):
set_default = {charm_key: vpair[0]}
set_alternate = {charm_key: vpair[1]}
app_name = service
expected = {
section: {
config_file_key: [str(vpair[1])],
},
}
with self.config_change(set_default,
set_alternate,
application_name=app_name):
zaza.model.block_until_oslo_config_entries_match(
self.application_name,
conf_file,
expected,
)
logging.debug('OK')
def test_201_l2pop_propagation(self):
"""Verify that l2pop setting propagates to neutron-ovs."""
self._check_settings_in_config(
'neutron-api',
'l2-population',
'l2_population',
[False, True],
'agent',
'/etc/neutron/plugins/ml2/openvswitch_agent.ini')
def test_202_nettype_propagation(self):
"""Verify that nettype setting propagates to neutron-ovs."""
self._check_settings_in_config(
'neutron-api',
'overlay-network-type',
'tunnel_types',
['vxlan', 'gre'],
'agent',
'/etc/neutron/plugins/ml2/openvswitch_agent.ini')
def test_301_secgroup_propagation_local_override(self):
"""Verify disable-security-groups overrides what neutron-api says."""
if self.current_os_release >= self.trusty_mitaka:
conf_file = "/etc/neutron/plugins/ml2/openvswitch_agent.ini"
else:
conf_file = "/etc/neutron/plugins/ml2/ml2_conf.ini"
with self.config_change(
{'neutron-security-groups': False},
{'neutron-security-groups': True},
application_name='neutron-api'):
with self.config_change(
{'disable-security-groups': False},
{'disable-security-groups': True}):
zaza.model.block_until_oslo_config_entries_match(
self.application_name,
conf_file,
{'securitygroup': {'enable_security_group': ['False']}})
def test_401_restart_on_config_change(self):
"""Verify that the specified services are restarted.
When the config is changed we need to make sure that the services are
restarted.
"""
self.restart_on_changed(
'/etc/neutron/neutron.conf',
{'debug': False},
{'debug': True},
{'DEFAULT': {'debug': ['False']}},
{'DEFAULT': {'debug': ['True']}},
['neutron-openvswitch-agent'],
pgrep_full=self.pgrep_full)
def test_501_enable_qos(self):
"""Check qos settings set via neutron-api charm."""
if self.current_os_release < self.trusty_mitaka:
logging.debug('Skipping test')
return
set_default = {'enable-qos': False}
set_alternate = {'enable-qos': True}
app_name = 'neutron-api'
conf_file = '/etc/neutron/plugins/ml2/openvswitch_agent.ini'
expected = {
'agent': {
'extensions': ['qos'],
},
}
with self.config_change(set_default,
set_alternate,
application_name=app_name):
zaza.model.block_until_oslo_config_entries_match(
self.application_name,
conf_file,
expected,
)
logging.debug('OK')
def test_901_pause_and_resume(self):
"""Run pause and resume tests.
Pause service and check services are stopped then resume and check
they are started
"""
with self.pause_resume(['neutron-openvswitch-agent'],
pgrep_full=self.pgrep_full):
logging.info('Testing pause resume')
class NeutronBridgePortMappingTest(NeutronPluginApiSharedTests):
"""Test correct handling of network-bridge-port mapping functionality."""
def test_600_conflict_data_ext_ports(self):
"""Verify proper handling of conflict between data-port and ext-port.
Configuring ext-port and data-port at the same time should make the
charm to enter "blocked" state. After unsetting ext-port charm should
be active again.
"""
if self.application_name not in ["neutron-gateway",
"neutron-openvswitch"]:
logging.debug("Skipping test, charm under test is not "
"neutron-gateway or neutron-openvswitch")
return
current_data_port = zaza.model.get_application_config(
self.application_name).get("data-port").get("value", "")
current_ext_port = zaza.model.get_application_config(
self.application_name).get("ext-port").get("value", "")
logging.debug("Current data-port: '{}'".format(current_data_port))
logging.debug("Current data-port: '{}'".format(current_ext_port))
test_config = zaza.charm_lifecycle.utils.get_charm_config(
fatal=False)
current_state = test_config.get("target_deploy_status", {})
blocked_state = copy.deepcopy(current_state)
blocked_state[self.application_name] = {
"workload-status": "blocked",
"workload-status-message":
"ext-port set when data-port set: see config.yaml"}
logging.info("Setting conflicting ext-port and data-port options")
zaza.model.set_application_config(
self.application_name, {"data-port": "br-phynet43:eth43",
"ext-port": "br-phynet43:eth43"})
zaza.model.wait_for_application_states(states=blocked_state)
# unset ext-port and wait for app state to return to active
logging.info("Unsetting conflicting ext-port option")
zaza.model.set_application_config(
self.application_name, {"ext-port": ""})
zaza.model.wait_for_application_states(states=current_state)
# restore original config
zaza.model.set_application_config(
self.application_name, {'data-port': current_data_port,
'ext-port': current_ext_port})
zaza.model.wait_for_application_states(states=current_state)
logging.info('OK')
class NeutronOvsVsctlTest(NeutronPluginApiSharedTests):
"""Test 'ovs-vsctl'-related functionality on Neutron charms."""
def test_800_ovs_bridges_are_managed_by_us(self):
"""Checking OVS bridges' external-id.
OVS bridges created by us should be marked as managed by us in their
external-id. See
http://docs.openvswitch.org/en/latest/topics/integration/
"""
for unit in zaza.model.get_units(self.application_name,
model_name=self.model_name):
for bridge_name in ('br-int', 'br-ex'):
logging.info(
'Checking that the bridge {}:{}'.format(
unit.name, bridge_name
) + ' is marked as managed by us'
)
expected_external_id = 'charm-{}=managed'.format(
self.application_name)
actual_external_id = zaza.model.run_on_unit(
unit.entity_id,
'ovs-vsctl br-get-external-id {}'.format(bridge_name),
model_name=self.model_name
)['Stdout'].strip()
self.assertEqual(actual_external_id, expected_external_id)
class NeutronNetworkingBase(test_utils.OpenStackBaseTest):
"""Base for checking openstack instances have valid networking."""
RESOURCE_PREFIX = 'zaza-neutrontests'
@classmethod
def setUpClass(cls):
"""Run class setup for running Neutron API Networking tests."""
super(NeutronNetworkingBase, cls).setUpClass(
application_name='neutron-api')
cls.neutron_client = (
openstack_utils.get_neutron_session_client(cls.keystone_session))
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
reraise=True, stop=tenacity.stop_after_attempt(8))
def validate_instance_can_reach_other(self,
instance_1,
instance_2,
verify,
mtu=None):
"""
Validate that an instance can reach a fixed and floating of another.
:param instance_1: The instance to check networking from
:type instance_1: nova_client.Server
:param instance_2: The instance to check networking from
:type instance_2: nova_client.Server
:param verify: callback to verify result
:type verify: callable
:param mtu: Check that we can send non-fragmented packets of given size
:type mtu: Optional[int]
"""
floating_1 = floating_ips_from_instance(instance_1)[0]
floating_2 = floating_ips_from_instance(instance_2)[0]
address_2 = fixed_ips_from_instance(instance_2)[0]
username = guest.boot_tests['bionic']['username']
password = guest.boot_tests['bionic'].get('password')
privkey = openstack_utils.get_private_key(nova_utils.KEYPAIR_NAME)
cmds = [
'ping -c 1',
]
if mtu:
# the on-wire packet will be 28 bytes larger than the value
# provided to ping(8) -s parameter
packetsize = mtu - 28
cmds.append(
'ping -M do -s {} -c 1'.format(packetsize))
for cmd in cmds:
openstack_utils.ssh_command(
username, floating_1, 'instance-1',
'{} {}'.format(cmd, address_2),
password=password, privkey=privkey, verify=verify)
openstack_utils.ssh_command(
username, floating_1, 'instance-1',
'{} {}'.format(cmd, floating_2),
password=password, privkey=privkey, verify=verify)
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60),
reraise=True, stop=tenacity.stop_after_attempt(8))
def validate_instance_can_reach_router(self, instance, verify, mtu=None):
"""
Validate that an instance can reach it's primary gateway.
We make the assumption that the router's IP is 192.168.0.1
as that's the network that is setup in
neutron.setup.basic_overcloud_network which is used in all
Zaza Neutron validations.
:param instance: The instance to check networking from
:type instance: nova_client.Server
:param verify: callback to verify result
:type verify: callable
:param mtu: Check that we can send non-fragmented packets of given size
:type mtu: Optional[int]
"""
address = floating_ips_from_instance(instance)[0]
username = guest.boot_tests['bionic']['username']
password = guest.boot_tests['bionic'].get('password')
privkey = openstack_utils.get_private_key(nova_utils.KEYPAIR_NAME)
cmds = [
'ping -c 1',
]
if mtu:
# the on-wire packet will be 28 bytes larger than the value
# provided to ping(8) -s parameter
packetsize = mtu - 28
cmds.append(
'ping -M do -s {} -c 1'.format(packetsize))
for cmd in cmds:
openstack_utils.ssh_command(
username, address, 'instance', '{} 192.168.0.1'.format(cmd),
password=password, privkey=privkey, verify=verify)
@tenacity.retry(wait=tenacity.wait_exponential(min=5, max=60),
reraise=True, stop=tenacity.stop_after_attempt(8),
retry=tenacity.retry_if_exception_type(AssertionError))
def check_server_state(self, nova_client, state, server_id=None,
server_name=None):
"""Wait for server to reach desired state.
:param nova_client: Nova client to use when checking status
:type nova_client: nova client
:param state: Target state for server
:type state: str
:param server_id: UUID of server to check
:type server_id: str
:param server_name: Name of server to check
:type server_name: str
:raises: AssertionError
"""
if server_name:
server_id = nova_client.servers.find(name=server_name).id
server = nova_client.servers.find(id=server_id)
assert server.status == state
@tenacity.retry(wait=tenacity.wait_exponential(min=5, max=60),
reraise=True, stop=tenacity.stop_after_attempt(8),
retry=tenacity.retry_if_exception_type(AssertionError))
def check_neutron_agent_up(self, neutron_client, host_name):
"""Wait for agents to come up.
:param neutron_client: Neutron client to use when checking status
:type neutron_client: neutron client
:param host_name: The name of the host whose agents need checking
:type host_name: str
:raises: AssertionError
"""
for agent in neutron_client.list_agents()['agents']:
if agent['host'] == host_name:
assert agent['admin_state_up']
assert agent['alive']
def effective_network_mtu(self, network_name):
"""Retrieve effective MTU for a network.
If the `instance-mtu` configuration option is set to a value lower than
the network MTU this method will return the value of that. Otherwise
Neutron's value for MTU on a network will be returned.
:param network_name: Name of network to query
:type network_name: str
:returns: MTU for network
:rtype: int
"""
cfg_instance_mtu = None
for app in ('neutron-gateway', 'neutron-openvswitch'):
try:
cfg = zaza.model.get_application_config(app)
cfg_instance_mtu = int(cfg['instance-mtu']['value'])
break
except KeyError:
pass
networks = self.neutron_client.show_network('', name=network_name)
network_mtu = int(next(iter(networks['networks']))['mtu'])
if cfg_instance_mtu and cfg_instance_mtu < network_mtu:
logging.info('Using MTU from application "{}" config: {}'
.format(app, cfg_instance_mtu))
return cfg_instance_mtu
else:
logging.info('Using MTU from network "{}": {}'
.format(network_name, network_mtu))
return network_mtu
def check_connectivity(self, instance_1, instance_2):
"""Run North/South and East/West connectivity tests."""
def verify(stdin, stdout, stderr):
"""Validate that the SSH command exited 0."""
self.assertEqual(stdout.channel.recv_exit_status(), 0)
try:
mtu_1 = self.effective_network_mtu(
network_name_from_instance(instance_1))
mtu_2 = self.effective_network_mtu(
network_name_from_instance(instance_2))
mtu_min = min(mtu_1, mtu_2)
except neutronexceptions.NotFound:
# Older versions of OpenStack cannot look up network by name, just
# skip the check if that is the case.
mtu_1 = mtu_2 = mtu_min = None
# Verify network from 1 to 2
self.validate_instance_can_reach_other(