Skip to content

Commit

Permalink
feat: fix sentry test and add release data to sentry errors
Browse files Browse the repository at this point in the history
Refactored the mock test server to use bottle for easier mocking. Added
full release name/version to sentry errors and refactored sentry error
handling in client.rs to easily add event tags as desired before the
event is submitted.

Closes #66
  • Loading branch information
bbangert committed Aug 30, 2018
1 parent 617e9ef commit 8e59c67
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 91 deletions.
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ click==6.7
configargparse==0.13.0
constantly==15.1.0 # via twisted
contextlib2==0.5.5 # via raven
cryptography==2.2.2
cryptography==2.3.1
cyclone==1.1
datadog==0.20.0
decorator==4.2.1 # via datadog
Expand All @@ -39,11 +39,11 @@ objgraph==3.4.0
pyasn1-modules==0.2.1 # via service-identity
pyasn1==0.4.2
pycparser==2.18 # via cffi
pycryptodome==3.5.1 # via python-jose
pycryptodome==3.6.6 # via python-jose
pyfcm==1.4.5
pyopenssl==17.5.0
python-dateutil==2.6.1 # via botocore
python-jose==2.0.2
python-jose==3.0.1
raven==6.6.0
requests-toolbelt==0.8.0 # via pyfcm
requests==2.18.4
Expand Down
19 changes: 13 additions & 6 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ use futures::AsyncSink;
use futures::{Async, Future, Poll, Sink, Stream};
use reqwest::unstable::async::Client as AsyncClient;
use rusoto_dynamodb::UpdateItemOutput;
use sentry::integrations::error_chain::capture_error_chain;
use sentry;
use sentry::integrations::error_chain::event_from_error_chain;
use state_machine_future::RentToOwn;
use tokio_core::reactor::Timeout;
use uuid::Uuid;
Expand Down Expand Up @@ -461,10 +462,7 @@ where
| Err(Error(ErrorKind::Io(_), _))
| Err(Error(ErrorKind::PongTimeout, _))
| Err(Error(ErrorKind::RepeatUaidDisconnect, _)) => None,
Err(e) => {
capture_error_chain(&e);
Some(e.display_chain().to_string())
}
Err(e) => Some(e),
}
};

Expand Down Expand Up @@ -502,6 +500,15 @@ where
// If there's direct unack'd messages, they need to be saved out without blocking
// here
srv.disconnet_client(&webpush.uaid, &webpush.uid);

