def setUp(self): """Create our simulated clock and scaling group.""" self.clock = Clock() self.pool = object() self.treq = object() self.queried_server_ids = [] class FakeRCS(object): endpoints = {'nova': 'novaurl'} token = "token" @attributes(['id', 'pool', 'treq']) class FakeNova(object): def get_addresses(nova_self, rcs): self.assertIs(nova_self.pool, self.pool) self.assertIs(nova_self.treq, self.treq) self.queried_server_ids.append(nova_self.id) return defer.succeed({ 'addresses': { 'private': [ {'addr': '10.0.0.{0}'.format( len(self.queried_server_ids)), 'version': 4} ] } }) self.rcs = FakeRCS() self.sg = ScalingGroup(group_config={}, pool=self.pool, reactor=self.clock, treq=self.treq, server_client=FakeNova)
def setUp(self): """Create our simulated clock and scaling group. We link the group to the clock, giving us explicit (and, for the purposes of our test, synchronous) control over the scaling group's concept of elapsed time. """ self.clock = Clock() self.sg = ScalingGroup(group_config={}, pool=None, reactor=self.clock) self.counter = 0
def create_group(self, **kwargs): """ :return: a tuple of the scaling group with (the helper's pool) and the server name prefix used for the scaling group. """ if self.clbs: # allow us to override the CLB setup kwargs.setdefault( 'use_lbs', [clb.scaling_group_spec() for clb in self.clbs]) kwargs.setdefault("flavor_ref", flavor_ref) kwargs.setdefault("min_entities", 0) server_name_prefix = "{}-{}".format( random_string(), reactor.seconds()) if "server_name_prefix" in kwargs: server_name_prefix = "{}-{}".format(kwargs['server_name_prefix'], server_name_prefix) kwargs['server_name_prefix'] = server_name_prefix return ( ScalingGroup( group_config=create_scaling_group_dict(**kwargs), treq=self.treq, pool=self.pool), server_name_prefix)
def setUp(self): """ Establish an HTTP connection pool and commonly used resources for each test. The HTTP connection pool is important for maintaining a clean Twisted reactor. """ self.helper = TestHelper(self) self.rcs = TestResources() self.identity = get_identity(self.helper.pool) scaling_group_config = { 'launchConfiguration': { 'args': { 'stack': { 'template': { 'heat_template_version': '2015-04-30', 'resources': { 'rand': { 'type': 'OS::Heat::RandomString' } } } } }, 'type': 'launch_stack' }, 'groupConfiguration': { 'name': 'test_launch_stack', 'cooldown': 0, 'minEntities': 0, 'maxEntities': 10 }, 'scalingPolicies': [], } self.group = ScalingGroup(group_config=scaling_group_config, treq=self.helper.treq, pool=self.helper.pool) return self.identity.authenticate_user( self.rcs, resources=get_resource_mapping(), region=region)
def setUp(self): """ Establish an HTTP connection pool and commonly used resources for each test. The HTTP connection pool is important for maintaining a clean Twisted reactor. """ self.helper = TestHelper(self) self.rcs = TestResources() self.identity = get_identity(self.helper.pool) scaling_group_config = { 'launchConfiguration': { 'args': { 'stack': { 'template': { 'heat_template_version': '2015-04-30', 'resources': { 'rand': {'type': 'OS::Heat::RandomString'} } } } }, 'type': 'launch_stack' }, 'groupConfiguration': { 'name': 'test_launch_stack', 'cooldown': 0, 'minEntities': 0, 'maxEntities': 10 }, 'scalingPolicies': [], } self.group = ScalingGroup(group_config=scaling_group_config, treq=self.helper.treq, pool=self.helper.pool) return self.identity.authenticate_user( self.rcs, resources=get_resource_mapping(), region=region)
class WaitForStateTestCase(SynchronousTestCase): empty_group_state = { "group": { "paused": False, "pendingCapacity": 0, "name": "blah", "active": [], "activeCapacity": 0, "desiredCapacity": 0, } } group_state_w_2_servers = { "group": { "paused": False, "pendingCapacity": 0, "name": "blah", "active": ["hello", "world"], "activeCapacity": 2, "desiredCapacity": 0, } } def setUp(self): """Create our simulated clock and scaling group. We link the group to the clock, giving us explicit (and, for the purposes of our test, synchronous) control over the scaling group's concept of elapsed time. """ self.clock = Clock() self.sg = ScalingGroup(group_config={}, pool=None, reactor=self.clock) self.counter = 0 def get_scaling_group_state_happy(self, rcs, success_codes=None): """This method implements a synchronous simulation of what we'd expect to see over the wire on an HTTP API transaction. This method replaces the actual ScalingGroup method on instances. (See setUp for an example of how this method is used.) This version emulates completion after a period of time. """ self.assertEquals(success_codes, [200]) if self.counter == self.threshold: return defer.succeed((200, self.group_state_w_2_servers)) else: self.counter = self.counter + 1 return defer.succeed((200, self.empty_group_state)) def get_scaling_group_state_timeout(self, rcs, success_codes=None): """This method implements a synchronous simulation of what we'd expect to see over the wire on an HTTP API transaction. This method replaces the actual ScalingGroup method on instances. (See setUp for an example of how this method is used.) This version never yields the desired number of servers. """ self.assertEquals(success_codes, [200]) return defer.succeed((200, self.empty_group_state)) def test_poll_until_happy(self): """When wait_for_state completes before timeout, we expect our deferred to fire successfully. """ self.sg.group_id = 'abc' self.sg.get_scaling_group_state = self.get_scaling_group_state_happy self.threshold = 25 d = self.sg.wait_for_state(None, HasActive(2), clock=self.clock) for _ in range(24): self.clock.advance(10) self.assertNoResult(d) self.clock.advance(10) self.successResultOf(d) def test_poll_until_timeout(self): """When wait_for_state exceeds a maximum time threshold, we expect it to raise an exception. """ self.sg.group_id = 'abc' self.sg.get_scaling_group_state = self.get_scaling_group_state_timeout d = self.sg.wait_for_state(None, HasActive(2), clock=self.clock) for _ in range(59): self.clock.advance(10) self.assertNoResult(d) self.clock.advance(10) self.failureResultOf(d, TimedOutError)
class GetServicenetIPs(SynchronousTestCase): """ Tests for :func:`ScalingGroup.get_servicenet_ips`. """ def setUp(self): """Create our simulated clock and scaling group.""" self.clock = Clock() self.pool = object() self.treq = object() self.queried_server_ids = [] class FakeRCS(object): endpoints = {'nova': 'novaurl'} token = "token" @attributes(['id', 'pool', 'treq']) class FakeNova(object): def get_addresses(nova_self, rcs): self.assertIs(nova_self.pool, self.pool) self.assertIs(nova_self.treq, self.treq) self.queried_server_ids.append(nova_self.id) return defer.succeed({ 'addresses': { 'private': [ {'addr': '10.0.0.{0}'.format( len(self.queried_server_ids)), 'version': 4} ] } }) self.rcs = FakeRCS() self.sg = ScalingGroup(group_config={}, pool=self.pool, reactor=self.clock, treq=self.treq, server_client=FakeNova) def test_queries_for_provided_server_ids(self): """ If server IDs are provided, IPs are queried for those server IDs. And if the same server ID is given multiple times, only one query is made for any given server ID. """ server_ids = ['1', '2', '2', '2', '3'] d = self.sg.get_servicenet_ips(self.rcs, server_ids) result = self.successResultOf(d) self.assertEqual(['10.0.0.1', '10.0.0.2', '10.0.0.3'], sorted(result.values())) self.assertEqual(['1', '2', '3'], sorted(result.keys())) self.assertEqual(['1', '2', '3'], sorted(self.queried_server_ids)) def test_gets_active_server_ids_if_server_ids_not_provided(self): """ If server IDs are not provided, IPs are queried for the active servers on the group server IDs. """ def get_scaling_group_state(_, success_codes): self.assertEqual(success_codes, [200]) return defer.succeed(( 200, {'group': {'active': [{'id': '11'}, {'id': '12'}]}} )) self.sg.get_scaling_group_state = get_scaling_group_state d = self.sg.get_servicenet_ips(self.rcs) self.assertEqual({'11': '10.0.0.1', '12': '10.0.0.2'}, self.successResultOf(d)) self.assertEqual(['11', '12'], self.queried_server_ids)
class TestLaunchStack(unittest.TestCase): """Tests making sure launch_stack launch configurations can be used.""" def setUp(self): """ Establish an HTTP connection pool and commonly used resources for each test. The HTTP connection pool is important for maintaining a clean Twisted reactor. """ self.helper = TestHelper(self) self.rcs = TestResources() self.identity = get_identity(self.helper.pool) scaling_group_config = { 'launchConfiguration': { 'args': { 'stack': { 'template': { 'heat_template_version': '2015-04-30', 'resources': { 'rand': {'type': 'OS::Heat::RandomString'} } } } }, 'type': 'launch_stack' }, 'groupConfiguration': { 'name': 'test_launch_stack', 'cooldown': 0, 'minEntities': 0, 'maxEntities': 10 }, 'scalingPolicies': [], } self.group = ScalingGroup(group_config=scaling_group_config, treq=self.helper.treq, pool=self.helper.pool) return self.identity.authenticate_user( self.rcs, resources=get_resource_mapping(), region=region) def get_stack_list(self): return (self.helper.treq.get( '{}/stacks'.format(self.rcs.endpoints['heat']), headers=headers(str(self.rcs.token)), params={ 'tags': get_stack_tag_for_group(self.group.group_id)}, pool=self.helper.pool) .addCallback(check_success, [200]) .addCallback(self.helper.treq.json_content)) def wait_for_stack_list(self, expected_states, timeout=180, period=10): def check(content): states = pbag([s['stack_status'] for s in content['stacks']]) if not (states == expected_states): msg("Waiting for group {} to reach desired group state.\n" "{} (actual) {} (expected)" .format(self.group.group_id, states, expected_states)) raise TransientRetryError( "Group states of {} did not match expected {})" .format(states, expected_states)) msg("Success: desired group state reached:\n{}" .format(expected_states)) return self.rcs def poll(): return self.get_stack_list().addCallback(check) expected_states = pbag(expected_states) return retry_and_timeout( poll, timeout, can_retry=terminal_errors_except(TransientRetryError), next_interval=repeating_interval(period), clock=reactor, deferred_description=( "Waiting for group {} to reach state {}".format( self.group.group_id, str(expected_states)))) @timeout(180 * 3 + 10) @inlineCallbacks def test_create(self): """ For a launch_stack config, stacks are created, checked, updated, and deleted through Heat. """ p = ScalingPolicy(set_to=5, scaling_group=self.group) scale_up = ScalingPolicy(set_to=7, scaling_group=self.group) scale_down = ScalingPolicy(set_to=1, scaling_group=self.group) yield self.group.start(self.rcs, self) yield p.start(self.rcs, self) yield p.execute(self.rcs) yield self.wait_for_stack_list([u'UPDATE_COMPLETE'] * 5) yield scale_up.start(self.rcs, self) yield scale_up.execute(self.rcs) yield self.wait_for_stack_list( [u'UPDATE_COMPLETE'] * 5 + [u'CREATE_COMPLETE'] * 2) yield scale_down.start(self.rcs, self) yield scale_down.execute(self.rcs) yield self.wait_for_stack_list([u'UPDATE_COMPLETE'])
class TestLaunchStack(unittest.TestCase): """Tests making sure launch_stack launch configurations can be used.""" def setUp(self): """ Establish an HTTP connection pool and commonly used resources for each test. The HTTP connection pool is important for maintaining a clean Twisted reactor. """ self.helper = TestHelper(self) self.rcs = TestResources() self.identity = get_identity(self.helper.pool) scaling_group_config = { 'launchConfiguration': { 'args': { 'stack': { 'template': { 'heat_template_version': '2015-04-30', 'resources': { 'rand': { 'type': 'OS::Heat::RandomString' } } } } }, 'type': 'launch_stack' }, 'groupConfiguration': { 'name': 'test_launch_stack', 'cooldown': 0, 'minEntities': 0, 'maxEntities': 10 }, 'scalingPolicies': [], } self.group = ScalingGroup(group_config=scaling_group_config, treq=self.helper.treq, pool=self.helper.pool) return self.identity.authenticate_user( self.rcs, resources=get_resource_mapping(), region=region) def get_stack_list(self): return (self.helper.treq.get( '{}/stacks'.format(self.rcs.endpoints['heat']), headers=headers(str(self.rcs.token)), params={ 'tags': get_stack_tag_for_group(self.group.group_id) }, pool=self.helper.pool).addCallback( check_success, [200]).addCallback(self.helper.treq.json_content)) def wait_for_stack_list(self, expected_states, timeout=180, period=10): def check(content): states = pbag([s['stack_status'] for s in content['stacks']]) if not (states == expected_states): msg("Waiting for group {} to reach desired group state.\n" "{} (actual) {} (expected)".format(self.group.group_id, states, expected_states)) raise TransientRetryError( "Group states of {} did not match expected {})".format( states, expected_states)) msg("Success: desired group state reached:\n{}".format( expected_states)) return self.rcs def poll(): return self.get_stack_list().addCallback(check) expected_states = pbag(expected_states) return retry_and_timeout( poll, timeout, can_retry=terminal_errors_except(TransientRetryError), next_interval=repeating_interval(period), clock=reactor, deferred_description=( "Waiting for group {} to reach state {}".format( self.group.group_id, str(expected_states)))) @timeout(180 * 3 + 10) @inlineCallbacks def test_create(self): """ For a launch_stack config, stacks are created, checked, updated, and deleted through Heat. """ p = ScalingPolicy(set_to=5, scaling_group=self.group) scale_up = ScalingPolicy(set_to=7, scaling_group=self.group) scale_down = ScalingPolicy(set_to=1, scaling_group=self.group) yield self.group.start(self.rcs, self) yield p.start(self.rcs, self) yield p.execute(self.rcs) yield self.wait_for_stack_list([u'UPDATE_COMPLETE'] * 5) yield scale_up.start(self.rcs, self) yield scale_up.execute(self.rcs) yield self.wait_for_stack_list([u'UPDATE_COMPLETE'] * 5 + [u'CREATE_COMPLETE'] * 2) yield scale_down.start(self.rcs, self) yield scale_down.execute(self.rcs) yield self.wait_for_stack_list([u'UPDATE_COMPLETE'])