octavia 14.0.0__py3-none-any.whl → 14.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. octavia/amphorae/backends/agent/api_server/keepalivedlvs.py +9 -0
  2. octavia/amphorae/backends/agent/api_server/osutils.py +4 -2
  3. octavia/amphorae/backends/agent/api_server/plug.py +5 -4
  4. octavia/amphorae/backends/agent/api_server/server.py +3 -2
  5. octavia/amphorae/backends/agent/api_server/util.py +35 -2
  6. octavia/amphorae/backends/utils/interface.py +2 -37
  7. octavia/amphorae/backends/utils/interface_file.py +23 -10
  8. octavia/amphorae/backends/utils/nftable_utils.py +33 -11
  9. octavia/amphorae/drivers/keepalived/jinja/jinja_cfg.py +0 -1
  10. octavia/amphorae/drivers/keepalived/jinja/templates/keepalived_base.template +0 -1
  11. octavia/api/common/pagination.py +1 -1
  12. octavia/api/v2/controllers/health_monitor.py +3 -1
  13. octavia/api/v2/controllers/load_balancer.py +7 -0
  14. octavia/api/v2/controllers/member.py +12 -2
  15. octavia/common/clients.py +7 -1
  16. octavia/common/constants.py +3 -3
  17. octavia/controller/worker/v2/controller_worker.py +2 -2
  18. octavia/controller/worker/v2/flows/amphora_flows.py +14 -3
  19. octavia/controller/worker/v2/flows/flow_utils.py +6 -4
  20. octavia/controller/worker/v2/flows/listener_flows.py +17 -5
  21. octavia/controller/worker/v2/tasks/database_tasks.py +10 -6
  22. octavia/controller/worker/v2/tasks/network_tasks.py +12 -13
  23. octavia/db/base_models.py +16 -4
  24. octavia/network/drivers/neutron/allowed_address_pairs.py +3 -2
  25. octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py +1 -1
  26. octavia/tests/functional/api/v2/test_health_monitor.py +18 -0
  27. octavia/tests/functional/api/v2/test_member.py +32 -0
  28. octavia/tests/unit/amphorae/backends/agent/api_server/test_osutils.py +1 -1
  29. octavia/tests/unit/amphorae/backends/agent/api_server/test_plug.py +4 -3
  30. octavia/tests/unit/amphorae/backends/agent/api_server/test_util.py +89 -1
  31. octavia/tests/unit/amphorae/backends/utils/test_interface.py +3 -64
  32. octavia/tests/unit/amphorae/backends/utils/test_nftable_utils.py +28 -22
  33. octavia/tests/unit/amphorae/drivers/keepalived/jinja/test_jinja_cfg.py +0 -4
  34. octavia/tests/unit/api/common/test_pagination.py +78 -1
  35. octavia/tests/unit/cmd/test_prometheus_proxy.py +8 -1
  36. octavia/tests/unit/controller/worker/v2/flows/test_listener_flows.py +10 -15
  37. octavia/tests/unit/controller/worker/v2/flows/test_load_balancer_flows.py +4 -6
  38. octavia/tests/unit/controller/worker/v2/tasks/test_database_tasks.py +28 -6
  39. octavia/tests/unit/controller/worker/v2/tasks/test_network_tasks.py +57 -2
  40. octavia/tests/unit/controller/worker/v2/test_controller_worker.py +56 -1
  41. octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py +2 -1
  42. {octavia-14.0.0.dist-info → octavia-14.0.1.dist-info}/AUTHORS +5 -0
  43. octavia-14.0.1.dist-info/METADATA +156 -0
  44. {octavia-14.0.0.dist-info → octavia-14.0.1.dist-info}/RECORD +59 -59
  45. {octavia-14.0.0.dist-info → octavia-14.0.1.dist-info}/WHEEL +1 -1
  46. {octavia-14.0.0.dist-info → octavia-14.0.1.dist-info}/entry_points.txt +0 -1
  47. octavia-14.0.1.dist-info/pbr.json +1 -0
  48. octavia-14.0.0.dist-info/METADATA +0 -158
  49. octavia-14.0.0.dist-info/pbr.json +0 -1
  50. {octavia-14.0.0.data → octavia-14.0.1.data}/data/share/octavia/LICENSE +0 -0
  51. {octavia-14.0.0.data → octavia-14.0.1.data}/data/share/octavia/README.rst +0 -0
  52. {octavia-14.0.0.data → octavia-14.0.1.data}/data/share/octavia/diskimage-create/README.rst +0 -0
  53. {octavia-14.0.0.data → octavia-14.0.1.data}/data/share/octavia/diskimage-create/diskimage-create.sh +0 -0
  54. {octavia-14.0.0.data → octavia-14.0.1.data}/data/share/octavia/diskimage-create/image-tests.sh +0 -0
  55. {octavia-14.0.0.data → octavia-14.0.1.data}/data/share/octavia/diskimage-create/requirements.txt +0 -0
  56. {octavia-14.0.0.data → octavia-14.0.1.data}/data/share/octavia/diskimage-create/test-requirements.txt +0 -0
  57. {octavia-14.0.0.data → octavia-14.0.1.data}/data/share/octavia/diskimage-create/tox.ini +0 -0
  58. {octavia-14.0.0.data → octavia-14.0.1.data}/data/share/octavia/diskimage-create/version.txt +0 -0
  59. {octavia-14.0.0.data → octavia-14.0.1.data}/scripts/octavia-wsgi +0 -0
  60. {octavia-14.0.0.dist-info → octavia-14.0.1.dist-info}/LICENSE +0 -0
  61. {octavia-14.0.0.dist-info → octavia-14.0.1.dist-info}/top_level.txt +0 -0
