Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
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
Ejemplo n.º 3
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)
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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'])
Ejemplo n.º 9
0
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'])