diff --git a/tacker/api/vnflcm/v1/controller.py b/tacker/api/vnflcm/v1/controller.py index 338d053f9..30a3af245 100644 --- a/tacker/api/vnflcm/v1/controller.py +++ b/tacker/api/vnflcm/v1/controller.py @@ -940,6 +940,15 @@ class VnfLcmController(wsgi.Controller): filter_uni = subscription_request_data.get('filter') filter = ast.literal_eval(str(filter_uni).replace("'", "'")) + if CONF.vnf_lcm.test_callback_uri: + resp = self._test_notification(request.context, + vnf_lcm_subscription) + if resp == -1: + LOG.exception(traceback.format_exc()) + return self._make_problem_detail( + 'Failed to Test Notification', 400, + title='Bad Request') + try: vnf_lcm_subscription = vnf_lcm_subscription.create(filter) LOG.debug("vnf_lcm_subscription %s" % vnf_lcm_subscription) @@ -1526,6 +1535,11 @@ class VnfLcmController(wsgi.Controller): res.status_int = status return res + def _test_notification(self, context, vnf_lcm_subscription): + resp = self.rpc_api.test_notification(context, + vnf_lcm_subscription, cast=False) + return resp + def create_resource(): return wsgi.Resource(VnfLcmController()) diff --git a/tacker/conductor/conductor_server.py b/tacker/conductor/conductor_server.py index 6d17e521a..f6d6fb796 100644 --- a/tacker/conductor/conductor_server.py +++ b/tacker/conductor/conductor_server.py @@ -1467,11 +1467,6 @@ class Conductor(manager.Manager): "Failed to send notification {}. Details: {}".format( vnf_lcm_op_occs_id, str(ex))) - def _retry_check(self, retry_count): - time.sleep(CONF.vnf_lcm.retry_wait) - if retry_count == CONF.vnf_lcm.retry_num: - LOG.warn("Number of retries exceeded retry count") - def send_notification(self, context, notification): """Function to send notification to client @@ -1574,6 +1569,65 @@ class Conductor(manager.Manager): return -2 return 0 + def _retry_check(self, retry_count): + time.sleep(CONF.vnf_lcm.retry_wait) + if retry_count == CONF.vnf_lcm.retry_num: + LOG.warn( + "Number of retries exceeded retry count [%s]" % + CONF.vnf_lcm.retry_num) + + def test_notification(self, context, vnf_lcm_subscription=None): + """Function to send test notification to client + + This function is used to send test notification + to client during Register Subscription. + + :returns: 0 if status code of the response is 204 + or if CONF.vnf_lcm.test_callback_uri is False, + -1 if status code of the response is not 204 + """ + + if not CONF.vnf_lcm.test_callback_uri: + LOG.warning("Callback URI is %s", CONF.vnf_lcm.test_callback_uri) + return 0 + + # Notification shipping + for num in range(CONF.vnf_lcm.retry_num): + try: + auth_client = auth.auth_manager.get_auth_client( + vnf_lcm_subscription.id) + + notification = {} + response = auth_client.get( + vnf_lcm_subscription.callback_uri, + data=json.dumps(notification), + timeout=CONF.vnf_lcm.retry_timeout) + + if response.status_code == 204: + return 0 + else: + LOG.warning( + "Notification failed status[%s] \ + callback_uri[%s]" % + (response.status_code, + vnf_lcm_subscription.callback_uri)) + LOG.debug( + "retry_wait %s" % + CONF.vnf_lcm.retry_wait) + self._retry_check(num) + + continue + except requests.Timeout as e: + LOG.warning("Notification request timed out." + " callback_uri[%(uri)s]" + " reason[%(reason)s]", { + "uri": vnf_lcm_subscription.callback_uri, + "reason": str(e)}) + self._retry_check(num) + + # return -1 since the response is not 204 + return -1 + @coordination.synchronized('{vnf_instance[id]}') def instantiate( self, diff --git a/tacker/conductor/conductorrpc/vnf_lcm_rpc.py b/tacker/conductor/conductorrpc/vnf_lcm_rpc.py index 72e62664f..e64cb681c 100644 --- a/tacker/conductor/conductorrpc/vnf_lcm_rpc.py +++ b/tacker/conductor/conductorrpc/vnf_lcm_rpc.py @@ -113,6 +113,17 @@ class VNFLcmRPCAPI(object): return rpc_method(context, 'send_notification', notification=notification) + def test_notification(self, context, + vnf_lcm_subscription=None, cast=False): + serializer = objects_base.TackerObjectSerializer() + + client = rpc.get_client(self.target, version_cap=None, + serializer=serializer) + cctxt = client.prepare() + rpc_method = cctxt.cast if cast else cctxt.call + return rpc_method(context, 'test_notification', + vnf_lcm_subscription=vnf_lcm_subscription) + def rollback(self, context, vnf_info, vnf_instance, operation_params, cast=True): serializer = objects_base.TackerObjectSerializer() diff --git a/tacker/conf/vnf_lcm.py b/tacker/conf/vnf_lcm.py index be97e95c1..3d116b6c0 100644 --- a/tacker/conf/vnf_lcm.py +++ b/tacker/conf/vnf_lcm.py @@ -35,7 +35,11 @@ OPTS = [ cfg.IntOpt( 'retry_timeout', default=10, - help="Retry Timeout(sec)")] + help="Retry Timeout(sec)"), + cfg.BoolOpt( + 'test_callback_uri', + default=True, + help="Test callbackUri")] vnf_lcm_group = cfg.OptGroup('vnf_lcm', title='vnf_lcm options', diff --git a/tacker/tests/functional/sol/vnflcm/base.py b/tacker/tests/functional/sol/vnflcm/base.py index e66a78162..3412b7084 100644 --- a/tacker/tests/functional/sol/vnflcm/base.py +++ b/tacker/tests/functional/sol/vnflcm/base.py @@ -237,6 +237,11 @@ class BaseVnfLcmTest(base.BaseTackerTest): callback_url, status_code=204 ) + FAKE_SERVER_MANAGER.set_callback( + 'GET', + callback_url, + status_code=204 + ) self.tacker_client = base.BaseTackerTest.tacker_http_client() @@ -1089,6 +1094,14 @@ class BaseVnfLcmTest(base.BaseTackerTest): 'VnfLcmOperationOccurrenceNotification', 'COMPLETED') + def assert_notification_get(self, callback_url): + notify_mock_responses = FAKE_SERVER_MANAGER.get_history( + callback_url) + FAKE_SERVER_MANAGER.clear_history( + callback_url) + self.assertEqual(1, len(notify_mock_responses)) + self.assertEqual(204, notify_mock_responses[0].status_code) + def assert_notification_mock_response( self, notify_mock_response, diff --git a/tacker/tests/functional/sol/vnflcm/test_vnf_instance_with_user_data.py b/tacker/tests/functional/sol/vnflcm/test_vnf_instance_with_user_data.py index d00c60c9b..b1157827b 100644 --- a/tacker/tests/functional/sol/vnflcm/test_vnf_instance_with_user_data.py +++ b/tacker/tests/functional/sol/vnflcm/test_vnf_instance_with_user_data.py @@ -111,14 +111,16 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): - Delete subscription """ # Create subscription and register it. + callback_url = os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) request_body = fake_vnflcm.Subscription.make_create_request_body( 'http://localhost:{}{}'.format( vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT, - os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, - self._testMethodName))) + callback_url)) resp, response_body = self._register_subscription(request_body) self.assertEqual(201, resp.status_code) self.assert_http_header_location_for_subscription(resp.headers) + self.assert_notification_get(callback_url) subscription_id = response_body.get('id') self.addCleanup( self._delete_subscription, @@ -237,14 +239,16 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): - Delete subscription. """ # Create subscription and register it. + callback_url = os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) request_body = fake_vnflcm.Subscription.make_create_request_body( 'http://localhost:{}{}'.format( vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT, - os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, - self._testMethodName))) + callback_url)) resp, response_body = self._register_subscription(request_body) self.assertEqual(201, resp.status_code) self.assert_http_header_location_for_subscription(resp.headers) + self.assert_notification_get(callback_url) subscription_id = response_body.get('id') self.addCleanup(self._delete_subscription, subscription_id) @@ -388,14 +392,16 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): - Delete subscription. """ # Create subscription and register it. + callback_url = os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) request_body = fake_vnflcm.Subscription.make_create_request_body( 'http://localhost:{}{}'.format( vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT, - os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, - self._testMethodName))) + callback_url)) resp, response_body = self._register_subscription(request_body) self.assertEqual(201, resp.status_code) self.assert_http_header_location_for_subscription(resp.headers) + self.assert_notification_get(callback_url) subscription_id = response_body.get('id') self.addCleanup(self._delete_subscription, subscription_id) @@ -1003,14 +1009,16 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): - Delete subscription. """ # Create subscription and register it. + callback_url = os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) request_body = fake_vnflcm.Subscription.make_create_request_body( 'http://localhost:{}{}'.format( vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT, - os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, - self._testMethodName))) + callback_url)) resp, response_body = self._register_subscription(request_body) self.assertEqual(201, resp.status_code) self.assert_http_header_location_for_subscription(resp.headers) + self.assert_notification_get(callback_url) subscription_id = response_body.get('id') self.addCleanup(self._delete_subscription, subscription_id) @@ -1098,14 +1106,16 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): - Delete subscription. """ # Create subscription and register it. + callback_url = os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) request_body = fake_vnflcm.Subscription.make_create_request_body( 'http://localhost:{}{}'.format( vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT, - os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, - self._testMethodName))) + callback_url)) resp, response_body = self._register_subscription(request_body) self.assertEqual(201, resp.status_code) self.assert_http_header_location_for_subscription(resp.headers) + self.assert_notification_get(callback_url) subscription_id = response_body.get('id') self.addCleanup(self._delete_subscription, subscription_id) @@ -1205,6 +1215,7 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): In this test case, we do following steps. - Create subscription. + - Test notification. - Create VNF package. - Upload VNF package. - Create VNF instance. @@ -1226,6 +1237,10 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): subscription_id = response_body.get('id') self.addCleanup(self._delete_subscription, subscription_id) + self.assert_notification_get( + os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName)) + # Pre Setting: Create vnf package. sample_name = 'functional3' csar_package_path = os.path.abspath( @@ -1303,6 +1318,7 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): In this test case, we do following steps. - Create subscription. + - Test notification. - Create VNF package. - Upload VNF package. - Create VNF instance. @@ -1326,6 +1342,10 @@ class VnfLcmWithUserDataTest(vnflcm_base.BaseVnfLcmTest): subscription_id = response_body.get('id') self.addCleanup(self._delete_subscription, subscription_id) + self.assert_notification_get( + os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName)) + # Pre Setting: Create vnf package. sample_name = 'functional4' csar_package_path = os.path.abspath( diff --git a/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py b/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py index 0bcafa2c4..9767d83b0 100644 --- a/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py +++ b/tacker/tests/functional/sol_separated_nfvo/vnflcm/test_vnf_instance_with_user_data_nfvo_separate.py @@ -108,15 +108,16 @@ class VnfLcmWithNfvoSeparator(vnflcm_base.BaseVnfLcmTest): glance_image = self._list_glance_image()[0] # Create subscription and register it. + callback_url = os.path.join(vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, + self._testMethodName) request_body = fake_vnflcm.Subscription.make_create_request_body( 'http://localhost:{}{}'.format( vnflcm_base.FAKE_SERVER_MANAGER.SERVER_PORT, - os.path.join( - vnflcm_base.MOCK_NOTIFY_CALLBACK_URL, - self._testMethodName))) + callback_url)) resp, response_body = self._register_subscription(request_body) self.assertEqual(201, resp.status_code) self.assert_http_header_location_for_subscription(resp.headers) + self.assert_notification_get(callback_url) subscription_id = response_body.get('id') self.addCleanup(self._delete_subscription, subscription_id) diff --git a/tacker/tests/unit/conductor/test_conductor_server.py b/tacker/tests/unit/conductor/test_conductor_server.py index 338c5be6a..d01c881b0 100644 --- a/tacker/tests/unit/conductor/test_conductor_server.py +++ b/tacker/tests/unit/conductor/test_conductor_server.py @@ -2734,7 +2734,7 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): @mock.patch.object(objects.LccnSubscriptionRequest, 'vnf_lcm_subscriptions_get') - def test_send_notification_rety_notification(self, + def test_send_notification_retry_notification(self, mock_subscriptions_get): self.requests_mock.register_uri('POST', "https://localhost/callback", @@ -2760,7 +2760,7 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): @mock.patch.object(objects.LccnSubscriptionRequest, 'vnf_lcm_subscriptions_get') - def test_sendNotification_sendError(self, + def test_send_notification_send_error(self, mock_subscriptions_get): self.requests_mock.register_uri( 'POST', @@ -2818,10 +2818,118 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase): # return value when timeout for POST method is 0 self.assertEqual(result, 0) + def test_get_notification(self): + cfg.CONF.set_override('test_callback_uri', True, + group='vnf_lcm') + self.requests_mock.register_uri('GET', + "https://localhost/callback", + headers={ + 'Content-Type': 'application/json'}, + status_code=204) + + callback_uri = 'https://localhost/callback' + + vnf_lcm_subscription = objects.\ + LccnSubscriptionRequest(context=self.context) + vnf_lcm_subscription.id = uuidsentinel.lcm_subscription_id + vnf_lcm_subscription.callback_uri = callback_uri + + result = self.conductor.test_notification(self.context, + vnf_lcm_subscription) + + # return value when successful for GET method is 0 + self.assertEqual(result, 0) + + history = self.requests_mock.request_history + req_count = nfvo_client._count_mock_history( + history, "https://localhost") + self.assertEqual(1, req_count) + cfg.CONF.set_override('test_callback_uri', False, + group='vnf_lcm') + + def test_get_notification_callback_uri_false(self): + cfg.CONF.set_override('test_callback_uri', False, + group='vnf_lcm') + self.requests_mock.register_uri('GET', + "https://localhost/callback", + headers={ + 'Content-Type': 'application/json'}, + status_code=204) + + callback_uri = 'https://localhost/callback' + + vnf_lcm_subscription = objects.\ + LccnSubscriptionRequest(context=self.context) + vnf_lcm_subscription.id = uuidsentinel.lcm_subscription_id + vnf_lcm_subscription.callback_uri = callback_uri + + result = self.conductor.test_notification(self.context, + vnf_lcm_subscription) + + # return value when successful for GET method is 0 + self.assertEqual(result, 0) + + history = self.requests_mock.request_history + req_count = nfvo_client._count_mock_history( + history, "https://localhost") + self.assertEqual(0, req_count) + + def test_get_notification_retry(self): + cfg.CONF.set_override('test_callback_uri', True, + group='vnf_lcm') + self.requests_mock.register_uri('GET', + "https://localhost/callback", + headers={ + 'Content-Type': 'application/json'}, + status_code=400) + + callback_uri = 'https://localhost/callback' + + vnf_lcm_subscription = objects.\ + LccnSubscriptionRequest(context=self.context) + vnf_lcm_subscription.id = uuidsentinel.lcm_subscription_id + vnf_lcm_subscription.callback_uri = callback_uri + + result = self.conductor.test_notification(self.context, + vnf_lcm_subscription) + + # return value when error occurs for GET method is -1 + self.assertEqual(result, -1) + history = self.requests_mock.request_history req_count = nfvo_client._count_mock_history( history, "https://localhost") self.assertEqual(3, req_count) + cfg.CONF.set_override('test_callback_uri', False, + group='vnf_lcm') + + def test_get_notification_timeout(self): + cfg.CONF.set_override('test_callback_uri', True, + group='vnf_lcm') + self.requests_mock.register_uri( + 'GET', + "https://localhost/callback", + exc=requests.Timeout) + + callback_uri = 'https://localhost/callback' + + vnf_lcm_subscription = objects.\ + LccnSubscriptionRequest(context=self.context) + vnf_lcm_subscription.id = uuidsentinel.lcm_subscription_id + vnf_lcm_subscription.callback_uri = callback_uri + + result = self.conductor.test_notification(self.context, + vnf_lcm_subscription) + + # return value when error for GET method is -1 + self.assertEqual(result, -1) + + history = self.requests_mock.request_history + req_count = nfvo_client._count_mock_history( + history, "https://localhost") + self.assertEqual(3, req_count) + cfg.CONF.set_override('test_callback_uri', False, + group='vnf_lcm') @mock.patch.object(conductor_server, 'revert_update_lcm') @mock.patch.object(t_context.get_admin_context().session, "add")