@@ -536,11 +536,10 @@ class PlugVIPAmphora(BaseNetworkTask):
536
536
  """Handle a failure to plumb a vip."""
537
537
  if isinstance(result, failure.Failure):
538
538
  return
539
+ lb_id = loadbalancer[constants.LOADBALANCER_ID]
539
540
  LOG.warning("Unable to plug VIP for amphora id %s "
540
541
  "load balancer id %s",
541
- amphora.get(constants.ID),
542
- loadbalancer[constants.LOADBALANCER_ID])
543
-
542
+ amphora.get(constants.ID), lb_id)
544
543
  try:
545
544
  session = db_apis.get_session()
546
545
  with session.begin():
@@ -550,15 +549,16 @@ class PlugVIPAmphora(BaseNetworkTask):
550
549
  db_amp.ha_port_id = result[constants.HA_PORT_ID]
551
550
  db_subnet = self.network_driver.get_subnet(
552
551
  subnet[constants.ID])
553
- db_lb = self.loadbalancer_repo.get(
554
- session,
555
- id=loadbalancer[constants.LOADBALANCER_ID])
556
-
552
+ db_lb = self.loadbalancer_repo.get(session, id=lb_id)
557
553
  self.network_driver.unplug_aap_port(db_lb.vip,
558
554
  db_amp, db_subnet)
559
555
  except Exception as e:
560
- LOG.error('Failed to unplug AAP port. Resources may still be in '
561
- 'use for VIP: %s due to error: %s', db_lb.vip, str(e))
556
+ LOG.error(
557
+ 'Failed to unplug AAP port for load balancer: %s. '
558
+ 'Resources may still be in use for VRRP port: %s. '
559
+ 'Due to error: %s',
560
+ lb_id, result[constants.VRRP_PORT_ID], str(e)
561
+ )
562
562
 
563
563
 
564
564
  class UnplugVIP(BaseNetworkTask):
@@ -984,10 +984,9 @@ class CreateVIPBasePort(BaseNetworkTask):
984
984
  return
985
985
  try:
986
986
  port_name = constants.AMP_BASE_PORT_PREFIX + amphora_id
987
- for port in result:
988
- self.network_driver.delete_port(port.id)
989
- LOG.info('Deleted port %s with ID %s for amphora %s due to a '
990
- 'revert.', port_name, port.id, amphora_id)
987
+ self.network_driver.delete_port(result[constants.ID])
988
+ LOG.info('Deleted port %s with ID %s for amphora %s due to a '
989
+ 'revert.', port_name, result[constants.ID], amphora_id)
991
990
  except Exception as e:
992
991
  LOG.error('Failed to delete port %s. Resources may still be in '
993
992
  'use for a port intended for amphora %s due to error '
octavia/db/base_models.py CHANGED
@@ -12,6 +12,8 @@
12
12
  # License for the specific language governing permissions and limitations
13
13
  # under the License.
14
14
 
15
+ from wsme import types as wtypes
16
+
15
17
  from oslo_db.sqlalchemy import models
16
18
  from oslo_utils import strutils
17
19
  from oslo_utils import uuidutils
@@ -19,6 +21,8 @@ import sqlalchemy as sa
19
21
  from sqlalchemy.orm import collections
20
22
  from sqlalchemy.orm import declarative_base
21
23
 
24
+ from octavia.common import constants
25
+
22
26
 
23
27
  class OctaviaBase(models.ModelBase):
24
28
 
@@ -112,12 +116,20 @@ class OctaviaBase(models.ModelBase):
112
116
 
113
117
  @staticmethod
114
118
  def apply_filter(query, model, filters):
119
+ # Convert boolean filters to proper type
120
+ for key in filters:
121
+ attr = getattr(model.__v2_wsme__, key, None)
122
+ if isinstance(attr, wtypes.wsattr) and attr.datatype == bool:
123
+ filters[key] = strutils.bool_from_string(filters[key])
124
+ # Special case for 'enabled', it's 'admin_state_up' in the WSME class
125
+ # definition and the attribute has already been renamed to 'enabled' by
126
+ # a previous pagination filter
127
+ if constants.ENABLED in filters:
128
+ filters[constants.ENABLED] = strutils.bool_from_string(
129
+ filters[constants.ENABLED])
130
+
115
131
  translated_filters = {}
116
132
  child_map = {}
117
- # Convert enabled to proper type
118
- if 'enabled' in filters:
119
- filters['enabled'] = strutils.bool_from_string(
120
- filters['enabled'])
121
133
  for attr, name_map in model.__v2_wsme__._child_map.items():
122
134
  for k, v in name_map.items():
123
135
  if attr in filters and k in filters[attr]:
@@ -194,12 +194,13 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver):
194
194
  # Don't remove egress rules and don't confuse other protocols with
