blob: bffb9f3b32ac8183b032206034472a36b0cf861b [file]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#
import asyncio
import threading
import unittest
from http.server import BaseHTTPRequestHandler, HTTPServer
from unittest.mock import AsyncMock, MagicMock, patch
from skywalking import config
class _OkHandler(BaseHTTPRequestHandler):
def do_POST(self): # noqa
length = int(self.headers.get('Content-Length') or 0)
self.rfile.read(length)
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(b'{}')
def log_message(self, *_args):
pass
def _start_http_server():
server = HTTPServer(('127.0.0.1', 0), _OkHandler)
threading.Thread(target=server.serve_forever, daemon=True).start()
return server, server.server_address[1]
class TestAsyncHttpClientSession(unittest.TestCase):
def setUp(self):
self._saved = (
config.agent_collector_backend_services,
config.agent_protocol,
config.agent_name,
config.agent_instance_name,
)
config.agent_protocol = 'http'
config.agent_name = 'test-service'
config.agent_instance_name = 'test-instance'
def tearDown(self):
(
config.agent_collector_backend_services,
config.agent_protocol,
config.agent_name,
config.agent_instance_name,
) = self._saved
def test_async_http_session_survives_repeated_posts(self):
"""async with session.post closes the response, not the long-lived session."""
server, port = _start_http_server()
config.agent_collector_backend_services = f'127.0.0.1:{port}'
try:
from skywalking.client.http_aio import HttpServiceManagementClientAsync
async def run():
client = HttpServiceManagementClientAsync()
self.assertFalse(client.client.closed)
await client.send_instance_props()
self.assertFalse(
client.client.closed,
'ClientSession must stay open after the first request',
)
await client.send_heart_beat()
self.assertFalse(client.client.closed)
await client.aclose()
self.assertTrue(client.client.closed)
asyncio.run(run())
finally:
server.shutdown()
def test_async_http_segment_reporter_reuses_session(self):
server, port = _start_http_server()
config.agent_collector_backend_services = f'127.0.0.1:{port}'
try:
from skywalking.client.http_aio import HttpTraceSegmentReportServiceAsync
class _Seg:
related_traces = ['t1']
segment_id = 's1'
is_size_limited = False
spans = []
async def gen():
yield _Seg()
yield _Seg()
async def run():
reporter = HttpTraceSegmentReportServiceAsync()
await reporter.report(gen())
self.assertFalse(reporter.client.closed)
await reporter.aclose()
asyncio.run(run())
finally:
server.shutdown()
def test_async_http_heartbeat_with_aiohttp_plugin_installed(self):
from aiohttp import ClientSession
from aiohttp.web_protocol import RequestHandler
from skywalking.agent.protocol.http_aio import HttpProtocolAsync
from skywalking.plugins import sw_aiohttp
self.addCleanup(setattr, ClientSession, '_request', ClientSession._request)
self.addCleanup(setattr, RequestHandler, '_handle_request', RequestHandler._handle_request)
sw_aiohttp.install()
server, port = _start_http_server()
self.addCleanup(server.server_close)
self.addCleanup(server.shutdown)
config.agent_collector_backend_services = f'127.0.0.1:{port}'
async def run():
protocol = HttpProtocolAsync()
sessions = [part.client for part in (
protocol.service_management, protocol.traces_reporter, protocol.log_reporter,
)]
try:
await asyncio.wait_for(protocol.heartbeat(), timeout=5)
await asyncio.wait_for(protocol.heartbeat(), timeout=5)
self.assertTrue(all(not session.closed for session in sessions))
finally:
await protocol.aclose()
self.assertTrue(all(session.closed for session in sessions))
with patch.object(_OkHandler, 'do_POST', autospec=True, side_effect=_OkHandler.do_POST) as post, \
patch.object(sw_aiohttp, 'get_context') as get_context, \
patch.object(config, 'agent_collector_properties_report_period_factor', 10):
asyncio.run(run())
self.assertEqual(post.call_count, 3)
get_context.assert_not_called()
class TestAiohttpServerCollectorSkip(unittest.TestCase):
"""RequestHandler skip must await the original handler, not return the function."""
def setUp(self):
self._saved = (
config.agent_collector_backend_services,
config.agent_protocol,
)
config.agent_protocol = 'http'
config.agent_collector_backend_services = '127.0.0.1:12800'
def tearDown(self):
(
config.agent_collector_backend_services,
config.agent_protocol,
) = self._saved
def test_collector_host_skip_awaits_original_handler(self):
from aiohttp import ClientSession
from aiohttp.web_protocol import RequestHandler
from skywalking.plugins import sw_aiohttp
self.addCleanup(setattr, ClientSession, '_request', ClientSession._request)
self.addCleanup(setattr, RequestHandler, '_handle_request', RequestHandler._handle_request)
stub = AsyncMock(return_value=('resp', False))
RequestHandler._handle_request = stub
sw_aiohttp.install()
handler = MagicMock()
request = MagicMock()
request.url.host = '127.0.0.1'
request.url.port = 12800
async def run():
with patch.object(sw_aiohttp, 'get_context') as get_context:
result = await RequestHandler._handle_request(handler, request, 1.5, extra=True)
get_context.assert_not_called()
return result
result = asyncio.run(run())
self.assertEqual(('resp', False), result)
stub.assert_awaited_once_with(handler, request, 1.5, extra=True)
def test_non_collector_host_still_creates_entry_span(self):
from aiohttp import ClientSession
from aiohttp.web_protocol import RequestHandler
from skywalking.plugins import sw_aiohttp
self.addCleanup(setattr, ClientSession, '_request', ClientSession._request)
self.addCleanup(setattr, RequestHandler, '_handle_request', RequestHandler._handle_request)
resp = MagicMock()
resp.status = 200
stub = AsyncMock(return_value=(resp, False))
RequestHandler._handle_request = stub
sw_aiohttp.install()
handler = MagicMock()
request = MagicMock()
request.url.host = '127.0.0.1'
request.url.port = 9999
request.method = 'GET'
request.path = '/users'
request.headers = {}
request._transport_peername = ('10.0.0.1', 1234)
request.scheme = 'http'
request.host = '127.0.0.1:9999'
span = MagicMock()
context = MagicMock()
context.new_entry_span.return_value = span
async def run():
with patch.object(sw_aiohttp, 'get_context', return_value=context):
return await RequestHandler._handle_request(handler, request, 0.0)
result = asyncio.run(run())
self.assertEqual((resp, False), result)
context.new_entry_span.assert_called_once()
stub.assert_awaited_once()
if __name__ == '__main__':
unittest.main()