From 8346a956fa308bdc3e1167c885d21faab952d1b4 Mon Sep 17 00:00:00 2001 From: ricolin Date: Wed, 18 Dec 2024 13:52:41 +0800 Subject: [PATCH] bump octavia image version --- images/octavia/Dockerfile | 2 +- .../0001-Allow-failover-on-error.patch | 517 +++++++++++++++++- 2 files changed, 497 insertions(+), 22 deletions(-) diff --git a/images/octavia/Dockerfile b/images/octavia/Dockerfile index 2cad0a9261..8095047232 100644 --- a/images/octavia/Dockerfile +++ b/images/octavia/Dockerfile @@ -4,7 +4,7 @@ ARG RELEASE FROM harbor.atmosphere.dev/library/openstack-venv-builder:${RELEASE} AS build -ARG OCTAVIA_GIT_REF=824b51a1dad80292b7a8ad5d61bf3ce706b1fb29 +ARG OCTAVIA_GIT_REF=dda68b1cf6387170c3c68aad7dec82e6d84596a8 ADD --keep-git-dir=true https://opendev.org/openstack/octavia.git#${OCTAVIA_GIT_REF} /src/octavia RUN git -C /src/octavia fetch --unshallow COPY patches/octavia /patches/octavia diff --git a/images/octavia/patches/octavia/0001-Allow-failover-on-error.patch b/images/octavia/patches/octavia/0001-Allow-failover-on-error.patch index d4f592a63e..d6e9c565ce 100644 --- a/images/octavia/patches/octavia/0001-Allow-failover-on-error.patch +++ b/images/octavia/patches/octavia/0001-Allow-failover-on-error.patch @@ -1,7 +1,7 @@ -From 4a639f5c0f75cc5e58adf2e247808836de9a0d23 Mon Sep 17 00:00:00 2001 +From c73b1ba3b0ae0fc55f95b3433d3db98a9e131d0b Mon Sep 17 00:00:00 2001 From: ricolin -Date: Mon, 11 Nov 2024 19:04:39 +0800 -Subject: [PATCH] Allow failover on error +Date: Wed, 18 Dec 2024 13:39:26 +0800 +Subject: [PATCH] [2024.1]Allow failover on error Add config `failover_on_error` to allow Amphora failover with `ERROR` status. @@ -15,25 +15,31 @@ A lot of Amphora `ERROR` are able to solved by running failover on LB. With nothing change on original scenario, these new configs allow environments to do failover automatically for Amphora on ERROR status. -Change-Id: Icff02f7a621cc13a8a0383e1b322f96027c421a6 +ref Change-Id: Icff02f7a621cc13a8a0383e1b322f96027c421a6 + +Change-Id: I0e120449d7cbe82a9ca5aecaf41709299f03308a --- - octavia/common/config.py | 11 ++++++ + octavia/common/config.py | 11 ++++ octavia/common/constants.py | 3 ++ octavia/common/data_models.py | 4 +- .../healthmanager/health_manager.py | 3 +- - .../controller/worker/v2/controller_worker.py | 23 +++++++++-- + .../controller/worker/v2/controller_worker.py | 27 ++++++++-- .../worker/v2/flows/amphora_flows.py | 2 +- .../worker/v2/tasks/database_tasks.py | 3 +- - .../6c59498c8922_add_error_retries.py | 39 +++++++++++++++++++ + .../6c59498c8922_add_error_retries.py | 39 ++++++++++++++ octavia/db/models.py | 1 + - octavia/db/repositories.py | 34 ++++++++++++++-- - ...ror-amphora-failover-ab882982adc05f01.yaml | 24 ++++++++++++ - 15 files changed, 202 insertions(+), 32 deletions(-) + octavia/db/repositories.py | 34 ++++++++++-- + .../tests/functional/db/test_repositories.py | 42 +++++++++++++++ + .../worker/v2/flows/test_amphora_flows.py | 27 ++++++---- + .../v2/flows/test_load_balancer_flows.py | 12 +++-- + .../worker/v2/test_controller_worker.py | 53 ++++++++++++++----- + ...ror-amphora-failover-ab882982adc05f01.yaml | 24 +++++++++ + 15 files changed, 248 insertions(+), 37 deletions(-) create mode 100644 octavia/db/migration/alembic_migrations/versions/6c59498c8922_add_error_retries.py create mode 100644 releasenotes/notes/allow-error-amphora-failover-ab882982adc05f01.yaml diff --git a/octavia/common/config.py b/octavia/common/config.py -index a414b737e..c115f279f 100644 +index 00f09930e..6b574d6bf 100644 --- a/octavia/common/config.py +++ b/octavia/common/config.py @@ -309,6 +309,17 @@ health_manager_opts = [ @@ -55,12 +61,12 @@ index a414b737e..c115f279f 100644 oslo_messaging_opts = [ diff --git a/octavia/common/constants.py b/octavia/common/constants.py -index 7e31cc2ad..bf0ffd31f 100644 +index 1368c011e..9abd65d9f 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py -@@ -977,3 +977,6 @@ NFT_TABLE = 'amphora_table' - NFT_CHAIN = 'amphora_chain' +@@ -977,3 +977,6 @@ NFT_VIP_TABLE = 'amphora_vip' NFT_VIP_CHAIN = 'amphora_vip_chain' + NFT_SRIOV_PRIORITY = '-310' PROTOCOL = 'protocol' + +# Amphora failover retries on ERROR status @@ -88,10 +94,10 @@ index b416c25e7..059684561 100644 def delete(self): for amphora in self.load_balancer.amphorae: diff --git a/octavia/controller/healthmanager/health_manager.py b/octavia/controller/healthmanager/health_manager.py -index 1ba19c025..d037564e9 100644 +index 24e1761ac..7902bde73 100644 --- a/octavia/controller/healthmanager/health_manager.py +++ b/octavia/controller/healthmanager/health_manager.py -@@ -138,7 +138,8 @@ class HealthManager: +@@ -132,7 +132,8 @@ class HealthManager: LOG.info("Stale amphora's id is: %s", amp_health.amphora_id) fut = self.executor.submit( @@ -102,7 +108,7 @@ index 1ba19c025..d037564e9 100644 functools.partial(update_stats_on_done, stats) ) diff --git a/octavia/controller/worker/v2/controller_worker.py b/octavia/controller/worker/v2/controller_worker.py -index fa1feb216..1ad6616a0 100644 +index fa1feb216..0a4d9132b 100644 --- a/octavia/controller/worker/v2/controller_worker.py +++ b/octavia/controller/worker/v2/controller_worker.py @@ -395,7 +395,8 @@ class ControllerWorker: @@ -130,7 +136,7 @@ index fa1feb216..1ad6616a0 100644 :param amphora_id: ID for amphora to failover :param reraise: If enabled reraise any caught exception + :param count_error_retries: If enabled allows counts Amphora failover -+ on ERROR status ++ on ERROR status :returns: None :raises octavia.common.exceptions.NotFound: The referenced amphora was not found @@ -163,6 +169,17 @@ index fa1feb216..1ad6616a0 100644 self.run_flow( flow_utils.get_failover_amphora_flow, +@@ -1218,7 +1235,9 @@ class ControllerWorker: + constants.LB_CREATE_FAILOVER_PRIORITY, + constants.SERVER_GROUP_ID: lb.server_group_id, + constants.LOADBALANCER_ID: lb.id, +- constants.FLAVOR: flavor} ++ constants.FLAVOR: flavor, ++ constants.AMP_ERR_RETRIES: 0, ++ } + + if lb.availability_zone: + with session.begin(): diff --git a/octavia/controller/worker/v2/flows/amphora_flows.py b/octavia/controller/worker/v2/flows/amphora_flows.py index 45816b2f7..ff624a8d6 100644 --- a/octavia/controller/worker/v2/flows/amphora_flows.py @@ -177,7 +194,7 @@ index 45816b2f7..ff624a8d6 100644 create_amp_for_lb_subflow.add(cert_task.GenerateServerPEMTask( diff --git a/octavia/controller/worker/v2/tasks/database_tasks.py b/octavia/controller/worker/v2/tasks/database_tasks.py -index 48b4d92d4..3ccfcc44b 100644 +index e78ef41d7..ca2cdf2bd 100644 --- a/octavia/controller/worker/v2/tasks/database_tasks.py +++ b/octavia/controller/worker/v2/tasks/database_tasks.py @@ -102,7 +102,8 @@ class CreateAmphoraInDB(BaseDatabaseTask): @@ -248,7 +265,7 @@ index e1ab20ba3..63c109a85 100644 def __str__(self): return (f"Amphora(id={self.id!r}, load_balancer_id=" diff --git a/octavia/db/repositories.py b/octavia/db/repositories.py -index 48656540e..bed58c450 100644 +index cc4bd6f8a..f8cd51e71 100644 --- a/octavia/db/repositories.py +++ b/octavia/db/repositories.py @@ -27,6 +27,8 @@ from oslo_db import exception as db_exception @@ -260,7 +277,7 @@ index 48656540e..bed58c450 100644 from sqlalchemy.orm import noload from sqlalchemy.orm import Session from sqlalchemy.orm import subqueryload -@@ -1605,11 +1607,35 @@ class AmphoraHealthRepository(BaseRepository): +@@ -1579,11 +1581,35 @@ class AmphoraHealthRepository(BaseRepository): # We don't want to attempt to failover amphora that are not # currently in the ALLOCATED or FAILOVER_STOPPED state. # i.e. Not DELETED, PENDING_*, etc. @@ -300,6 +317,464 @@ index 48656540e..bed58c450 100644 # Pick one expired amphora for automatic failover amp_health = lock_session.query( +diff --git a/octavia/tests/functional/db/test_repositories.py b/octavia/tests/functional/db/test_repositories.py +index ebb4532cf..e5e47202f 100644 +--- a/octavia/tests/functional/db/test_repositories.py ++++ b/octavia/tests/functional/db/test_repositories.py +@@ -3957,6 +3957,48 @@ class AmphoraHealthRepositoryTest(BaseRepositoryTest): + self.session) + self.assertEqual(uuid, stale_amphora.amphora_id) + ++ def test_get_stale_error_amphora(self): ++ conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) ++ conf.config(group='health_manager', failover_on_error=True) ++ self._get_stale_error_amphora() ++ ++ def _get_stale_error_amphora(self): ++ stale_amphora = self.amphora_health_repo.get_stale_amphora( ++ self.session) ++ self.assertIsNone(stale_amphora) ++ ++ uuid = uuidutils.generate_uuid() ++ self.create_amphora(uuid) ++ self.amphora_repo.update(self.session, uuid, ++ status=constants.ERROR) ++ self.create_amphora_health(uuid) ++ stale_amphora = self.amphora_health_repo.get_stale_amphora( ++ self.session) ++ self.assertEqual(uuid, stale_amphora.amphora_id) ++ ++ def test_get_stale_error_amphora_with_unlimited(self): ++ conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) ++ conf.config(group='health_manager', failover_on_error=True) ++ conf.config(group='health_manager', failover_on_error_max_retries=-1) ++ self._get_stale_error_amphora() ++ ++ def test_get_stale_error_amphora_with_limit_hit(self): ++ conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) ++ conf.config(group='health_manager', failover_on_error=True) ++ conf.config(group='health_manager', failover_on_error_max_retries=0) ++ stale_amphora = self.amphora_health_repo.get_stale_amphora( ++ self.session) ++ self.assertIsNone(stale_amphora) ++ ++ uuid = uuidutils.generate_uuid() ++ self.create_amphora(uuid) ++ self.amphora_repo.update(self.session, uuid, ++ status=constants.ERROR) ++ self.create_amphora_health(uuid) ++ stale_amphora = self.amphora_health_repo.get_stale_amphora( ++ self.session) ++ self.assertIsNone(stale_amphora) ++ + def test_get_stale_amphora_past_threshold(self): + conf = self.useFixture(oslo_fixture.Config(cfg.CONF)) + conf.config(group='health_manager', failover_threshold=3) +diff --git a/octavia/tests/unit/controller/worker/v2/flows/test_amphora_flows.py b/octavia/tests/unit/controller/worker/v2/flows/test_amphora_flows.py +index 5c3f3fbba..936b2d83e 100644 +--- a/octavia/tests/unit/controller/worker/v2/flows/test_amphora_flows.py ++++ b/octavia/tests/unit/controller/worker/v2/flows/test_amphora_flows.py +@@ -97,6 +97,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.AVAILABILITY_ZONE, amp_flow.requires) + self.assertIn(constants.BUILD_TYPE_PRIORITY, amp_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, amp_flow.requires) + + self.assertIn(constants.AMPHORA, amp_flow.provides) + self.assertIn(constants.AMPHORA_ID, amp_flow.provides) +@@ -105,7 +106,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.SERVER_PEM, amp_flow.provides) + + self.assertEqual(5, len(amp_flow.provides)) +- self.assertEqual(5, len(amp_flow.requires)) ++ self.assertEqual(6, len(amp_flow.requires)) + + def test_get_cert_create_amphora_for_lb_flow(self, mock_get_net_driver): + +@@ -120,6 +121,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.AVAILABILITY_ZONE, amp_flow.requires) + self.assertIn(constants.BUILD_TYPE_PRIORITY, amp_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, amp_flow.requires) + + self.assertIn(constants.AMPHORA, amp_flow.provides) + self.assertIn(constants.AMPHORA_ID, amp_flow.provides) +@@ -128,7 +130,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.SERVER_PEM, amp_flow.provides) + + self.assertEqual(5, len(amp_flow.provides)) +- self.assertEqual(5, len(amp_flow.requires)) ++ self.assertEqual(6, len(amp_flow.requires)) + + def test_get_cert_master_create_amphora_for_lb_flow( + self, mock_get_net_driver): +@@ -144,6 +146,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.AVAILABILITY_ZONE, amp_flow.requires) + self.assertIn(constants.BUILD_TYPE_PRIORITY, amp_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, amp_flow.requires) + + self.assertIn(constants.AMPHORA, amp_flow.provides) + self.assertIn(constants.AMPHORA_ID, amp_flow.provides) +@@ -152,7 +155,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.SERVER_PEM, amp_flow.provides) + + self.assertEqual(5, len(amp_flow.provides)) +- self.assertEqual(5, len(amp_flow.requires)) ++ self.assertEqual(6, len(amp_flow.requires)) + + def test_get_cert_master_rest_anti_affinity_create_amphora_for_lb_flow( + self, mock_get_net_driver): +@@ -170,6 +173,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.BUILD_TYPE_PRIORITY, amp_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires) + self.assertIn(constants.SERVER_GROUP_ID, amp_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, amp_flow.requires) + + self.assertIn(constants.AMPHORA, amp_flow.provides) + self.assertIn(constants.AMPHORA_ID, amp_flow.provides) +@@ -178,7 +182,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.SERVER_PEM, amp_flow.provides) + + self.assertEqual(5, len(amp_flow.provides)) +- self.assertEqual(5, len(amp_flow.requires)) ++ self.assertEqual(6, len(amp_flow.requires)) + self.conf.config(group="nova", enable_anti_affinity=False) + + def test_get_cert_backup_create_amphora_for_lb_flow( +@@ -194,6 +198,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.AVAILABILITY_ZONE, amp_flow.requires) + self.assertIn(constants.BUILD_TYPE_PRIORITY, amp_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, amp_flow.requires) + + self.assertIn(constants.AMPHORA, amp_flow.provides) + self.assertIn(constants.AMPHORA_ID, amp_flow.provides) +@@ -202,7 +207,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.SERVER_PEM, amp_flow.provides) + + self.assertEqual(5, len(amp_flow.provides)) +- self.assertEqual(5, len(amp_flow.requires)) ++ self.assertEqual(6, len(amp_flow.requires)) + + def test_get_cert_bogus_create_amphora_for_lb_flow( + self, mock_get_net_driver): +@@ -217,6 +222,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.AVAILABILITY_ZONE, amp_flow.requires) + self.assertIn(constants.BUILD_TYPE_PRIORITY, amp_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, amp_flow.requires) + + self.assertIn(constants.AMPHORA, amp_flow.provides) + self.assertIn(constants.AMPHORA_ID, amp_flow.provides) +@@ -225,7 +231,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.SERVER_PEM, amp_flow.provides) + + self.assertEqual(5, len(amp_flow.provides)) +- self.assertEqual(5, len(amp_flow.requires)) ++ self.assertEqual(6, len(amp_flow.requires)) + + def test_get_cert_backup_rest_anti_affinity_create_amphora_for_lb_flow( + self, mock_get_net_driver): +@@ -242,6 +248,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.BUILD_TYPE_PRIORITY, amp_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires) + self.assertIn(constants.SERVER_GROUP_ID, amp_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, amp_flow.requires) + + self.assertIn(constants.AMPHORA_ID, amp_flow.provides) + self.assertIn(constants.SERVER_GROUP_ID, amp_flow.requires) +@@ -250,7 +257,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.SERVER_PEM, amp_flow.provides) + + self.assertEqual(5, len(amp_flow.provides)) +- self.assertEqual(5, len(amp_flow.requires)) ++ self.assertEqual(6, len(amp_flow.requires)) + self.conf.config(group="nova", enable_anti_affinity=False) + + def test_get_delete_amphora_flow(self, mock_get_net_driver): +@@ -280,6 +287,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.LOADBALANCER, amp_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires) + self.assertIn(constants.VIP, amp_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, amp_flow.requires) + + self.assertIn(constants.UPDATED_PORTS, amp_flow.provides) + self.assertIn(constants.AMP_VRRP_INT, amp_flow.provides) +@@ -296,7 +304,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.SERVER_PEM, amp_flow.provides) + self.assertIn(constants.VIP_SG_ID, amp_flow.provides) + +- self.assertEqual(8, len(amp_flow.requires)) ++ self.assertEqual(9, len(amp_flow.requires)) + self.assertEqual(14, len(amp_flow.provides)) + + def test_get_failover_flow_standalone(self, mock_get_net_driver): +@@ -316,6 +324,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.LOADBALANCER, amp_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, amp_flow.requires) + self.assertIn(constants.VIP, amp_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, amp_flow.requires) + + self.assertIn(constants.UPDATED_PORTS, amp_flow.provides) + self.assertIn(constants.AMPHORA, amp_flow.provides) +@@ -331,7 +340,7 @@ class TestAmphoraFlows(base.TestCase): + self.assertIn(constants.SERVER_PEM, amp_flow.provides) + self.assertIn(constants.VIP_SG_ID, amp_flow.provides) + +- self.assertEqual(8, len(amp_flow.requires)) ++ self.assertEqual(9, len(amp_flow.requires)) + self.assertEqual(13, len(amp_flow.provides)) + + def test_get_failover_flow_bogus_role(self, mock_get_net_driver): +diff --git a/octavia/tests/unit/controller/worker/v2/flows/test_load_balancer_flows.py b/octavia/tests/unit/controller/worker/v2/flows/test_load_balancer_flows.py +index fc48ce8b9..e36da6597 100644 +--- a/octavia/tests/unit/controller/worker/v2/flows/test_load_balancer_flows.py ++++ b/octavia/tests/unit/controller/worker/v2/flows/test_load_balancer_flows.py +@@ -270,6 +270,7 @@ class TestLoadBalancerFlows(base.TestCase): + self.assertIn(constants.FLAVOR, create_flow.requires) + self.assertIn(constants.AVAILABILITY_ZONE, create_flow.requires) + self.assertIn(constants.SERVER_GROUP_ID, create_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, create_flow.requires) + + self.assertIn(constants.LISTENERS, create_flow.provides) + self.assertIn(constants.SUBNET, create_flow.provides) +@@ -287,7 +288,7 @@ class TestLoadBalancerFlows(base.TestCase): + self.assertIn(constants.ADDITIONAL_VIPS, create_flow.provides) + self.assertIn(constants.AMPHORAE_NETWORK_CONFIG, create_flow.provides) + +- self.assertEqual(6, len(create_flow.requires)) ++ self.assertEqual(7, len(create_flow.requires)) + self.assertEqual(15, len(create_flow.provides)) + + @mock.patch('octavia.common.rpc.NOTIFIER', +@@ -306,6 +307,7 @@ class TestLoadBalancerFlows(base.TestCase): + self.assertIn(constants.LOADBALANCER_ID, create_flow.requires) + self.assertIn(constants.SERVER_GROUP_ID, create_flow.requires) + self.assertIn(constants.UPDATE_DICT, create_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, create_flow.requires) + + self.assertIn(constants.UPDATED_PORTS, create_flow.provides) + self.assertIn(constants.AMP_DATA, create_flow.provides) +@@ -326,7 +328,7 @@ class TestLoadBalancerFlows(base.TestCase): + self.assertIn(constants.VIP, create_flow.provides) + self.assertIn(constants.ADDITIONAL_VIPS, create_flow.provides) + +- self.assertEqual(6, len(create_flow.requires), create_flow.requires) ++ self.assertEqual(7, len(create_flow.requires), create_flow.requires) + self.assertEqual(18, len(create_flow.provides), + create_flow.provides) + +@@ -344,6 +346,7 @@ class TestLoadBalancerFlows(base.TestCase): + self.assertIn(constants.FLAVOR, failover_flow.requires) + self.assertIn(constants.LOADBALANCER, failover_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, failover_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, failover_flow.requires) + + self.assertIn(constants.UPDATED_PORTS, failover_flow.provides) + self.assertIn(constants.AMPHORA, failover_flow.provides) +@@ -363,7 +366,7 @@ class TestLoadBalancerFlows(base.TestCase): + self.assertIn(constants.SUBNET, failover_flow.provides) + self.assertIn(constants.NEW_AMPHORAE, failover_flow.provides) + +- self.assertEqual(6, len(failover_flow.requires), ++ self.assertEqual(7, len(failover_flow.requires), + failover_flow.requires) + self.assertEqual(17, len(failover_flow.provides), + failover_flow.provides) +@@ -423,6 +426,7 @@ class TestLoadBalancerFlows(base.TestCase): + self.assertIn(constants.FLAVOR, failover_flow.requires) + self.assertIn(constants.LOADBALANCER, failover_flow.requires) + self.assertIn(constants.LOADBALANCER_ID, failover_flow.requires) ++ self.assertIn(constants.AMP_ERR_RETRIES, failover_flow.requires) + + self.assertIn(constants.UPDATED_PORTS, failover_flow.provides) + self.assertIn(constants.AMPHORA, failover_flow.provides) +@@ -443,7 +447,7 @@ class TestLoadBalancerFlows(base.TestCase): + self.assertIn(constants.SUBNET, failover_flow.provides) + self.assertIn(constants.NEW_AMPHORAE, failover_flow.provides) + +- self.assertEqual(6, len(failover_flow.requires), ++ self.assertEqual(7, len(failover_flow.requires), + failover_flow.requires) + self.assertEqual(17, len(failover_flow.provides), + failover_flow.provides) +diff --git a/octavia/tests/unit/controller/worker/v2/test_controller_worker.py b/octavia/tests/unit/controller/worker/v2/test_controller_worker.py +index 71285ce39..ea7cc2639 100644 +--- a/octavia/tests/unit/controller/worker/v2/test_controller_worker.py ++++ b/octavia/tests/unit/controller/worker/v2/test_controller_worker.py +@@ -540,6 +540,7 @@ class TestControllerWorker(base.TestCase): + constants.FLAVOR: None, + constants.SERVER_GROUP_ID: None, + constants.AVAILABILITY_ZONE: None, ++ constants.AMP_ERR_RETRIES: 0, + } + lb_mock = mock.MagicMock() + lb_mock.listeners = [] +@@ -581,6 +582,7 @@ class TestControllerWorker(base.TestCase): + constants.FLAVOR: None, + constants.SERVER_GROUP_ID: None, + constants.AVAILABILITY_ZONE: None, ++ constants.AMP_ERR_RETRIES: 0, + } + setattr(mock_lb_repo_get.return_value, 'topology', + constants.TOPOLOGY_ACTIVE_STANDBY) +@@ -627,6 +629,7 @@ class TestControllerWorker(base.TestCase): + constants.FLAVOR: None, + constants.SERVER_GROUP_ID: None, + constants.AVAILABILITY_ZONE: None, ++ constants.AMP_ERR_RETRIES: 0, + } + + cw = controller_worker.ControllerWorker() +@@ -674,6 +677,7 @@ class TestControllerWorker(base.TestCase): + constants.FLAVOR: None, + constants.SERVER_GROUP_ID: None, + constants.AVAILABILITY_ZONE: None, ++ constants.AMP_ERR_RETRIES: 0, + } + + cw = controller_worker.ControllerWorker() +@@ -721,6 +725,7 @@ class TestControllerWorker(base.TestCase): + constants.FLAVOR: None, + constants.SERVER_GROUP_ID: None, + constants.AVAILABILITY_ZONE: None, ++ constants.AMP_ERR_RETRIES: 0, + } + + cw = controller_worker.ControllerWorker() +@@ -1560,7 +1565,9 @@ class TestControllerWorker(base.TestCase): + constants.LOADBALANCER_ID: LB_ID, + constants.SERVER_GROUP_ID: None, + constants.VIP: mock_lb.vip.to_dict(), +- constants.ADDITIONAL_VIPS: []} ++ constants.ADDITIONAL_VIPS: [], ++ constants.AMP_ERR_RETRIES: 0 ++ } + + cw = controller_worker.ControllerWorker() + cw.services_controller.reset_mock() +@@ -1617,7 +1624,9 @@ class TestControllerWorker(base.TestCase): + constants.LOADBALANCER_ID: LB_ID, + constants.SERVER_GROUP_ID: None, + constants.VIP: mock_lb.vip.to_dict(), +- constants.ADDITIONAL_VIPS: []} ++ constants.ADDITIONAL_VIPS: [], ++ constants.AMP_ERR_RETRIES: 0 ++ } + + cw = controller_worker.ControllerWorker() + cw.services_controller.reset_mock() +@@ -1674,7 +1683,9 @@ class TestControllerWorker(base.TestCase): + constants.LOADBALANCER_ID: LB_ID, + constants.SERVER_GROUP_ID: SERVER_GROUP_ID, + constants.VIP: mock_lb.vip.to_dict(), +- constants.ADDITIONAL_VIPS: []} ++ constants.ADDITIONAL_VIPS: [], ++ constants.AMP_ERR_RETRIES: 0 ++ } + + cw = controller_worker.ControllerWorker() + cw.services_controller.reset_mock() +@@ -1727,7 +1738,9 @@ class TestControllerWorker(base.TestCase): + constants.LOADBALANCER_ID: LB_ID, + constants.SERVER_GROUP_ID: SERVER_GROUP_ID, + constants.VIP: mock_lb.vip.to_dict(), +- constants.ADDITIONAL_VIPS: []} ++ constants.ADDITIONAL_VIPS: [], ++ constants.AMP_ERR_RETRIES: 0 ++ } + + cw = controller_worker.ControllerWorker() + cw.services_controller.reset_mock() +@@ -1784,7 +1797,9 @@ class TestControllerWorker(base.TestCase): + constants.LOADBALANCER_ID: LB_ID, + constants.SERVER_GROUP_ID: None, + constants.VIP: mock_lb.vip.to_dict(), +- constants.ADDITIONAL_VIPS: []} ++ constants.ADDITIONAL_VIPS: [], ++ constants.AMP_ERR_RETRIES: 0 ++ } + mock_get_flavor_meta.return_value = {'taste': 'spicy'} + + cw = controller_worker.ControllerWorker() +@@ -1842,7 +1857,9 @@ class TestControllerWorker(base.TestCase): + constants.LOADBALANCER_ID: LB_ID, + constants.SERVER_GROUP_ID: None, + constants.VIP: mock_lb.vip.to_dict(), +- constants.ADDITIONAL_VIPS: []} ++ constants.ADDITIONAL_VIPS: [], ++ constants.AMP_ERR_RETRIES: 0 ++ } + mock_get_az_meta.return_value = {'planet': 'jupiter'} + + cw = controller_worker.ControllerWorker() +@@ -1903,7 +1920,9 @@ class TestControllerWorker(base.TestCase): + constants.ADDITIONAL_VIPS: [ + add_vips.to_dict() + for add_vips in mock_additional_vips +- ]} ++ ], ++ constants.AMP_ERR_RETRIES: 0 ++ } + + cw = controller_worker.ControllerWorker() + cw.services_controller.reset_mock() +@@ -2016,7 +2035,9 @@ class TestControllerWorker(base.TestCase): + constants.LOADBALANCER_ID: None, + constants.SERVER_GROUP_ID: None, + constants.VIP: {}, +- constants.ADDITIONAL_VIPS: []} ++ constants.ADDITIONAL_VIPS: [], ++ constants.AMP_ERR_RETRIES: 0 ++ } + + cw = controller_worker.ControllerWorker() + cw.services_controller.reset_mock() +@@ -2217,7 +2238,9 @@ class TestControllerWorker(base.TestCase): + _load_balancer_mock[ + constants.SERVER_GROUP_ID], + constants.FLAVOR: expected_flavor, +- constants.AVAILABILITY_ZONE: {}} ++ constants.AVAILABILITY_ZONE: {}, ++ constants.AMP_ERR_RETRIES: 0 ++ } + + cw = controller_worker.ControllerWorker() + cw.failover_loadbalancer(LB_ID) +@@ -2269,7 +2292,9 @@ class TestControllerWorker(base.TestCase): + constants.SERVER_GROUP_ID: + load_balancer_mock.server_group_id, + constants.FLAVOR: expected_flavor, +- constants.AVAILABILITY_ZONE: {}} ++ constants.AVAILABILITY_ZONE: {}, ++ constants.AMP_ERR_RETRIES: 0 ++ } + + cw = controller_worker.ControllerWorker() + cw.failover_loadbalancer(LB_ID) +@@ -2370,7 +2395,9 @@ class TestControllerWorker(base.TestCase): + constants.SERVER_GROUP_ID: + load_balancer_mock.server_group_id, + constants.AVAILABILITY_ZONE: { +- 'planet': 'jupiter'}} ++ 'planet': 'jupiter'}, ++ constants.AMP_ERR_RETRIES: 0 ++ } + + cw = controller_worker.ControllerWorker() + cw.failover_loadbalancer(LB_ID) +@@ -2424,7 +2451,9 @@ class TestControllerWorker(base.TestCase): + constants.FLAVOR: expected_flavor, + constants.SERVER_GROUP_ID: + load_balancer_mock.server_group_id, +- constants.AVAILABILITY_ZONE: {}} ++ constants.AVAILABILITY_ZONE: {}, ++ constants.AMP_ERR_RETRIES: 0 ++ } + + cw = controller_worker.ControllerWorker() + cw.failover_loadbalancer(LB_ID) diff --git a/releasenotes/notes/allow-error-amphora-failover-ab882982adc05f01.yaml b/releasenotes/notes/allow-error-amphora-failover-ab882982adc05f01.yaml new file mode 100644 index 000000000..5fabe45ff