195
195
  # None ports with the egress rules. VRRP uses protocol 51 and 112
196
196
  if (rule.get('direction') == 'egress' or
197
- rule.get('protocol').upper() not in
197
+ rule.get('protocol') is None or
198
+ rule['protocol'].upper() not in
198
199
  [constants.PROTOCOL_TCP, constants.PROTOCOL_UDP,
199
200
  lib_consts.PROTOCOL_SCTP]):
200
201
  continue
201
202
  old_ports.append((rule.get('port_range_max'),
202
- rule.get('protocol').lower(),
203
+ rule['protocol'].lower(),
203
204
  rule.get('remote_ip_prefix')))
204
205
 
205
206
  add_ports = set(updated_ports) - set(old_ports)
@@ -3060,7 +3060,7 @@ class TestServerTestCase(base.TestCase):
3060
3060
  @mock.patch('octavia.amphorae.backends.utils.nftable_utils.'
3061
3061
  'load_nftables_file')
3062
3062
  @mock.patch('octavia.amphorae.backends.utils.nftable_utils.'
3063
- 'write_nftable_vip_rules_file')
3063
+ 'write_nftable_rules_file')
3064
3064
  @mock.patch('octavia.amphorae.backends.agent.api_server.amphora_info.'
3065
3065
  'AmphoraInfo.get_interface')
3066
3066
  def test_set_interface_rules(self, mock_get_int, mock_write_rules,
@@ -1782,6 +1782,24 @@ class TestHealthMonitor(base.BaseAPITest):
1782
1782
  pool_prov_status=constants.PENDING_UPDATE,
1783
1783
  hm_prov_status=constants.PENDING_UPDATE)
1784
1784
 
1785
+ def test_update_udp_case_with_udp_hm(self):
1786
+ api_hm = self.create_health_monitor(
1787
+ self.udp_pool_with_listener_id,
1788
+ constants.HEALTH_MONITOR_UDP_CONNECT, 3, 1, 1, 1).get(
1789
+ self.root_tag)
1790
+ self.set_lb_status(self.udp_lb_id)
1791
+ new_hm = {'timeout': 2}
1792
+ self.put(
1793
+ self.HM_PATH.format(healthmonitor_id=api_hm.get('id')),
1794
+ self._build_body(new_hm))
1795
+ self.assert_correct_status(
1796
+ lb_id=self.udp_lb_id, listener_id=self.udp_listener_id,
1797
+ pool_id=self.udp_pool_with_listener_id, hm_id=api_hm.get('id'),
1798
+ lb_prov_status=constants.PENDING_UPDATE,
1799
+ listener_prov_status=constants.PENDING_UPDATE,
1800
+ pool_prov_status=constants.PENDING_UPDATE,
1801
+ hm_prov_status=constants.PENDING_UPDATE)
1802
+
1785
1803
  def test_negative_update_udp_case(self):