// Log out the sentry message if applicable and convert to error msg
let error = if let Some(ref err) = error {
let mut event = event_from_error_chain(err);
sentry::capture_event(event);
err.display_chain().to_string()
} else {
"".to_string()
};
let mut stats = webpush.stats.clone();
let unacked_direct_notifs = webpush.unacked_direct_notifs.len();
if unacked_direct_notifs > 0 {
Expand Down Expand Up @@ -538,7 +545,7 @@ where
"nacks" => stats.nacks,
"registers" => stats.registers,
"unregisters" => stats.unregisters,
"disconnect_reason" => error.unwrap_or("".to_string()),
"disconnect_reason" => error,
);
transition!(UnAuthDone(()))
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ extern crate reqwest;
extern crate rusoto_core;
extern crate rusoto_credential;
extern crate rusoto_dynamodb;
#[macro_use]
extern crate sentry;
extern crate serde;
#[macro_use]
Expand Down
8 changes: 7 additions & 1 deletion src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,13 @@ impl Server {
/// Setup Sentry logging if a SENTRY_DSN exists
fn start_sentry() -> Option<sentry::internals::ClientInitGuard> {
if let Ok(dsn) = env::var("SENTRY_DSN") {
let guard = sentry::init(dsn);
let guard = sentry::init((
dsn,
sentry::ClientOptions {
release: sentry_crate_release!(),
..Default::default()
}
));
register_panic_handler();
Some(guard)
} else {
Expand Down
138 changes: 57 additions & 81 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
CN_SERVER = None
CN_MP_SERVER = None
MOCK_SERVER_THREAD = None
MOCK_SENTRY_THREAD = None
CN_QUEUES = []


Expand All @@ -89,8 +88,10 @@ def get_free_port():
return port


MOCK_MP_SERVER_PORT = get_free_port()
MOCK_SENTRY_PORT = get_free_port()
MOCK_SERVER_PORT = get_free_port()
MOCK_MP_SERVICES = {}
MOCK_MP_TOKEN = "Bearer {}".format(uuid.uuid4().hex)
MOCK_MP_POLLED = Event()
MOCK_SENTRY_QUEUE = Queue()


Expand All @@ -100,9 +101,24 @@ def enqueue_output(out, queue):
out.close()


@bottle.get("/v1/broadcasts")
def broadcast_handler():
assert bottle.request.headers["Authorization"] == MOCK_MP_TOKEN
MOCK_MP_POLLED.set()
return dict(broadcasts=MOCK_MP_SERVICES)


@bottle.post("/api/1/store/")
def sentry_handler():
content = bottle.request.json
MOCK_SENTRY_QUEUE.put(content)
return {
"id": "fc6d8c0c43fc4630ad850ee518f1b9d0"
}


def setup_module():
global CN_SERVER, CN_QUEUES, CN_MP_SERVER, MOCK_SERVER_THREAD, \
MOCK_SENTRY_THREAD
global CN_SERVER, CN_QUEUES, CN_MP_SERVER, MOCK_SERVER_THREAD
ap_tests.ddb_jar = os.path.join(root_dir, "ddb", "DynamoDBLocal.jar")
ap_tests.ddb_lib_dir = os.path.join(root_dir, "ddb", "DynamoDBLocal_lib")
ap_tests.setUp()
Expand Down Expand Up @@ -139,6 +155,10 @@ def setup_module():
for key, val in conn_conf.items():
key = "autopush_" + key
os.environ[key.upper()] = str(val)
# Sentry API mock
os.environ["SENTRY_DSN"] = 'http://foo:bar@localhost:{}/1'.format(
MOCK_SERVER_PORT
)

cmd = [rust_bin]
CN_SERVER = subprocess.Popen(cmd, shell=True, env=os.environ,
Expand All @@ -156,31 +176,17 @@ def setup_module():
t.start()
CN_QUEUES.extend([out_q, err_q])

# Sentry API mock
os.environ["SENTRY_DSN"] = 'http://foo:bar@localhost:{}/1'.format(
MOCK_SENTRY_PORT
)
MOCK_SENTRY_THREAD = Thread(
MOCK_SERVER_THREAD = Thread(
target=bottle.run,
kwargs=dict(
port=MOCK_SENTRY_PORT, debug=True
port=MOCK_SERVER_PORT, debug=True
))
MOCK_SENTRY_THREAD.setDaemon(True)
MOCK_SENTRY_THREAD.start()

# Megaphone API mock
MockMegaphoneRequestHandler.services = {}
MockMegaphoneRequestHandler.polled.clear()

mock_server = HTTPServer(('localhost', MOCK_MP_SERVER_PORT),
MockMegaphoneRequestHandler)
MOCK_SERVER_THREAD = Thread(target=mock_server.serve_forever)
MOCK_SERVER_THREAD.setDaemon(True)
MOCK_SERVER_THREAD.start()

# Setup the megaphone connection node
megaphone_api_url = 'http://localhost:{port}/v1/broadcasts'.format(
port=MOCK_MP_SERVER_PORT)
port=MOCK_SERVER_PORT)
conn_conf.update(dict(
port=MP_CONNECTION_PORT,
endpoint_port=ENDPOINT_PORT,
Expand All @@ -190,7 +196,7 @@ def setup_module():
close_handshake_timeout=5,
max_connections=5000,
megaphone_api_url=megaphone_api_url,
megaphone_api_token=MockMegaphoneRequestHandler.token,
megaphone_api_token=MOCK_MP_TOKEN,
megaphone_poll_interval=1,
))

Expand All @@ -201,7 +207,7 @@ def setup_module():

cmd = [rust_bin]
CN_MP_SERVER = subprocess.Popen(cmd, shell=True, env=os.environ)
time.sleep(5)
time.sleep(2)


def teardown_module():
Expand All @@ -221,37 +227,6 @@ def teardown_module():
CN_MP_SERVER.wait()


class MockMegaphoneRequestHandler(BaseHTTPRequestHandler):
API_PATTERN = re.compile(r'/v1/broadcasts')
services = {}
polled = Event()
token = "Bearer {}".format(uuid.uuid4().hex)

def do_GET(self):
if re.search(self.API_PATTERN, self.path):
assert self.headers.getheader("Authorization") == self.token
self.send_response(requests.codes.ok)
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.end_headers()
response_content = json.dumps(
{"broadcasts": self.services}
)
self.wfile.write(response_content.encode('utf-8'))
self.polled.set()
return


@bottle.route("<path:re:.*>")
def sentry_handler(path):
global MOCK_SENTRY_QUEUE
log.critical("Got a request!")
content = bottle.request.json
MOCK_SENTRY_QUEUE.put(content)
return {
"id": "fc6d8c0c43fc4630ad850ee518f1b9d0"
}


class TestRustWebPush(unittest.TestCase):
_endpoint_defaults = dict(
hostname='localhost',
Expand Down Expand Up @@ -328,15 +303,14 @@ def _ws_url(self):

@inlineCallbacks
def test_sentry_output(self):
raise SkipTest("Skip until we know why locally sentry fails")
client = Client(self._ws_url)
yield client.connect()
yield client.hello()
# Send unsolicited ack and drop
yield client.ack("garbage", "data")
yield self.shut_down(client)
data = MOCK_SENTRY_QUEUE.get(timeout=1)
assert data == "Fred"
assert data["exception"]["values"][0]["value"] == "invalid json text"

@inlineCallbacks
def test_hello_echo(self):
Expand Down Expand Up @@ -979,9 +953,6 @@ def start_ep(self, ep_conf):
self.addCleanup(ep.stopService)

def setUp(self):
self.mock_server_thread = MOCK_SERVER_THREAD
self.mock_megaphone = MockMegaphoneRequestHandler

self.logs = TestingLogObserver()
begin_or_register(self.logs)
self.addCleanup(globalLogPublisher.removeObserver, self.logs)
Expand Down Expand Up @@ -1048,9 +1019,10 @@ def _ws_url(self):

@inlineCallbacks
def test_broadcast_update_on_connect(self):
self.mock_megaphone.services = {"kinto:123": "ver1"}
self.mock_megaphone.polled.clear()
self.mock_megaphone.polled.wait(timeout=5)
global MOCK_MP_SERVICES
MOCK_MP_SERVICES = {"kinto:123": "ver1"}
MOCK_MP_POLLED.clear()
MOCK_MP_POLLED.wait(timeout=5)

old_ver = {"kinto:123": "ver0"}
client = Client(self._ws_url)
Expand All @@ -1060,9 +1032,9 @@ def test_broadcast_update_on_connect(self):
assert result["use_webpush"] is True
assert result["broadcasts"]["kinto:123"] == "ver1"

self.mock_megaphone.services = {"kinto:123": "ver2"}
self.mock_megaphone.polled.clear()
self.mock_megaphone.polled.wait(timeout=5)
MOCK_MP_SERVICES = {"kinto:123": "ver2"}
MOCK_MP_POLLED.clear()
MOCK_MP_POLLED.wait(timeout=5)

result = yield client.get_broadcast(2)
assert result["broadcasts"]["kinto:123"] == "ver2"
Expand All @@ -1071,9 +1043,10 @@ def test_broadcast_update_on_connect(self):

@inlineCallbacks
def test_broadcast_update_on_connect_with_errors(self):
self.mock_megaphone.services = {"kinto:123": "ver1"}
self.mock_megaphone.polled.clear()
self.mock_megaphone.polled.wait(timeout=5)
global MOCK_MP_SERVICES
MOCK_MP_SERVICES = {"kinto:123": "ver1"}
MOCK_MP_POLLED.clear()
MOCK_MP_POLLED.wait(timeout=5)

old_ver = {"kinto:123": "ver0", "kinto:456": "ver1"}
client = Client(self._ws_url)
Expand All @@ -1088,9 +1061,10 @@ def test_broadcast_update_on_connect_with_errors(self):

@inlineCallbacks
def test_broadcast_subscribe(self):
self.mock_megaphone.services = {"kinto:123": "ver1"}
self.mock_megaphone.polled.clear()
self.mock_megaphone.polled.wait(timeout=5)
global MOCK_MP_SERVICES
MOCK_MP_SERVICES = {"kinto:123": "ver1"}
MOCK_MP_POLLED.clear()
MOCK_MP_POLLED.wait(timeout=5)

old_ver = {"kinto:123": "ver0"}
client = Client(self._ws_url)
Expand All @@ -1104,9 +1078,9 @@ def test_broadcast_subscribe(self):
result = yield client.get_broadcast()
assert result["broadcasts"]["kinto:123"] == "ver1"

self.mock_megaphone.services = {"kinto:123": "ver2"}
self.mock_megaphone.polled.clear()
self.mock_megaphone.polled.wait(timeout=5)
MOCK_MP_SERVICES = {"kinto:123": "ver2"}
MOCK_MP_POLLED.clear()
MOCK_MP_POLLED.wait(timeout=5)

result = yield client.get_broadcast(2)
assert result["broadcasts"]["kinto:123"] == "ver2"
Expand All @@ -1115,9 +1089,10 @@ def test_broadcast_subscribe(self):

@inlineCallbacks
def test_broadcast_subscribe_with_errors(self):
self.mock_megaphone.services = {"kinto:123": "ver1"}
self.mock_megaphone.polled.clear()
self.mock_megaphone.polled.wait(timeout=5)
global MOCK_MP_SERVICES
MOCK_MP_SERVICES = {"kinto:123": "ver1"}
MOCK_MP_POLLED.clear()
MOCK_MP_POLLED.wait(timeout=5)

old_ver = {"kinto:123": "ver0", "kinto:456": "ver1"}
client = Client(self._ws_url)
Expand All @@ -1137,9 +1112,10 @@ def test_broadcast_subscribe_with_errors(self):

@inlineCallbacks
def test_broadcast_no_changes(self):
self.mock_megaphone.services = {"kinto:123": "ver1"}
self.mock_megaphone.polled.clear()
self.mock_megaphone.polled.wait(timeout=5)
global MOCK_MP_SERVICES
MOCK_MP_SERVICES = {"kinto:123": "ver1"}
MOCK_MP_POLLED.clear()
MOCK_MP_POLLED.wait(timeout=5)

old_ver = {"kinto:123": "ver1"}
client = Client(self._ws_url)
Expand Down

0 comments on commit 8e59c67

Please sign in to comment.