From 1a3cda9f40728ded855d8045771ac38f8d8afd81 Mon Sep 17 00:00:00 2001 From: Tao Zou Date: Tue, 25 Jan 2022 09:21:18 +0800 Subject: [PATCH] Fix the logical port created twice Logical port creation is a POST request. Sometimes it will trigger ConnectionResetError which is a IOError. request_with_retry_on_ssl_error will retry it. If request has parameter retry_confirm, exception will be raised so ncp could query if port has been created to avoid creating port twice. Change-Id: Ic97b39c7a3736f02a79ab891970c1ad67b123156 (cherry picked from commit ac224a85a83821c759295705a7cebfad5bc0613e) --- .../tests/unit/v3/policy/test_resources.py | 2 +- vmware_nsxlib/v3/client.py | 16 +++++++++++----- vmware_nsxlib/v3/cluster.py | 10 +++++++++- vmware_nsxlib/v3/constants.py | 3 +++ vmware_nsxlib/v3/exceptions.py | 4 ++++ vmware_nsxlib/v3/resources.py | 5 +++-- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/vmware_nsxlib/tests/unit/v3/policy/test_resources.py b/vmware_nsxlib/tests/unit/v3/policy/test_resources.py index 43ee6136..cf351aa6 100644 --- a/vmware_nsxlib/tests/unit/v3/policy/test_resources.py +++ b/vmware_nsxlib/tests/unit/v3/policy/test_resources.py @@ -2613,7 +2613,7 @@ class TestPolicyEnforcementPoint(NsxPolicyLibTestCase): tenant=TEST_TENANT) api_post.assert_called_once_with( expected_def.get_resource_path() + '?action=reload', - None, expected_results=None, headers=None) + None, expected_results=None, headers=None, retry_confirm=False) class TestPolicyDeploymentMap(NsxPolicyLibTestCase): diff --git a/vmware_nsxlib/v3/client.py b/vmware_nsxlib/v3/client.py index 030f846d..f29e7227 100644 --- a/vmware_nsxlib/v3/client.py +++ b/vmware_nsxlib/v3/client.py @@ -22,6 +22,7 @@ from oslo_serialization import jsonutils import requests from vmware_nsxlib._i18n import _ +from vmware_nsxlib.v3 import constants from vmware_nsxlib.v3 import exceptions from vmware_nsxlib.v3 import utils @@ -173,9 +174,10 @@ class RESTClient(object): expected_results=expected_results) def create(self, resource='', body=None, headers=None, - expected_results=None): + expected_results=None, retry_confirm=False): return self.url_post(resource, body, headers=headers, - expected_results=expected_results) + expected_results=expected_results, + retry_confirm=retry_confirm) def patch(self, resource='', body=None, headers=None): return self.url_patch(resource, body, headers=headers) @@ -205,9 +207,11 @@ class RESTClient(object): return self._rest_call(url, method='PUT', body=body, headers=headers, expected_results=expected_results) - def url_post(self, url, body, headers=None, expected_results=None): + def url_post(self, url, body, headers=None, expected_results=None, + retry_confirm=False): return self._rest_call(url, method='POST', body=body, headers=headers, - expected_results=expected_results) + expected_results=expected_results, + retry_confirm=retry_confirm) def url_patch(self, url, body, headers=None): return self._rest_call(url, method='PATCH', body=body, headers=headers) @@ -268,6 +272,7 @@ class RESTClient(object): silent=False, expected_results=None, **kwargs): request_headers = headers.copy() if headers else {} request_headers.update(self._default_headers) + retry_confirm = kwargs.pop(constants.API_RETRY_CONFIRM, False) if utils.INJECT_HEADERS_CALLBACK: inject_headers = utils.INJECT_HEADERS_CALLBACK() @@ -281,7 +286,8 @@ class RESTClient(object): result = do_request( request_url, data=body, - headers=request_headers) + headers=request_headers, + retry_confirm=retry_confirm) te = time.time() if silent: self._conn.set_silent(False) diff --git a/vmware_nsxlib/v3/cluster.py b/vmware_nsxlib/v3/cluster.py index 83e98b68..f71968ca 100644 --- a/vmware_nsxlib/v3/cluster.py +++ b/vmware_nsxlib/v3/cluster.py @@ -104,15 +104,23 @@ class TimeoutSession(requests.Session): # wrapper timeouts at the session level # see: https://goo.gl/xNk7aM def request(self, *args, **kwargs): + retry_confirm = kwargs.pop(constants.API_RETRY_CONFIRM, False) + def request_with_retry_on_ssl_error(*args, **kwargs): try: return super(TimeoutSession, self).request(*args, **kwargs) - except (IOError, OpenSSL.SSL.Error): + except (IOError, OpenSSL.SSL.Error) as err: # This can happen when connection tries to access certificate # file it was opened with (renegotiation?) # Proper way to solve this would be to pass in-memory cert # to ssl C code. # Retrying here works around the problem + + # IOError covered too much Exception, if resources created + # successfully on nsxt side but ConnectResetError happened, + # Retrying here will create resource twice + if isinstance(err, IOError) and retry_confirm: + raise exceptions.RetryConfirm(msg=err) return super(TimeoutSession, self).request(*args, **kwargs) def get_cert_provider(): diff --git a/vmware_nsxlib/v3/constants.py b/vmware_nsxlib/v3/constants.py index 5809c1e7..9fa8a0ac 100644 --- a/vmware_nsxlib/v3/constants.py +++ b/vmware_nsxlib/v3/constants.py @@ -127,3 +127,6 @@ API_DEFAULT_MAX_RATE = 100 # API Call Log Related const API_CALL_LOG_PER_CLUSTER = 'API_LOG_PER_CLUSTER' API_CALL_LOG_PER_ENDPOINT = 'API_LOG_PER_ENDPOINT' + +# API Retry confirm parameter +API_RETRY_CONFIRM = 'retry_confirm' diff --git a/vmware_nsxlib/v3/exceptions.py b/vmware_nsxlib/v3/exceptions.py index c2e2c794..095d369c 100644 --- a/vmware_nsxlib/v3/exceptions.py +++ b/vmware_nsxlib/v3/exceptions.py @@ -238,6 +238,10 @@ class CannotConnectToServer(ManagerError): message = _("Cannot connect to server") +class RetryConfirm(NsxLibException): + message = _("Retry will be handled by upper layer: %(msg)s") + + class ResourceInUse(ManagerError): message = _("The object cannot be deleted as either it has children or it " "is being referenced by other objects") diff --git a/vmware_nsxlib/v3/resources.py b/vmware_nsxlib/v3/resources.py index 15abfcce..3bebfefe 100644 --- a/vmware_nsxlib/v3/resources.py +++ b/vmware_nsxlib/v3/resources.py @@ -164,7 +164,7 @@ class LogicalPort(utils.NsxLibApiBase): switch_profile_ids=None, vif_type=None, app_id=None, allocate_addresses=nsx_constants.ALLOCATE_ADDRESS_NONE, description=None, tn_uuid=None, - extra_configs=None): + extra_configs=None, retry_confirm=False): tags = tags or [] body = {'logical_switch_id': lswitch_id} # NOTE(arosen): If parent_vif_id is specified we need to use @@ -181,7 +181,8 @@ class LogicalPort(utils.NsxLibApiBase): attachment=attachment, description=description, extra_configs=extra_configs)) - return self.client.create(self.get_path(), body=body) + return self.client.create(self.get_path(), body=body, + retry_confirm=retry_confirm) def delete(self, lport_id): self._delete_with_retry('%s?detach=true' % lport_id)