1786
1804
  api_hm = self.create_health_monitor(
1787
1805
  self.udp_pool_with_listener_id,
@@ -913,6 +913,38 @@ class TestMember(base.BaseAPITest):
913
913
  m_subnet_exists.assert_called_once_with(
914
914
  member1['subnet_id'], context=mock.ANY)
915
915
 
916
+ @mock.patch('octavia.api.drivers.driver_factory.get_driver')
917
+ @mock.patch('octavia.api.drivers.utils.call_provider')
918
+ def test_update_members_member_duplicate(
919
+ self, mock_provider, mock_get_driver):
920
+ mock_driver = mock.MagicMock()
921
+ mock_driver.name = 'noop_driver'
922
+ mock_get_driver.return_value = mock_driver
923
+ subnet_id = uuidutils.generate_uuid()
924
+
925
+ member1 = {'address': '192.0.2.1', 'protocol_port': 80,
926
+ 'project_id': self.project_id, 'subnet_id': subnet_id}
927
+
928
+ req_dict = [member1]
929
+ body = {self.root_tag_list: req_dict}
930
+ path = self.MEMBERS_PATH.format(pool_id=self.pool_id)
931
+ self.put(path, body, status=202)
932
+
933
+ self.set_lb_status(self.lb_id)
934
+
935
+ # Same member (same address and protocol_port) updated twice in the
936
+ # same PUT request
937
+ member1 = {'address': '192.0.2.1', 'protocol_port': 80,
938
+ 'project_id': self.project_id, 'subnet_id': subnet_id,
939
+ 'name': 'member1'}
940
+ member2 = {'address': '192.0.2.1', 'protocol_port': 80,
941
+ 'project_id': self.project_id, 'subnet_id': subnet_id,
942
+ 'name': 'member2'}
943
+
944
+ req_dict = [member1, member2]
945
+ body = {self.root_tag_list: req_dict}
946
+ self.put(path, body, status=400)
947
+
916
948
  @mock.patch('octavia.api.drivers.driver_factory.get_driver')
917
949
  @mock.patch('octavia.api.drivers.utils.call_provider')
918
950
  def test_update_members_subnet_not_found(
@@ -229,5 +229,5 @@ class TestOSUtils(base.TestCase):
229
229
  mock_port_interface_file.assert_called_once_with(
230
230
  name=netns_interface,
231
231
  fixed_ips=fixed_ips,
232
- mtu=MTU)
232
+ mtu=MTU, is_sriov=False)
233
233
  mock_port_interface_file.return_value.write.assert_called_once()
@@ -285,7 +285,7 @@ class TestPlug(base.TestCase):
285
285
  self.test_plug.plug_network(FAKE_MAC_ADDRESS, fixed_ips, 1400)
286
286
 
287
287
  mock_write_port_interface.assert_called_once_with(
288
- interface='eth2', fixed_ips=fixed_ips, mtu=mtu)
288
+ interface='eth2', fixed_ips=fixed_ips, mtu=mtu, is_sriov=False)
289
289
  mock_if_up.assert_called_once_with('eth2', 'network')
290
290
  mock_send_member_adv.assert_called_once_with(fixed_ips)
291
291
 
@@ -332,7 +332,8 @@ class TestPlug(base.TestCase):
332
332
  self.test_plug.plug_network(FAKE_MAC_ADDRESS, fixed_ips, 1400)
333
333
 
334
334
  mock_write_port_interface.assert_called_once_with(
335
- interface=FAKE_INTERFACE, fixed_ips=fixed_ips, mtu=mtu)
335
+ interface=FAKE_INTERFACE, fixed_ips=fixed_ips, mtu=mtu,
336
+ is_sriov=False)
336
337
  mock_if_up.assert_called_once_with(FAKE_INTERFACE, 'network')
337
338
  mock_send_member_adv.assert_called_once_with(fixed_ips)
338
339
 
@@ -401,7 +402,7 @@ class TestPlug(base.TestCase):
401
402
  'gateway': vip_net_info['gateway'],
402
403
  'host_routes': [],
403
404
  },
404
- fixed_ips=fixed_ips, mtu=mtu)
405
+ fixed_ips=fixed_ips, mtu=mtu, is_sriov=False)
405
406
 
406
407
  mock_if_up.assert_called_once_with(FAKE_INTERFACE, 'vip')
407
408
  mock_send_member_adv.assert_called_once_with(fixed_ips)
@@ -370,13 +370,57 @@ class TestUtil(base.TestCase):
370
370
  self.assertEqual([], util.get_haproxy_vip_addresses(LB_ID1))
371
371
  mock_cfg_path.assert_called_once_with(LB_ID1)
372
372
 
373
+ @mock.patch('octavia.amphorae.backends.agent.api_server.util.'
374
+ 'keepalived_lvs_cfg_path')
375
+ def test_get_lvs_vip_addresses(self, mock_cfg_path):
376
+ FAKE_PATH = 'fake_path'
377
+ mock_cfg_path.return_value = FAKE_PATH
378
+ self.useFixture(
379
+ test_utils.OpenFixture(FAKE_PATH, 'no match')).mock_open()
380
+
381
+ # Test with no matching lines in the config file
382
+ self.assertEqual([], util.get_lvs_vip_addresses(LB_ID1))
383
+ mock_cfg_path.assert_called_once_with(LB_ID1)
384
+
385
+ # Test with 2 matching lines
386
+ mock_cfg_path.reset_mock()
387
+ test_data = ('virtual_server_group ipv4-group {\n'
388
+ ' 203.0.113.43 1\n'
389
+ ' 203.0.113.44 1\n'
390
+ '}\n')
391
+ self.useFixture(
392
+ test_utils.OpenFixture(FAKE_PATH, test_data)).mock_open()
393
+ expected_result = ['203.0.113.43', '203.0.113.44']
394
+ self.assertEqual(expected_result,
395
+ util.get_lvs_vip_addresses(LB_ID1))
396
+ mock_cfg_path.assert_called_once_with(LB_ID1)
397
+
398
+ # Test with 2 groups
399
+ mock_cfg_path.reset_mock()
400
+ test_data = ('virtual_server_group ipv4-group {\n'
401
+ ' 203.0.113.43 1\n'
402
+ '}\n'
403
+ 'virtual_server_group ipv6-group {\n'
404
+ ' 2d01:27::1 2\n'
405
+ ' 2d01:27::2 2\n'
406
+ '}\n')
407
+ self.useFixture(
408
+ test_utils.OpenFixture(FAKE_PATH, test_data)).mock_open()
409
+ expected_result = ['203.0.113.43', '2d01:27::1', '2d01:27::2']
410
+ self.assertEqual(expected_result,
411
+ util.get_lvs_vip_addresses(LB_ID1))
412
+ mock_cfg_path.assert_called_once_with(LB_ID1)
413
+
373
414
  @mock.patch('octavia.amphorae.backends.utils.ip_advertisement.'
374
415
  'send_ip_advertisement')
