class TopLevelFormatterTest(unittest.TestCase): def setUp(self): self.handler = LogCapture() self.handler.addFilter(TopLevelFormatter(['test'])) def test_top_level_logger(self): logger = logging.getLogger('test') with self.handler as l: logger.warning('test log msg') l.check(('test', 'WARNING', 'test log msg')) def test_children_logger(self): logger = logging.getLogger('test.test1') with self.handler as l: logger.warning('test log msg') l.check(('test', 'WARNING', 'test log msg')) def test_overlapping_name_logger(self): logger = logging.getLogger('test2') with self.handler as l: logger.warning('test log msg') l.check(('test2', 'WARNING', 'test log msg')) def test_different_name_logger(self): logger = logging.getLogger('different') with self.handler as l: logger.warning('test log msg') l.check(('different', 'WARNING', 'test log msg'))
class TopLevelFormatterTest(unittest.TestCase): def setUp(self): self.handler = LogCapture() self.handler.addFilter(TopLevelFormatter(["test"])) def test_top_level_logger(self): logger = logging.getLogger("test") with self.handler as l: logger.warning("test log msg") l.check(("test", "WARNING", "test log msg")) def test_children_logger(self): logger = logging.getLogger("test.test1") with self.handler as l: logger.warning("test log msg") l.check(("test", "WARNING", "test log msg")) def test_overlapping_name_logger(self): logger = logging.getLogger("test2") with self.handler as l: logger.warning("test log msg") l.check(("test2", "WARNING", "test log msg")) def test_different_name_logger(self): logger = logging.getLogger("different") with self.handler as l: logger.warning("test log msg") l.check(("different", "WARNING", "test log msg"))
class TestPluginBase(unittest.TestCase): """ Base class for our tests, which sets up log capture. """ def setUp(self): self.lc = LogCapture() self.lc.setLevel(logging.DEBUG) self.lc.addFilter(test_common.MyLogCaptureFilter()) self.addCleanup(self.cleanup) def cleanup(self): self.lc.uninstall()
class TestHTMLFilter(TestCase): def setUp(self): self.log = LogCapture() self.logger = getLogger() self.log.addFilter(HTMLFilter()) def tearDown(self): self.log.uninstall() def test_plain_string(self): self.logger.info('foo') self.log.check(('root', 'INFO', 'foo'), ) def test_html_string(self): self.logger.info('<foo &bar>') self.log.check(('root', 'INFO', '<foo &bar>'), ) def test_with_params_string(self): self.logger.info('%s', 'foo') self.log.check(('root', 'INFO', 'foo'), ) def test_plain_unicode(self): self.logger.info(u"accentu\u00E9") self.log.check(('root', 'INFO', u'accentu\xe9'), ) def test_html_unicode(self): self.logger.info(u"<u\u00E9 &bar>") self.log.check(('root', 'INFO', u'<u\xe9 &bar>'), ) def test_with_params_unicode(self): self.logger.info(u"\u00E9%s", u"accentu\u00E9") self.log.check(('root', 'INFO', u'\xe9accentu\xe9'), ) def test_some_object(self): class AnObject(object): def __repr__(self): return 'obj' __str__ = __repr__ self.logger.info(AnObject()) self.log.check(('root', 'INFO', 'obj'), )
class TestHTMLFilter(TestCase): def setUp(self): self.log = LogCapture() self.logger = getLogger() self.log.addFilter(HTMLFilter()) def tearDown(self): self.log.uninstall() def test_plain_string(self): self.logger.info('foo') self.log.check(('root', 'INFO', 'foo'),) def test_html_string(self): self.logger.info('<foo &bar>') self.log.check(('root', 'INFO', '<foo &bar>'),) def test_with_params_string(self): self.logger.info('%s', 'foo') self.log.check(('root', 'INFO', 'foo'),) def test_plain_unicode(self): self.logger.info(u"accentu\u00E9") self.log.check(('root', 'INFO', u'accentu\xe9'),) def test_html_unicode(self): self.logger.info(u"<u\u00E9 &bar>") self.log.check(('root', 'INFO', u'<u\xe9 &bar>'),) def test_with_params_unicode(self): self.logger.info(u"\u00E9%s", u"accentu\u00E9") self.log.check(('root', 'INFO', u'\xe9accentu\xe9'),) def test_some_object(self): class AnObject(object): def __repr__(self): return 'obj' __str__ = __repr__ self.logger.info(AnObject()) self.log.check(('root', 'INFO', 'obj'),)
class TestVpcBotoInteractions(unittest.TestCase): """ We use the moto mock framework for boto in order to test our interactions with boto. """ def setUp(self): self.lc = LogCapture() self.lc.addFilter(test_common.MyLogCaptureFilter()) self.addCleanup(self.cleanup) # Hosts are chosen randomly from a prefix group. Therefore, we need to # seed the random number generator with a specific value in order to # have reproducible tests. random.seed(123) def cleanup(self): self.lc.uninstall() @mock_ec2_deprecated def make_mock_vpc(self): """ Use plain (but mocked) boto functions to create a small VPC with two subnets and two instances as a basis for our tests. (not quite sure why this doesn't run in setUp(). """ con = boto.vpc.connect_to_region("ap-southeast-2") # Note that moto doesn't seem to honor the subnet and VPC address # ranges, it seems all instances always get something random from a # 10/8 range. self.new_vpc = con.create_vpc('10.0.0.0/16') self.new_subnet_a = con.create_subnet(self.new_vpc.id, '10.1.0.0/16') self.new_subnet_b = con.create_subnet(self.new_vpc.id, '10.2.0.0/16') res1 = con.run_instances('ami-1234abcd', subnet_id=self.new_subnet_a.id) res2 = con.run_instances('ami-1234abcd', subnet_id=self.new_subnet_b.id) self.i1 = res1.instances[0] self.i2 = res2.instances[0] self.i1ip = self.i1.private_ip_address self.i2ip = self.i2.private_ip_address @mock_ec2_deprecated def test_connect(self): self.make_mock_vpc() # With a test VPC created, we now test our own functions # In the mocked test the meta data won't contain the info we need (vpc # and region name), because the emulated EC2 instance isn't in any # region or vpc. meta = vpc.get_ec2_meta_data() self.assertTrue(meta == {}) self.assertRaises(VpcRouteSetError, vpc.connect_to_region, "blah") con = vpc.connect_to_region("ap-southeast-2") # Error when specifying non-existent VPC self.assertRaises(VpcRouteSetError, vpc.get_vpc_overview, con, "non-existent-vpc", "ap-southeast-2") # Get the default: First VPC if no VPC is specified d = vpc.get_vpc_overview(con, None, "ap-southeast-2") self.assertEqual(d['vpc'].id, "vpc-be745e76") # Get specified VPC d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.assertEqual(d['vpc'].id, "vpc-be745e76") self.assertEqual( sorted([ 'subnets', 'route_tables', 'instance_by_id', 'instances', 'subnet_rt_lookup', 'zones', 'vpc' ]), sorted(d.keys())) self.assertEqual(self.new_vpc.id, d['vpc'].id) self.assertTrue(self.new_subnet_a.id in [s.id for s in d['subnets']]) self.assertTrue(self.new_subnet_b.id in [s.id for s in d['subnets']]) self.assertTrue(len(d['zones']) == 3) self.assertTrue(len(d['route_tables']) == 1) self.assertTrue(len(d['instance_by_id'].keys()) == 2) self.assertTrue(d['instance_by_id'][self.i1.id].id == self.i1.id) self.assertTrue(d['instance_by_id'][self.i2.id].id == self.i2.id) self.assertRaises(VpcRouteSetError, vpc.find_instance_and_eni_by_ip, d, "9.9.9.9") # Non existent IP self.assertTrue( vpc.find_instance_and_eni_by_ip(d, self.i1ip)[0].id == self.i1.id) self.assertTrue( vpc.find_instance_and_eni_by_ip(d, self.i2ip)[0].id == self.i2.id) def _prepare_mock_env(self): self.make_mock_vpc() con = vpc.connect_to_region("ap-southeast-2") d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") i1, eni1 = vpc.find_instance_and_eni_by_ip(d, self.i1ip) i2, eni2 = vpc.find_instance_and_eni_by_ip(d, self.i2ip) rt_id = d['route_tables'][0].id return con, d, i1, eni1, i2, eni2, rt_id @mock_ec2_deprecated def test_process_route_spec_config(self): con, d, i1, eni1, i2, eni2, rt_id = self._prepare_mock_env() route_spec = {u"10.1.0.0/16": [self.i1ip, self.i2ip]} # Process a simple route spec, a route should have been added self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, [], []) # One of the hosts is randomly chosen. We seeded the random number # generator at in this module, so we know that it will choose the # second host in this case. self.lc.check( ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- adding route in RT '%s' " "10.1.0.0/16 -> %s (%s, %s)" % (rt_id, self.i1ip, i1.id, eni1.id))) # One of the two IPs questionable, switch over d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, [], [self.i1ip]) self.lc.check( ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- eni in route in RT 'rtb-84dc7f2c' can't be found: " "10.1.0.0/16 -> (none) (instance '%s')" % i1.id), ('root', 'INFO', "--- updating existing route in RT '%s' 10.1.0.0/16 -> " "%s (%s, %s) (old IP: None, reason: old IP failed/questionable " "or not eligible anymore)" % (rt_id, self.i2ip, i2.id, eni2.id))) # Now switch back d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, [], [self.i2ip]) self.lc.check( ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- eni in route in RT 'rtb-84dc7f2c' can't be found: " "10.1.0.0/16 -> (none) (instance '%s')" % i2.id), ('root', 'INFO', "--- updating existing route in RT '%s' 10.1.0.0/16 -> " "%s (%s, %s) (old IP: None, reason: old IP failed/questionable " "or not eligible anymore)" % (rt_id, self.i1ip, i1.id, eni1.id))) # One of the two IPs failed, switch over d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, [self.i1ip], []) self.lc.check( ('root', 'DEBUG', 'Route spec processing. Failed IPs: %s' % self.i1ip), ('root', 'INFO', "--- eni in route in RT 'rtb-84dc7f2c' can't be found: " "10.1.0.0/16 -> (none) (instance '%s')" % i1.id), ('root', 'INFO', "--- updating existing route in RT '%s' 10.1.0.0/16 -> " "%s (%s, %s) (old IP: None, reason: old IP failed/questionable " "or not eligible anymore)" % (rt_id, self.i2ip, i2.id, eni2.id))) # Now all IPs for a route have failed d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, [self.i1ip, self.i2ip], []) self.lc.check( ('root', 'DEBUG', 'Route spec processing. Failed IPs: %s,%s' % (self.i1ip, self.i2ip)), ('root', 'INFO', "--- eni in route in RT 'rtb-84dc7f2c' can't be found: " "10.1.0.0/16 -> (none) (instance '%s')" % i2.id), ('root', 'WARNING', '--- cannot find available target for route update 10.1.0.0/16! ' 'Nothing I can do...')) # Add new route, remove old one route_spec = {u"10.2.0.0/16": [self.i1ip]} d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, [], []) self.lc.check( ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- eni in route in RT 'rtb-84dc7f2c' can't be found: " "10.1.0.0/16 -> (none) (instance '%s')" % i2.id), ('root', 'INFO', "--- route not in spec, deleting in RT '%s': " "10.1.0.0/16 -> ... ((unknown), (unknown))" % rt_id), ('root', 'INFO', "--- adding route in RT '%s' " "10.2.0.0/16 -> %s (%s, %s)" % (rt_id, self.i1ip, i1.id, eni1.id))) # Protect old route (ignore_routes), add new route, watch the old route # NOT disappear. CURRENT_STATE.ignore_routes.append("10.2.0.0/16") # protected route route_spec = {u"10.3.0.0/16": [self.i1ip]} d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, [], []) # See in the logs that 10.2.0.0/16 wasn't deleted, even though it's not # in the route spec anymore. self.lc.check( ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- adding route in RT '%s' " "10.3.0.0/16 -> %s (%s, %s)" % (rt_id, self.i1ip, i1.id, eni1.id))) @mock_ec2_deprecated def test_add_new_route(self): con, d, i1, eni1, i2, eni2, rt_id = self._prepare_mock_env() self.lc.clear() vpc._add_new_route("10.9.0.0/16", self.i1ip, d, con, rt_id) self.lc.check(('root', 'INFO', "--- adding route in RT '%s' " "10.9.0.0/16 -> %s (%s, %s)" % (rt_id, self.i1ip, i1.id, eni1.id))) self.lc.clear() vpc._add_new_route("10.9.0.0/16", "99.99.99.99", d, con, rt_id) self.lc.check( ('root', 'ERROR', "*** failed to add route in RT '%s' " "10.9.0.0/16 -> 99.99.99.99 (Could not find instance/eni " "for '99.99.99.99' in VPC '%s'.)" % (rt_id, self.new_vpc.id))) @mock_ec2_deprecated def test_update_route(self): con, d, i1, eni1, i2, eni2, rt_id = self._prepare_mock_env() vpc._add_new_route("10.9.0.0/16", self.i1ip, d, con, rt_id) self.lc.clear() vpc._update_route("10.9.0.0/16", self.i2ip, self.i1ip, d, con, rt_id, "foobar") self.lc.check( ('root', 'INFO', "--- updating existing route in RT '%s' " "10.9.0.0/16 -> %s (%s, %s) " "(old IP: %s, reason: foobar)" % (rt_id, self.i2ip, i2.id, eni2.id, self.i1ip))) self.lc.clear() vpc._update_route("10.9.0.0/16", "9.9.9.9", self.i2ip, d, con, rt_id, "foobar") self.lc.check( ('root', 'ERROR', "*** failed to update route in RT '%s' " "10.9.0.0/16 -> %s (Could not find instance/eni " "for '9.9.9.9' in VPC '%s'.)" % (rt_id, self.i2ip, self.new_vpc.id))) # Trying to update a non-existent route self.lc.clear() vpc._update_route("10.9.9.9/16", self.i1ip, self.i2ip, d, con, rt_id, "foobar") self.lc.check( ('root', 'INFO', "--- updating existing route in RT '%s' 10.9.9.9/16 -> %s " "(%s, %s) (old IP: %s, reason: foobar)" % (rt_id, self.i1ip, i1.id, eni1.id, self.i2ip)), ('root', 'ERROR', "*** failed to update route in RT '%s' 10.9.9.9/16 -> %s " "(replace_route failed: u'%s~10.9.9.9/16')" % (rt_id, self.i2ip, rt_id))) @mock_ec2_deprecated def test_get_real_instance_if_mismatched(self): con, d, i1, eni1, i2, eni2, rt_id = self._prepare_mock_env() self.assertFalse(vpc._get_real_instance_if_mismatch(d, None, i1, eni1)) ret = vpc._get_real_instance_if_mismatch(d, self.i1ip, i1, eni1) self.assertFalse(ret) for inst, eni in [(i2, eni2), (i1, eni2), (i2, eni1), (i1, None), (None, eni1), (i2, None), (None, eni2), (None, None)]: ret = vpc._get_real_instance_if_mismatch(d, self.i1ip, inst, eni) self.assertEqual(ret.id, i1.id) @mock_ec2_deprecated def test_get_host_for_route(self): con, d, i1, eni1, i2, eni2, rt_id = self._prepare_mock_env() vpc._add_new_route("10.9.0.0/16", self.i1ip, d, con, rt_id) rt = d['route_tables'][0] self.assertEqual(rt.id, rt_id) route = rt.routes[0] # Moto doesn't maintain intance or interface ID in the routes # correctly, so need to set this one manually route.instance_id = i1.id route.interface_id = eni1.id # Find correct host for route (the passed in cidr is only used for # logging) self.assertEqual((i1.id, self.i1ip, eni1.id), vpc._get_host_for_route(d, route, rt, "cidr-log")) # Look for broken route without an instance id route.instance_id = None self.lc.clear() self.assertEqual(('(unknown)', None, '(unknown)'), vpc._get_host_for_route(d, route, rt, "cidr-log")) self.lc.check( ('root', 'INFO', "--- obsoleted route in RT '%s' cidr-log -> " "... (doesn't point to instance anymore)" % rt_id)) # Look for broken route with instance id for non-existent instance route.instance_id = "blah" self.lc.clear() self.assertEqual(('(unknown)', None, '(unknown)'), vpc._get_host_for_route(d, route, rt, "cidr-log")) self.lc.check(('root', 'INFO', "--- instance in route in RT '%s' can't be found: " "cidr-log -> ... (instance 'blah')" % rt_id)) @mock_ec2_deprecated def test_update_existing_routes(self): con, d, i1, eni1, i2, eni2, rt_id = self._prepare_mock_env() vpc._add_new_route("10.0.0.0/16", self.i1ip, d, con, rt_id) route_spec = {u"10.0.0.0/16": [self.i1ip]} routes_in_rts = {} # Test that a protected route doesn't get updated self.lc.clear() CURRENT_STATE.ignore_routes = ["10.0.0.0/8"] vpc._update_existing_routes(route_spec, [], [], d, con, routes_in_rts) self.assertTrue(rt_id in CURRENT_STATE.vpc_state['route_tables']) self.assertTrue( "10.0.0.0/16" in CURRENT_STATE.vpc_state['route_tables'][rt_id]) self.assertTrue("Ignored: Protected CIDR" in CURRENT_STATE. vpc_state['route_tables'][rt_id]["10.0.0.0/16"]) self.lc.check() # Now we un-protect the route and try again. Moto doesn't manage the # instance or interface ID in routes, so this will fail, because the # route doesn't look like it's pointing to an instance CURRENT_STATE.ignore_routes = [] vpc._update_existing_routes(route_spec, [], [], d, con, routes_in_rts) self.assertTrue("Ignored: Not a route to an instance" in CURRENT_STATE. vpc_state['route_tables'][rt_id]["10.0.0.0/16"]) self.lc.check() # Now we manually set the instance and eni id in the route, so that the # test can proceed. rt = d['route_tables'][0] self.assertEqual(rt.id, rt_id) route = rt.routes[0] # Moto doesn't maintain intance or interface ID in the routes # correctly, so need to set this one manually. This time the route spec # won't contain eligible hosts. route.instance_id = i1.id route.interface_id = eni1.id self.lc.clear() route_spec = {u"10.0.0.0/16": []} vpc._update_existing_routes(route_spec, [], [], d, con, routes_in_rts) self.lc.check( ('root', 'INFO', "--- route not in spec, deleting in RT '%s': 10.0.0.0/16 -> " "... (%s, %s)" % (rt_id, i1.id, eni1.id))) # Get a refresh, since deleting via Boto interface doesn't update the # cached vpc-info d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") # There shouldn't be any routes left now rt = d['route_tables'][0] self.assertFalse(rt.routes) # Now try again, but with proper route spec. First we need to create # the route again and manually... vpc._add_new_route("10.0.0.0/16", self.i1ip, d, con, rt_id) # ... and update our cached vpc info d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") rt = d['route_tables'][0] route = rt.routes[0] route.instance_id = i1.id route.interface_id = eni1.id route_spec = {u"10.0.0.0/16": [self.i2ip]} # Only IP for spec is in failed IPs, can't do anything self.lc.clear() vpc._update_existing_routes(route_spec, [self.i2ip], [], d, con, routes_in_rts) self.lc.check(('root', 'WARNING', '--- cannot find available target for route update ' '10.0.0.0/16! Nothing I can do...')) # Now with available IPs self.lc.clear() vpc._update_existing_routes(route_spec, [], [], d, con, routes_in_rts) self.lc.check( ('root', 'INFO', "--- updating existing route in RT '%s' 10.0.0.0/16 -> " "%s (%s, %s) (old IP: %s, reason: old IP failed/questionable " "or not eligible anymore)" % (rt_id, self.i2ip, i2.id, eni2.id, self.i1ip))) # Now with same route spec again d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") rt = d['route_tables'][0] route = rt.routes[0] route.instance_id = i2.id route.interface_id = eni2.id self.lc.clear() routes_in_rts = {} vpc._update_existing_routes(route_spec, [], [], d, con, routes_in_rts) self.lc.check(('root', 'INFO', "--- route exists already in RT '%s': 10.0.0.0/16 -> " "%s (%s, %s)" % (rt_id, self.i2ip, i2.id, eni2.id))) @mock_ec2_deprecated def test_add_missing_routes(self): con, d, i1, eni1, i2, eni2, rt_id = self._prepare_mock_env() route_spec = {u"10.0.0.0/16": [self.i1ip]} routes_in_rts = {} self.lc.clear() vpc._update_existing_routes(route_spec, [], [], d, con, routes_in_rts) self.lc.check() self.lc.clear() vpc._add_missing_routes(route_spec, [], [], {}, d, con, routes_in_rts) self.lc.check( ('root', 'INFO', "--- adding route in RT '%s' 10.0.0.0/16 -> " "%s (%s, %s)" % (rt_id, self.i1ip, i1.id, eni1.id))) # The route exists already (passed in routes_in_rts), so no new route # should be created here. self.lc.clear() vpc._add_missing_routes(route_spec, [], [], {"10.0.0.0/16": self.i1ip}, d, con, {rt_id: ["10.0.0.0/16"]}) self.lc.check() # Force a route creation by passing nothing for routes_in_rts and # passing in a 'previous' choice for the router self.lc.clear() vpc._add_missing_routes(route_spec, [], [], {"10.0.0.0/16": self.i1ip}, d, con, {rt_id: []}) self.lc.check( ('root', 'INFO', "--- adding route in RT '%s' 10.0.0.0/16 -> " "%s (%s, %s)" % (rt_id, self.i1ip, i1.id, eni1.id))) # Now try the same with the only possible IP in failed IPs. self.lc.clear() vpc._add_missing_routes(route_spec, [self.i1ip], [], {}, d, con, {rt_id: []}) self.lc.check(('root', 'WARNING', '--- cannot find available target for route addition ' '10.0.0.0/16! Nothing I can do...')) @mock_ec2_deprecated def test_multi_address(self): # Testing that we can find interfaces, which have the specified IP on a # second, private IP address con, d, i1, eni1, i2, eni2, rt_id = self._prepare_mock_env() priv = eni1.private_ip_addresses[0] priv = boto.ec2.networkinterface.PrivateIPAddress( private_ip_address="10.9.9.9", primary=False) eni1.private_ip_addresses.append(priv) self.lc.clear() route_spec = {"10.0.0.0/16": ["10.9.9.9"]} self.lc.clear() vpc._add_missing_routes(route_spec, [], [], {}, d, con, {rt_id: []}) self.lc.check(('root', 'INFO', "--- adding route in RT '%s' 10.0.0.0/16 -> 10.9.9.9 " "(%s, %s)" % (rt_id, i1.id, eni1.id))) @mock_ec2_deprecated def test_handle_spec(self): self.make_mock_vpc() # Need to take a peek inside the VPC so we can properly evaluate the # output later on con = vpc.connect_to_region("ap-southeast-2") d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") i, eni = vpc.find_instance_and_eni_by_ip(d, self.i1ip) rt_id = d['route_tables'][0].id route_spec = {u"10.2.0.0/16": [self.i1ip]} # Test handle_spec vid = self.new_vpc.id self.lc.clear() vpc.handle_spec("ap-southeast-2", vid, route_spec, [], []) self.lc.check( ('root', 'DEBUG', 'Handle route spec'), ('root', 'DEBUG', "Connecting to AWS region 'ap-southeast-2'"), ('root', 'DEBUG', "Retrieving information for VPC '%s'" % vid), ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- adding route in RT '%s' 10.2.0.0/16 -> %s (%s, %s)" % (rt_id, self.i1ip, self.i1.id, eni.id))) # mock the get_instance_private_ip_from_route() function in vpc. Reason # being: The boto mocking library (moto) doesn't handle ENIs in routes # correctly. Therefore, a match against the information we get from the # routes will never work. So, we provide a wrapper, which fills the # instance's ENI information into the route. This means that this # function now will always match. It's good for testing the 'match' # part of the code. old_func = vpc.get_instance_private_ip_from_route def my_get_instance_private_ip_from_route(instance, route): route.interface_id = instance.interfaces[0].id return old_func(instance, route) vpc.get_instance_private_ip_from_route = \ my_get_instance_private_ip_from_route self.lc.clear() vpc.handle_spec("ap-southeast-2", vid, route_spec, [], []) vpc.get_instance_private_ip_from_route = old_func self.lc.check( ('root', 'DEBUG', 'Handle route spec'), ('root', 'DEBUG', "Connecting to AWS region 'ap-southeast-2'"), ('root', 'DEBUG', "Retrieving information for VPC '%s'" % vid), ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- route exists already in RT '%s': 10.2.0.0/16 -> " "%s (%s, %s)" % (rt_id, self.i1ip, self.i1.id, eni.id)))
class TestVpcBotoInteractions(unittest.TestCase): """ We use the moto mock framework for boto in order to test our interactions with boto. """ def setUp(self): self.lc = LogCapture() self.lc.addFilter(test_common.MyLogCaptureFilter()) self.addCleanup(self.cleanup) # Hosts are chosen randomly from a prefix group. Therefore, we need to # seed the random number generator with a specific value in order to # have reproducible tests. random.seed(123) def cleanup(self): self.lc.uninstall() @mock_ec2_deprecated def make_mock_vpc(self): """ Use plain (but mocked) boto functions to create a small VPC with two subnets and two instances as a basis for our tests. (not quite sure why this doesn't run in setUp(). """ con = boto.vpc.connect_to_region("ap-southeast-2") # Note that moto doesn't seem to honor the subnet and VPC address # ranges, it seems all instances always get something random from a # 10/8 range. self.new_vpc = con.create_vpc('10.0.0.0/16') self.new_subnet_a = con.create_subnet(self.new_vpc.id, '10.1.0.0/16') self.new_subnet_b = con.create_subnet(self.new_vpc.id, '10.2.0.0/16') res1 = con.run_instances('ami-1234abcd', subnet_id=self.new_subnet_a.id) res2 = con.run_instances('ami-1234abcd', subnet_id=self.new_subnet_b.id) self.i1 = res1.instances[0] self.i2 = res2.instances[0] self.i1ip = self.i1.private_ip_address self.i2ip = self.i2.private_ip_address @mock_ec2_deprecated def test_connect(self): self.make_mock_vpc() # With a test VPC created, we now test our own functions con = vpc.connect_to_region("ap-southeast-2") d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.assertEqual( sorted([ 'subnets', 'route_tables', 'instance_by_id', 'instances', 'zones', 'vpc' ]), sorted(d.keys())) self.assertEqual(self.new_vpc.id, d['vpc'].id) self.assertTrue(self.new_subnet_a.id in [s.id for s in d['subnets']]) self.assertTrue(self.new_subnet_b.id in [s.id for s in d['subnets']]) self.assertTrue(len(d['zones']) == 3) self.assertTrue(len(d['route_tables']) == 1) self.assertTrue(len(d['instance_by_id'].keys()) == 2) self.assertTrue(d['instance_by_id'][self.i1.id].id == self.i1.id) self.assertTrue(d['instance_by_id'][self.i2.id].id == self.i2.id) self.assertTrue( vpc.find_instance_and_eni_by_ip(d, self.i1ip)[0].id == self.i1.id) self.assertTrue( vpc.find_instance_and_eni_by_ip(d, self.i2ip)[0].id == self.i2.id) @mock_ec2_deprecated def test_process_route_spec_config(self): self.make_mock_vpc() con = vpc.connect_to_region("ap-southeast-2") d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") i1, eni1 = vpc.find_instance_and_eni_by_ip(d, self.i1ip) i2, eni2 = vpc.find_instance_and_eni_by_ip(d, self.i2ip) rt_id = d['route_tables'][0].id route_spec = {u"10.1.0.0/16": [self.i1ip, self.i2ip]} # Process a simple route spec, a route should have been added self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, []) # One of the hosts is randomly chosen. We seeded the random number # generator at in this module, so we know that it will choose the # second host in this case. self.lc.check( ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- adding route in RT '%s' " "10.1.0.0/16 -> %s (%s, %s)" % (rt_id, self.i2ip, i2.id, eni2.id))) # One of the two IPs failed, switch over d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, [self.i1ip]) self.lc.check( ('root', 'DEBUG', 'Route spec processing. Failed IPs: %s' % self.i1ip), ('root', 'INFO', "--- updating existing route in RT '%s' 10.1.0.0/16 -> " "%s (%s, %s) (old IP: None, reason: old IP failed or not " "eligible anymore)" % (rt_id, self.i2ip, i2.id, eni2.id))) # Now all IPs for a route have failed d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, [self.i1ip, self.i2ip]) self.lc.check( ('root', 'DEBUG', 'Route spec processing. Failed IPs: %s,%s' % (self.i1ip, self.i2ip)), ('root', 'WARNING', '--- cannot find available target for route update 10.1.0.0/16! ' 'Nothing I can do...')) # Add new route, remove old one route_spec = {u"10.2.0.0/16": [self.i1ip]} d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") self.lc.clear() vpc.process_route_spec_config(con, d, route_spec, []) self.lc.check( ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- route not in spec, deleting in RT '%s': " "10.1.0.0/16 -> ... (%s, (unknown))" % (rt_id, i2.id)), ('root', 'INFO', "--- adding route in RT '%s' " "10.2.0.0/16 -> %s (%s, %s)" % (rt_id, self.i1ip, i1.id, eni1.id))) @mock_ec2_deprecated def test_handle_spec(self): self.make_mock_vpc() # Need to take a peek inside the VPC so we can properly evaluate the # output later on con = vpc.connect_to_region("ap-southeast-2") d = vpc.get_vpc_overview(con, self.new_vpc.id, "ap-southeast-2") i, eni = vpc.find_instance_and_eni_by_ip(d, self.i1ip) rt_id = d['route_tables'][0].id route_spec = {u"10.2.0.0/16": [self.i1ip]} # Test handle_spec vid = self.new_vpc.id self.lc.clear() vpc.handle_spec("ap-southeast-2", vid, route_spec, []) self.lc.check( ('root', 'DEBUG', 'Handle route spec'), ('root', 'DEBUG', "Connecting to AWS region 'ap-southeast-2'"), ('root', 'DEBUG', "Retrieving information for VPC '%s'" % vid), ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- adding route in RT '%s' 10.2.0.0/16 -> %s (%s, %s)" % (rt_id, self.i1ip, self.i1.id, eni.id))) # mock the get_instance_private_ip_from_route() function in vpc. Reason # being: The boto mocking library (moto) doesn't handle ENIs in routes # correctly. Therefore, a match against the information we get from the # routes will never work. So, we provide a wrapper, which fills the # instance's ENI information into the route. This means that this # function now will always match. It's good for testing the 'match' # part of the code. old_func = vpc.get_instance_private_ip_from_route def my_get_instance_private_ip_from_route(instance, route): route.interface_id = instance.interfaces[0].id return old_func(instance, route) vpc.get_instance_private_ip_from_route = \ my_get_instance_private_ip_from_route self.lc.clear() vpc.handle_spec("ap-southeast-2", vid, route_spec, []) vpc.get_instance_private_ip_from_route = old_func self.lc.check( ('root', 'DEBUG', 'Handle route spec'), ('root', 'DEBUG', "Connecting to AWS region 'ap-southeast-2'"), ('root', 'DEBUG', "Retrieving information for VPC '%s'" % vid), ('root', 'DEBUG', 'Route spec processing. No failed IPs.'), ('root', 'INFO', "--- route exists already in RT '%s': 10.2.0.0/16 -> " "%s (%s, %s)" % (rt_id, self.i1ip, self.i1.id, eni.id)))
class TestRouteSpec(TestBase): def setUp(self): self.lc = LogCapture() self.lc.addFilter(test_common.MyLogCaptureFilter()) self.temp_dir = tempfile.mkdtemp() self.addCleanup(self.cleanup) def cleanup(self): self.lc.uninstall() shutil.rmtree(self.temp_dir) def test_file_event_watcher(self): # # Test for the detection of file events. # # Create a small test file global RES abs_fname = self.temp_dir + "/r.spec" class MyQueue(object): def put(self, msg): self.msg = msg with open(abs_fname, "w+") as f: myq = MyQueue() handler = configfile.RouteSpecChangeEventHandler( route_spec_fname = "r.spec", route_spec_abspath = abs_fname, q_route_spec = myq, plugin = None) # Install the file observer on the directory observer_thread = Observer() observer_thread.schedule(handler, self.temp_dir) observer_thread.start() # A write event to the file should be detected f.write("blah") f.flush() time.sleep(1) # not instantaneous, so need to wait a little # File is malformed, so should not have received a message self.assertTrue(myq.msg is None) # A new file created in the temp directory should not create an # event with open(self.temp_dir + "/foo", "w+") as f2: f2.write("blah") f2.flush() time.sleep(1) self.assertTrue(myq.msg is None) # Check that we received the right log messages about the file self.lc.check( ('root', 'INFO', 'Detected file change event for %s' % abs_fname), ('root', 'ERROR', "Config ignored: Cannot open file: [Errno 2] " "No such file or directory: 'r.spec'")) def test_route_spec_parser(self): # # Test the spec parsing function with a number of different inputs, # valid as well as malformed. # test_specs = [ { "inp" : { "10.1.0.0/16" : ["1.1.1.1", "2.2.2.2"], "10.2.0.0/16" : ["3.3.3.3"] }, "res" : "IDENT" }, { "inp" : { "10.1.0.0/16" : ["1.1.1.1", "2.2.2.2", "2.2.2.2"], "10.2.0.0/16" : ["3.3.3.3"] }, "res" : { "10.1.0.0/16" : ["1.1.1.1", "2.2.2.2"], "10.2.0.0/16" : ["3.3.3.3"] }, }, { # malformed list of IPs "inp" : { "10.1.0.0/16" : "Foo", }, "res" : None }, { # malformed IP in list "inp" : { "10.1.0.0/16" : ["1.1.1.", "2.2.2.2"], }, "res" : None }, { # malformed top level type "inp" : "Foo", "res" : None } ] for test_data in test_specs: if test_data['res'] is None: self.assertRaises(ValueError, watcher.common.parse_route_spec_config, test_data['inp']) else: if test_data['res'] == 'IDENT': expected_out = test_data['inp'] else: expected_out = test_data['res'] res = watcher.common.parse_route_spec_config(test_data['inp']) self.assertEqual(expected_out, res)
class TestWatcherConfigfile(TestBase): def additional_setup(self): self.temp_dir = tempfile.mkdtemp() self.abs_fname = self.temp_dir + "/r.spec" self.conf = { "file" : self.abs_fname, "region_name" : "dummy-region", "vpc_id" : "dummy-vpc", "mode" : "configfile", "health" : "icmpecho", "icmp_check_interval" : 2 } self.watcher_plugin_class = \ main.load_plugin("configfile", DEFAULT_WATCHER_PLUGIN_MOD) self.health_plugin_class = \ main.load_plugin("icmpecho", DEFAULT_HEALTH_PLUGIN_MOD) # The watcher thread needs to have a config file available right at the # start, even if there's nothing in it self.write_config({}) def setUp(self): self.lc = LogCapture() self.lc.setLevel(logging.DEBUG) self.lc.addFilter(test_common.MyLogCaptureFilter()) self.additional_setup() self.addCleanup(self.cleanup) self.old_handle_spec = vpc.handle_spec # Monkey patch the handle_spec function, which is called by the # watcher. The handle_spec function is defined in the VPC module. # However, it was directly imported by the watcher module, so it's now # a copy in the watcher module namespace. Thus, the patch has to be # done actually in the watcher module. For safety, we'll do it in both # the vpc and watcher module. def new_handle_spec(*args, **kwargs): pass watcher.handle_spec = vpc.handle_spec = new_handle_spec def additional_cleanup(self): shutil.rmtree(self.temp_dir) def cleanup(self): self.lc.uninstall() watcher.handle_spec = vpc.handle_spec = self.old_handle_spec self.additional_cleanup() def write_config(self, data): with open(self.abs_fname, "w+") as f: f.write(json.dumps(data)) def start_thread_log_tuple(self): return [ ('root', 'INFO', "Configfile watcher plugin: Starting to watch route spec file " "'%s' for changes..." % self.abs_fname) ] def change_event_log_tuple(self): return ('root', 'INFO', "Detected file change event for %s" % self.abs_fname) def test_watcher_thread_no_config(self): os.remove(self.abs_fname) watcher_plugin, health_plugin = \ watcher.start_plugins( self.conf, self.watcher_plugin_class, self.health_plugin_class, 2) time.sleep(0.5) # Config file doesn't exist yet, so we should get an error. # Health monitor is started with a second delay, so no messages from # there, yet. l = self.start_thread_log_tuple() l.extend([ ('root', 'ERROR', "Config ignored: Cannot open file: " "[Errno 2] No such file or directory: '%s'" % self.abs_fname), ('root', 'INFO', 'ICMPecho health monitor plugin: Starting to watch instances.') ]) self.lc.check(*l) watcher.stop_plugins(watcher_plugin, health_plugin) def test_watcher_thread_wrong_config(self): watcher_plugin, health_plugin = \ watcher.start_plugins( self.conf, self.watcher_plugin_class, self.health_plugin_class, 2) time.sleep(1.2) self.lc.clear() inp = "MALFORMED" self.write_config(inp) time.sleep(1) # Config file malformed l = [ self.change_event_log_tuple(), ('root', 'ERROR', 'Config ignored: Expected dictionary at top level') ] self.lc_compare(l) watcher.stop_plugins(watcher_plugin, health_plugin) def test_watcher_thread(self): # Monkey patch the healthcheck method of the ICMP health monitor class, # since we don't really want to send out ICMP echo requests when we run # the tests. Will indicate failure for all IP addresses starting with # "3." def new_do_health_checks(s, addrs): return [a for a in addrs if a.startswith("3.")], [] # We do this in the class, before the plugin is instantiated self.health_plugin_class.do_health_checks = new_do_health_checks watcher_plugin, health_plugin = \ watcher.start_plugins( self.conf, self.watcher_plugin_class, self.health_plugin_class, 2) time.sleep(2) l = self.start_thread_log_tuple() l.extend([ ('root', 'INFO', 'ICMPecho health monitor plugin: Starting to watch instances.'), ('root', 'DEBUG', 'Checking live IPs: (none alive)')]) self.lc.check(*l) self.lc.clear() inp = { u"10.1.0.0/16" : [u"1.1.1.1", u"2.2.2.2"], u"10.2.0.0/16" : [u"3.3.3.3"] } self.write_config(inp) time.sleep(2) watcher._event_monitor_loop( "dummy-region", "dummy-vpc", watcher_plugin, health_plugin, iterations=1, sleep_time=0.5) time.sleep(2) self.lc.check( self.change_event_log_tuple(), ('root', 'DEBUG', 'Checking live IPs: (none alive)'), ('root', 'DEBUG', 'New route spec detected. Updating health-monitor ' 'with: 1.1.1.1,2.2.2.2,3.3.3.3'), ('root', 'DEBUG', 'event_monitor_loop ended: Global stop'), ('root', 'DEBUG', u'Checking live IPs: 1.1.1.1,2.2.2.2,3.3.3.3'), ('root', 'INFO', u'Currently failed IPs: 3.3.3.3')) self.lc.clear() inp = { u"10.1.0.0/16" : [u"4.4.4.4", u"2.2.2.2"], u"10.2.0.0/16" : [u"3.3.3.3"] } self.write_config(inp) time.sleep(1) """ Remove this check: The log messages may come through in a different order, which isn't a problem. self.lc.check( ('root', 'INFO', 'Detected file change event for %s' % self.abs_fname), ('root', 'DEBUG', 'Checking live IPs: 1.1.1.1,2.2.2.2')) """ self.lc.clear() watcher._event_monitor_loop( "dummy-region", "dummy-vpc", watcher_plugin, health_plugin, iterations=1, sleep_time=0.5) time.sleep(2) self.lc.check( ('root', 'DEBUG', 'New route spec detected. Updating health-monitor ' 'with: 2.2.2.2,3.3.3.3,4.4.4.4'), ('root', 'DEBUG', 'event_monitor_loop ended: Global stop'), ('root', 'DEBUG', u'Checking live IPs: 2.2.2.2,4.4.4.4')) self.lc.clear() watcher._event_monitor_loop( "dummy-region", "dummy-vpc", watcher_plugin, health_plugin, iterations=2, sleep_time=1, route_check_time_interval=1) time.sleep(2) self.lc.check( ('root', 'DEBUG', u'Checking live IPs: 2.2.2.2,4.4.4.4'), ('root', 'DEBUG', 'Time for regular route check'), ('root', 'DEBUG', 'event_monitor_loop ended: Global stop'), ('root', 'DEBUG', u'Checking live IPs: 2.2.2.2,4.4.4.4')) watcher.stop_plugins(watcher_plugin, health_plugin)