diff --git a/chaos_monkey/__init__.py b/chaos_monkey/__init__.py deleted file mode 100644 index 05a457b0c..000000000 --- a/chaos_monkey/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'itamar' diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index addb9fd54..b26c911b0 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -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 = {} diff --git a/chaos_monkey/control.py b/chaos_monkey/control.py index f8f3e3ee0..cdeb47a76 100644 --- a/chaos_monkey/control.py +++ b/chaos_monkey/control.py @@ -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() diff --git a/chaos_monkey/requirements.txt b/chaos_monkey/requirements.txt index 8c77333f9..34f24c78f 100644 --- a/chaos_monkey/requirements.txt +++ b/chaos_monkey/requirements.txt @@ -12,3 +12,5 @@ psutil PyInstaller ecdsa netifaces +mock +nose diff --git a/chaos_monkey/test/__init__.py b/chaos_monkey/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/chaos_monkey/test/config__test.py b/chaos_monkey/test/config__test.py new file mode 100644 index 000000000..fccde2f0d --- /dev/null +++ b/chaos_monkey/test/config__test.py @@ -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() diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index ccf3fe995..b05c42e70 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -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)