375
416
  @mock.patch('octavia.amphorae.backends.utils.network_utils.'
376
417
  'get_interface_name')
377
418
  @mock.patch('octavia.amphorae.backends.agent.api_server.util.'
378
419
  'get_haproxy_vip_addresses')
379
- def test_send_vip_advertisements(self, mock_get_vip_addrs,
420
+ @mock.patch('octavia.amphorae.backends.agent.api_server.util.'
421
+ 'get_lvs_vip_addresses')
422
+ def test_send_vip_advertisements(self, mock_get_lvs_vip_addrs,
423
+ mock_get_vip_addrs,
380
424
  mock_get_int_name, mock_send_advert):
381
425
  mock_get_vip_addrs.side_effect = [[], ['203.0.113.46'],
382
426
  Exception('boom')]
@@ -385,6 +429,7 @@ class TestUtil(base.TestCase):
385
429
  # Test no VIPs
386
430
  util.send_vip_advertisements(LB_ID1)
387
431
  mock_get_vip_addrs.assert_called_once_with(LB_ID1)
432
+ mock_get_lvs_vip_addrs.assert_not_called()
388
433
  mock_get_int_name.assert_not_called()
389
434
  mock_send_advert.assert_not_called()
390
435
 
@@ -394,6 +439,7 @@ class TestUtil(base.TestCase):
394
439
  mock_send_advert.reset_mock()
395
440
  util.send_vip_advertisements(LB_ID1)
396
441
  mock_get_vip_addrs.assert_called_once_with(LB_ID1)
442
+ mock_get_lvs_vip_addrs.assert_not_called()
397
443
  mock_get_int_name.assert_called_once_with(
398
444
  '203.0.113.46', net_ns=consts.AMPHORA_NAMESPACE)
399
445
  mock_send_advert.assert_called_once_with(
@@ -407,6 +453,48 @@ class TestUtil(base.TestCase):
407
453
  mock_get_int_name.assert_not_called()
408
454
  mock_send_advert.assert_not_called()
409
455
 
456
+ @mock.patch('octavia.amphorae.backends.utils.ip_advertisement.'
457
+ 'send_ip_advertisement')
458
+ @mock.patch('octavia.amphorae.backends.utils.network_utils.'
459
+ 'get_interface_name')
460
+ @mock.patch('octavia.amphorae.backends.agent.api_server.util.'
461
+ 'get_haproxy_vip_addresses')
462
+ @mock.patch('octavia.amphorae.backends.agent.api_server.util.'
463
+ 'get_lvs_vip_addresses')
464
+ def test_send_vip_advertisements_udp(self, mock_get_lvs_vip_addrs,
465
+ mock_get_vip_addrs,
466
+ mock_get_int_name, mock_send_advert):
467
+ mock_get_lvs_vip_addrs.side_effect = [[], ['203.0.113.46'],
468
+ Exception('boom')]
469
+ mock_get_int_name.return_value = 'fake0'
470
+
471
+ # Test no VIPs
472
+ util.send_vip_advertisements(listener_id=LISTENER_ID1)
473
+ mock_get_lvs_vip_addrs.assert_called_once_with(LISTENER_ID1)
474
+ mock_get_vip_addrs.assert_not_called()
475
+ mock_get_int_name.assert_not_called()
476
+ mock_send_advert.assert_not_called()
477
+
478
+ # Test with a VIP
479
+ mock_get_lvs_vip_addrs.reset_mock()
480
+ mock_get_int_name.reset_mock()
481
+ mock_send_advert.reset_mock()
482
+ util.send_vip_advertisements(listener_id=LISTENER_ID1)
483
+ mock_get_lvs_vip_addrs.assert_called_once_with(LISTENER_ID1)
484
+ mock_get_vip_addrs.assert_not_called()
485
+ mock_get_int_name.assert_called_once_with(
486
+ '203.0.113.46', net_ns=consts.AMPHORA_NAMESPACE)
487
+ mock_send_advert.assert_called_once_with(
488
+ 'fake0', '203.0.113.46', net_ns=consts.AMPHORA_NAMESPACE)
489
+
490
+ # Test with an exception (should not raise)
491
+ mock_get_lvs_vip_addrs.reset_mock()
492
+ mock_get_int_name.reset_mock()
493
+ mock_send_advert.reset_mock()
494
+ util.send_vip_advertisements(listener_id=LISTENER_ID1)
495
+ mock_get_int_name.assert_not_called()
496
+ mock_send_advert.assert_not_called()
497
+
410
498
  @mock.patch('octavia.amphorae.backends.utils.ip_advertisement.'
411
499
  'send_ip_advertisement')
412
500
  @mock.patch('octavia.amphorae.backends.utils.network_utils.'
@@ -452,7 +452,7 @@ class TestInterface(base.TestCase):
452
452
  @mock.patch('octavia.amphorae.backends.utils.network_namespace.'
453
453
  'NetworkNamespace')
454
454
  @mock.patch('octavia.amphorae.backends.utils.nftable_utils.'
455
- 'write_nftable_vip_rules_file')
455
+ 'write_nftable_rules_file')
456
456
  @mock.patch('pyroute2.IPRoute.rule')
457
457
  @mock.patch('pyroute2.IPRoute.route')
458
458
  @mock.patch('pyroute2.IPRoute.addr')
@@ -578,16 +578,8 @@ class TestInterface(base.TestCase):
578
578
  family=socket.AF_INET6)])
