Don't crash when receiving unknown configuration variables

Instead of crashing if the monkey deserializes an unknown configuration
variable, send an error message to the current monkey server and keep on
working.

Add utnittests.

fixes #26
This commit is contained in:
Evstifeev Roman 2016-09-27 23:50:47 +03:00
parent 569a9b083e
commit f1dca7fa86
7 changed files with 75 additions and 3 deletions

View File

@ -1 +0,0 @@
__author__ = 'itamar'

View File

@ -45,6 +45,11 @@ def _cast_by_example(value, example):
class Configuration(object):
def from_dict(self, data):
"""
Get a dict of config variables, set known variables as attributes on self.
Return dict of unknown variables encountered.
"""
unknown_variables = {}
for key, value in data.items():
if key.startswith('_'):
continue
@ -55,9 +60,11 @@ class Configuration(object):
try:
default_value = getattr(Configuration, key)
except AttributeError:
raise
unknown_variables[key] = value
continue
setattr(self, key, _cast_by_example(value, default_value))
return unknown_variables
def as_dict(self):
result = {}

View File

@ -124,7 +124,7 @@ class ControlClient(object):
return
try:
WormConfiguration.from_dict(reply.json().get('config'))
unknown_variables = WormConfiguration.from_dict(reply.json().get('config'))
LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),))
except Exception, exc:
# we don't continue with default conf here because it might be dangerous
@ -132,6 +132,23 @@ class ControlClient(object):
WormConfiguration.current_server, reply._content, exc)
raise Exception("Couldn't load from from server's configuration, aborting. %s" % exc)
if unknown_variables:
ControlClient.send_config_error()
@staticmethod
def send_config_error():
if not WormConfiguration.current_server:
return
try:
requests.patch("https://%s/api/monkey/%s" % (WormConfiguration.current_server, GUID),
data=json.dumps({'config_error': True}),
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
except Exception, exc:
LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc)
return {}
@staticmethod
def check_for_stop():
ControlClient.load_control_config()

View File

@ -12,3 +12,5 @@ psutil
PyInstaller
ecdsa
netifaces
mock
nose

View File

View File

@ -0,0 +1,45 @@
# -*- coding: UTF-8 -*-
# NOTE: Launch all tests with `nosetests` command from chaos_monkey dir.
import json
import unittest
from mock import Mock, patch
import control
from config import GUID
class ReportConfigErrorTestCase(unittest.TestCase):
"""
When unknown config variable received form the island server, skip it and report config
error back to the server.
"""
config_response = Mock(json=Mock(return_value={'config': {'blah': 'blah'}}))
def teardown(self):
patch.stopall()
def test_config(self):
patch('control.requests.patch', Mock()).start()
patch('control.WormConfiguration', Mock(current_server='127.0.0.1:123')).start()
# GIVEN the server with uknown config variable
patch('control.requests.get', Mock(return_value=self.config_response)).start()
# WHEN monkey tries to load config from server
control.ControlClient.load_control_config()
# THEN she reports config error back to the server
control.requests.patch.assert_called_once_with(
"https://127.0.0.1:123/api/monkey/%s" % GUID,
data=json.dumps({'config_error': True}),
headers={'content-type': 'application/json'},
verify=False,
proxies=control.ControlClient.proxies)
if __name__ == '__main__':
unittest.main()

View File

@ -86,6 +86,8 @@ class Monkey(restful.Resource):
update['$set']['config'] = monkey_json['config']
if 'tunnel' in monkey_json:
update['$set']['tunnel'] = monkey_json['tunnel']
if 'config_error' in monkey_json:
update['$set']['config_error'] = monkey_json['config_error']
return mongo.db.monkey.update({"guid": guid}, update, upsert=False)