579
579
 
580
580
  mock_check_output.assert_has_calls([
581
- mock.call([consts.NFT_CMD, consts.NFT_ADD, 'table',
582
- consts.NFT_FAMILY, consts.NFT_VIP_TABLE], stderr=-2),
583
- mock.call([consts.NFT_CMD, consts.NFT_ADD, 'chain',
584
- consts.NFT_FAMILY, consts.NFT_VIP_TABLE,
585
- consts.NFT_VIP_CHAIN, '{', 'type', 'filter', 'hook',
586
- 'ingress', 'device', 'fake-eth1', 'priority',
587
- consts.NFT_SRIOV_PRIORITY, ';', 'policy', 'drop', ';',
588
- '}'], stderr=-2),
589
- mock.call([consts.NFT_CMD, '-o', '-f', consts.NFT_VIP_RULES_FILE],
590
- stderr=-2),
581
+ mock.call([consts.NFT_CMD, '-o', '-f', consts.NFT_RULES_FILE],
582
+ stderr=subprocess.STDOUT),
591
583
  mock.call(["post-up", "fake-eth1"])
592
584
  ])
593
585
 
@@ -1444,56 +1436,3 @@ class TestInterface(base.TestCase):
1444
1436
 
1445
1437
  addr = controller._normalize_ip_network(None)
1446
1438
  self.assertIsNone(addr)
1447
-
1448
- @mock.patch('octavia.amphorae.backends.utils.nftable_utils.'
1449
- 'load_nftables_file')
1450
- @mock.patch('octavia.amphorae.backends.utils.nftable_utils.'
1451
- 'write_nftable_vip_rules_file')
1452
- @mock.patch('subprocess.check_output')
1453
- def test__setup_nftables_chain(self, mock_check_output, mock_write_rules,
1454
- mock_load_rules):
1455
-
1456
- controller = interface.InterfaceController()
1457
-
1458
- mock_check_output.side_effect = [
1459
- mock.DEFAULT, mock.DEFAULT,
1460
- subprocess.CalledProcessError(cmd=consts.NFT_CMD, returncode=-1),
1461
- mock.DEFAULT,
1462
- subprocess.CalledProcessError(cmd=consts.NFT_CMD, returncode=-1)]
1463
-
1464
- interface_mock = mock.MagicMock()
1465
- interface_mock.name = 'fake2'
1466
-
1467
- # Test succeessful path
1468
- controller._setup_nftables_chain(interface_mock)
1469
-
1470
- mock_write_rules.assert_called_once_with('fake2', [])
1471
- mock_load_rules.assert_called_once_with()
1472
- mock_check_output.assert_has_calls([
1473
- mock.call([consts.NFT_CMD, 'add', 'table', consts.NFT_FAMILY,
1474
- consts.NFT_VIP_TABLE], stderr=subprocess.STDOUT),
1475
- mock.call([consts.NFT_CMD, 'add', 'chain', consts.NFT_FAMILY,
1476
- consts.NFT_VIP_TABLE, consts.NFT_VIP_CHAIN, '{',
1477
- 'type', 'filter', 'hook', 'ingress', 'device',
1478
- 'fake2', 'priority', consts.NFT_SRIOV_PRIORITY, ';',
1479
- 'policy', 'drop', ';', '}'], stderr=subprocess.STDOUT)])
1480
-
1481
- # Test first nft call fails
1482
- mock_write_rules.reset_mock()
1483
- mock_load_rules.reset_mock()
1484
- mock_check_output.reset_mock()
1485
-
1486
- self.assertRaises(subprocess.CalledProcessError,
1487
- controller._setup_nftables_chain, interface_mock)
1488
- mock_check_output.assert_called_once()
1489
- mock_write_rules.assert_not_called()
1490
-
1491
- # Test second nft call fails
1492
- mock_write_rules.reset_mock()
1493
- mock_load_rules.reset_mock()
1494
- mock_check_output.reset_mock()
1495
-
1496
- self.assertRaises(subprocess.CalledProcessError,
1497
- controller._setup_nftables_chain, interface_mock)
1498
- self.assertEqual(2, mock_check_output.call_count)
1499
- mock_write_rules.assert_not_called()
@@ -28,23 +28,22 @@ import octavia.tests.unit.base as base
28
28
  class TestNFTableUtils(base.TestCase):
29
29
  @mock.patch('os.open')
30
30
  @mock.patch('os.path.isfile')
31
- def test_write_nftable_vip_rules_file_exists(self, mock_isfile, mock_open):
31
+ def test_write_nftable_rules_file_exists(self, mock_isfile, mock_open):
32
32
  """Test when a rules file exists and no new rules
33
33
 
34
34
  When an existing rules file is present and we call
35
- write_nftable_vip_rules_file with no rules, the method should not
35
+ write_nftable_rules_file with no rules, the method should not
36
36
  overwrite the existing rules.
37
37
  """
38
38
  mock_isfile.return_value = True
39
39
 
40
- nftable_utils.write_nftable_vip_rules_file('fake-eth2', [])
40
+ nftable_utils.write_nftable_rules_file('fake-eth2', [])
41
41
 
42
42
  mock_open.assert_not_called()
43
43
 
44
44
  @mock.patch('os.open')
45
45
  @mock.patch('os.path.isfile')
46
- def test_write_nftable_vip_rules_file_rules(self, mock_isfile,
47
- mock_open):
46
+ def test_write_nftable_rules_file_rules(self, mock_isfile, mock_open):
48
47
  """Test when a rules file exists and rules are passed in
49
48
 
50
49
  This should create a simple rules file with the base chain and rules.
@@ -61,32 +60,40 @@ class TestNFTableUtils(base.TestCase):
61
60
 
62
61
  mocked_open = mock.mock_open()
63
62
  with mock.patch.object(os, 'fdopen', mocked_open):
64
- nftable_utils.write_nftable_vip_rules_file(
63
+ nftable_utils.write_nftable_rules_file(
65
64
  'fake-eth2', [test_rule_1, test_rule_2])
66
65
 
67
66
  mocked_open.assert_called_once_with('fake-fd', 'w')
68
67
  mock_open.assert_called_once_with(
69
- consts.NFT_VIP_RULES_FILE,
68
+ consts.NFT_RULES_FILE,
70
69
  (os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
71
70
  (stat.S_IRUSR | stat.S_IWUSR))
72
71
 
73
72
  handle = mocked_open()
74
73
  handle.write.assert_has_calls([
75
- mock.call(f'table {consts.NFT_FAMILY} {consts.NFT_VIP_TABLE} '
74
+ mock.call(f'table {consts.NFT_FAMILY} {consts.NFT_TABLE} '
76
75
  '{}\n'),
77
76
  mock.call(f'delete table {consts.NFT_FAMILY} '
78
- f'{consts.NFT_VIP_TABLE}\n'),
79
- mock.call(f'table {consts.NFT_FAMILY} {consts.NFT_VIP_TABLE} '
77
+ f'{consts.NFT_TABLE}\n'),
78
+ mock.call(f'table {consts.NFT_FAMILY} {consts.NFT_TABLE} '
80
79
  '{\n'),
81
- mock.call(f' chain {consts.NFT_VIP_CHAIN} {{\n'),
82
- mock.call(' type filter hook ingress device fake-eth2 '
83
- f'priority {consts.NFT_SRIOV_PRIORITY}; policy drop;\n'),
80
+ mock.call(f' chain {consts.NFT_CHAIN} {{\n'),
81
+ mock.call(' type filter hook input priority filter; '
82
+ 'policy drop;\n'),
83
+ mock.call(' ct state vmap { established : accept, related : '
84
+ 'accept, invalid : drop }\n'),
85
+ mock.call(' iif lo accept\n'),
86
+ mock.call(' ip saddr 127.0.0.0/8 drop\n'),
87
+ mock.call(' ip6 saddr ::1 drop\n'),
84
88
  mock.call(' icmp type destination-unreachable accept\n'),
85
89
  mock.call(' icmpv6 type { nd-neighbor-solicit, '
86
90
  'nd-router-advert, nd-neighbor-advert, packet-too-big, '
87
91
  'destination-unreachable } accept\n'),
88
92
  mock.call(' udp sport 67 udp dport 68 accept\n'),
89
93
  mock.call(' udp sport 547 udp dport 546 accept\n'),
94
+ mock.call(' iifname eth1 goto amphora_vip_chain\n'),
95
+ mock.call(' }\n'),
96
+ mock.call(' chain amphora_vip_chain {\n'),
90
97
  mock.call(' tcp dport 1234 accept\n'),
91
98
  mock.call(' ip saddr 192.0.2.0/24 ip protocol 112 accept\n'),
92
99
  mock.call(' }\n'),
@@ -95,8 +102,7 @@ class TestNFTableUtils(base.TestCase):
95
102
 
96
103
  @mock.patch('os.open')
97
104
  @mock.patch('os.path.isfile')
98
- def test_write_nftable_vip_rules_file_missing(self, mock_isfile,
99
- mock_open):
105
+ def test_write_nftable_rules_file_missing(self, mock_isfile, mock_open):
100
106
  """Test when a rules file does not exist and no new rules
101
107
 
102
108
  This should create a simple rules file with the base chain.
@@ -106,21 +112,21 @@ class TestNFTableUtils(base.TestCase):
106
112
 
107
113
  mocked_open = mock.mock_open()
108
114
  with mock.patch.object(os, 'fdopen', mocked_open):
109
- nftable_utils.write_nftable_vip_rules_file('fake-eth2', [])
115
+ nftable_utils.write_nftable_rules_file('fake-eth2', [])
110
116
 
111
117
  mocked_open.assert_called_once_with('fake-fd', 'w')
112
118
  mock_open.assert_called_once_with(
113
- consts.NFT_VIP_RULES_FILE,
119
+ consts.NFT_RULES_FILE,
114
120
  (os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
115
121
  (stat.S_IRUSR | stat.S_IWUSR))
116
122
 
117
123
  handle = mocked_open()
118
124
  handle.write.assert_has_calls([
119
- mock.call(f'table {consts.NFT_FAMILY} {consts.NFT_VIP_TABLE} '
125
+ mock.call(f'table {consts.NFT_FAMILY} {consts.NFT_TABLE} '
120
126
  '{\n'),
121
- mock.call(f' chain {consts.NFT_VIP_CHAIN} {{\n'),
122
- mock.call(' type filter hook ingress device fake-eth2 '
123
- f'priority {consts.NFT_SRIOV_PRIORITY}; policy drop;\n'),
127
+ mock.call(f' chain {consts.NFT_CHAIN} {{\n'),
128
+ mock.call(' type filter hook input priority filter; '
129
+ 'policy drop;\n'),
124
130
  mock.call(' icmp type destination-unreachable accept\n'),
125
131
  mock.call(' icmpv6 type { nd-neighbor-solicit, '
126
132
  'nd-router-advert, nd-neighbor-advert, packet-too-big, '
@@ -184,7 +190,7 @@ class TestNFTableUtils(base.TestCase):
184
190
 
185
191
  mock_netns.assert_called_once_with(consts.AMPHORA_NAMESPACE)
186
192
  mock_check_output.assert_called_once_with([
187
- consts.NFT_CMD, '-o', '-f', consts.NFT_VIP_RULES_FILE],
193
+ consts.NFT_CMD, '-o', '-f', consts.NFT_RULES_FILE],
188
194
  stderr=subprocess.STDOUT)
189
195
 
190
196
  self.assertRaises(subprocess.CalledProcessError,
@@ -71,7 +71,6 @@ class TestVRRPRestDriver(base.TestCase):
71
71
  "}\n"
72
72
  "\n"
73
73
  "vrrp_instance TESTGROUP {\n"
74
- " state MASTER\n"
75
74
  " interface eth1\n"
76
75
  " virtual_router_id 1\n"
77
76
  " priority 100\n"
@@ -124,7 +123,6 @@ class TestVRRPRestDriver(base.TestCase):
124
123
  "}\n"
125
124
  "\n"
126
125
  "vrrp_instance TESTGROUP {\n"
127
- " state MASTER\n"
128
126
  " interface eth1\n"
129
127
  " virtual_router_id 1\n"
130
128
  " priority 100\n"
@@ -170,7 +168,6 @@ class TestVRRPRestDriver(base.TestCase):
170
168
  "}\n"
171
169
  "\n"
172
170
  "vrrp_instance TESTGROUP {\n"
173
- " state MASTER\n"
174
171
  " interface eth1\n"
175
172
  " virtual_router_id 1\n"
176
173
  " priority 100\n"
@@ -220,7 +217,6 @@ class TestVRRPRestDriver(base.TestCase):
220
217
  "}\n"
221
218
  "\n"
222
219
  "vrrp_instance TESTGROUP {\n"
223
- " state MASTER\n"
224
220
  " interface eth1\n"
225
221
  " virtual_router_id 1\n"
226
222
  " priority 100\n"